文章目录
前言
上一篇博客《json:你或许还不知道的使用的坑(一)》说了一些可能被误用的方式,主要是讲了结构体的匿名字段的使用。现在就分析下json
的Marshal
,这部分代码整体逻辑比较简单,主要的流程有三几步:
- 反射获取结构体的每个字段的编码方法,如果字段是结构体就递归获取该字段结构体的每个编码器方法;
- 获取完之后对结构体的字段做一些排序,过滤(
json
的序列化一些处理规则,这里就是第一篇文章讲的一些莫名其妙的现象); - 然后就调用每个字段的编码方法对每个字段进行编码;
一、获取编码器方法和排序、过滤
首先从json.Marshal
这个入口方法进去看看主要逻辑:
func Marshal(v interface{
}) ([]byte, error) {
e := newEncodeState()
err := e.marshal(v, encOpts{
escapeHTML: true})
if err != nil {
return nil, err
}
buf := append([]byte(nil), e.Bytes()...)
encodeStatePool.Put(e)
return buf, nil
}
先忽略newEncodeState
的作用,我们先关注主要逻辑,大致意思是序列化的结果会存在这个里面。
进入marshal
去看看具体实现:
func (e *encodeState) marshal(v interface{
}, opts encOpts) (err error) {
...
e.reflectValue(reflect.ValueOf(v), opts)
return nil
}
这部分代码也没有好说的,只是获得了序列化的反射对象。
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
valueEncoder(v)(e, v, opts)
}
这部分代码的意思言简意赅,包括了前言讲的3个步骤,valueEncoder(v)
获取了编码器方法,valueEncoder(v)(e, v, opts)
就做编码操作获取结果。valueEncoder(v)
里面就调用了typeEncoder(v.Type())
,获取序列化对象的类型的每个字段类型的编码器方法,进去分析看看:
func typeEncoder(t reflect.Type) encoderFunc {
if fi, ok := encoderCache.Load(t); ok {
return fi.(encoderFunc)
}
var (
wg sync.WaitGroup
f encoderFunc
)
// 防止多迭代情况下使用还没被初始化完成的f
wg.Add(1)
fi, loaded := encoderCache.LoadOrStore(t, encoderFunc(func(e *encodeState, v reflect.Value, opts encOpts) {
wg.Wait()
f(e, v, opts)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = newTypeEncoder(t, true)
wg.Done()
encoderCache.Store(t, f)
return f
}
typeEncoder
有优化逻辑在里面,只要获取了某个类型的编码器方法就会放到缓存里面,所有12行就使用wg.Wait()
防止迭代情况下使用还没被初始化完成的编码器方法,必须等待19行真正获取到编码器方法才去编码。下面终于到了主题,进入到newTypeEncoder(t, true)
。
// 根据类型找到相应的类型编码器
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
// 非指针变量实现了Marshaler则
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(marshalerType) {
// 不是指针但是实现了Marshaler
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(marshalerType) {
return marshalerEncoder
}
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(textMarshalerType) {
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(textMarshalerType) {
return textMarshalerEncoder
}
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}
说明下上面代码的意思:
- 17~44行就是根据序列化值的类型,获得相应的编码器方法。所以可以知道当给类型取别名的时候是不支持的,除非自定义序列化。后面会着重分析
newStructEncoder(t)
,其他的编码器方法会选择性分析。 - 第4行,当序列化对象的指针类型实现了
Marshaler
,但是此时序列化对象却是值类型,那么就最好获得序列化对象的地址,这样就可以调用自定义的Marshaler
的MarshalJSON
方法,当然前提是该序列化对象是可以寻址的。如果是不能寻址的就会普通的获取到结构体的各个字段的编码器方法,此时自定义的序列化就无效了。这个坑在上篇文章有提到。 - 第8行判断该序列化对象是否实现了接口
Marshaler
,如果实现了接口就走自定义的序列化逻辑。序列化对象是指针的时候,会成功进入到这个判断。 - 11行~16行和上面的
Marshaler
逻辑差不多,只是这里是判断TextMarshaler
接口,也是用来自定义序列化的。在上一篇文章也介绍过该自定义序列方式的使用。 addrMarshalerEncoder
和marshalerEncoder
主要逻辑就是调用自定义序列化方式。
下面分析newStructEncoder
代码:当序列化对象的类型是结构体会往这里走:
func newStructEncoder(t reflect.Type) encoderFunc {
se := structEncoder