前言
上一篇博客《json:你或许还不知道的使用的坑(一)》说了一些可能被误用的方式,主要是讲了结构体的匿名字段的使用。现在就分析下json
的Marshal
,这部分代码整体逻辑比较简单,主要的流程有三步:
反射获取结构体的每个字段的编码方法,如果字段是结构体就递归获取该字段结构体的每个编码器方法;
获取完之后对结构体的字段做一些排序,过滤(
json
的序列化一些处理规则,这里就是第一篇文章讲的一些莫名其妙的现象);然后就调用每个字段的编码方法对每个字段进行编码;
一、获取编码器方法和排序、过滤
首先从json.Marshal
这个入口方法进去看看主要逻辑:
1func Marshal(v interface{}) ([]byte, error) {
2 e := newEncodeState()
3
4 err := e.marshal(v, encOpts{escapeHTML: true})
5 if err != nil {
6 return nil, err
7 }
8 buf := append([]byte(nil), e.Bytes()...)
9
10 encodeStatePool.Put(e)
11
12 return buf, nil
13}
先忽略newEncodeState
的作用,我们先关注主要逻辑,大致意思是序列化的结果会存在这个里面。
进入marshal
去看看具体实现:
1func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
2 ...
3 e.reflectValue(reflect.ValueOf(v), opts)
4 return nil
5}
这部分代码也没有好说的,只是获得了序列化的反射对象。
1func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
2 valueEncoder(v)(e, v, opts)
3}
这部分代码的意思言简意赅,包括了前言讲的3个步骤,valueEncoder(v)
获取了编码器方法,valueEncoder(v)(e, v, opts)
就做编码操作获取结果。valueEncoder(v)
里面就调用了typeEncoder(v.Type())
,获取序列化对象的类型的每个字段类型的编码器方法,进去分析看看:
1func typeEncoder(t reflect.Type) encoderFunc {
2 if fi, ok := encoderCache.Load(t); ok {
3 return fi.(encoderFunc)
4 }
5 var (
6 wg sync.WaitGroup
7 f encoderFunc
8 )
9 // 防止多迭代情况下使用还没被初始化完成的f
10 wg.Add(1)
11 fi, loaded := encoderCache.LoadOrStore(t, encoderFunc(func(e *encodeState, v reflect.Value, opts encOpts) {
12 wg.Wait()
13 f(e, v, opts)
14 }))
15 if loaded {
16 return fi.(encoderFunc)
17 }
18 // Compute the real encoder and replace the indirect func with it.
19 f = newTypeEncoder(t, true)
20 wg.Done()
21 encoderCache.Store(t, f)
22 return f
23}
typeEncoder
有优化逻辑在里面,只要获取了某个类型的编码器方法就会放到缓存里面,所有12行就使用wg.Wait()
防止迭代情况下使用还没被初始化完成的编码器方法,必须等待19行真正获取到编码器方法才去编码。下面终于到了主题,进入到newTypeEncoder(t, true)
。
1// 根据类型找到相应的类型编码器
2func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
3 // 非指针变量实现了Marshaler则
4 if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(marshalerType) {
5 // 不是指针但是实现了Marshaler
6 return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
7 }
8 if t.Implements(marshalerType) {
9 return marshalerEncoder
10 }
11 if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(textMarshalerType) {
12 return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
13 }
14 if t.Implements(textMarshalerType) {
15 return textMarshalerEncoder
16 }
17 switch t.Kind() {
18 case reflect.Bool:
19 return boolEncoder
20 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
21 return intEncoder
22 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
23 return uintEncoder
24 case reflect.Float32:
25 return float32Encoder
26 case reflect.Float64:
27 return float64Encoder
28 case reflect.String:
29 return stringEncoder
30 case reflect.Interface:
31 return interfaceEncoder
32 case reflect.Struct:
33 return newStructEncoder(t)
34 case reflect.Map:
35 return newMapEncoder(t)
36 case reflect.Slice:
37 return newSliceEncoder(t)
38 case reflect.Array:
39 return newArrayEncoder(t)
40 case reflect.Ptr:
41 return newPtrEncoder(t)
42 default:
43 return unsupportedTypeEncoder
44 }
45}
说明下上面代码的意思:
17~44行就是根据序列化值的类型,获得相应的编码器方法。所以可以知道当给类型取别名的时候是不支持的,除非自定义序列化。后面会着重分析
newStructEncoder(t)
,其他的编码器方法会选择性分析。第4行,当序列化对象的指针类型实现了
Marshaler
,但是此时序列化对象却是值类型,那么就最好获得序列化对象的地址,这样就可以调用自定义的Marshaler
的MarshalJSON
方法,当然前提是该序列化对象是可以寻址的。如果是不能寻址的就会普通的获取到结构体的各个字段的编码器方法,此时自定义的序列化就无效了。这个坑在上篇文章有提到。第8行判断该序列化对象是否实现了接口
Marshaler
,如果实现了接口就走自定义的序列化逻辑。序列化对象是指针的时候,会成功进入到这个判断。11行~16行和上面的
Marshaler
逻辑差不多,只是这里是判断TextMarshaler
接口,也是用来自定义序列化的。在上一篇文章也介绍过该自定义序列方式的使用。addrMarshalerEncoder
和marshalerEncoder
主要逻辑就是调用自定义序列化方式。
下面分析newStructEncoder
代码:当序列化对象的类型是结构体会往这里走:
1func newStructEncoder(t reflect.Type) encoderFunc {
2 se := structEncoder{fields: cachedTypeFields(t)}
3 return se.encode
4}
然后进去看看cachedTypeFields
,该方法会获取类型为结构体的t的各个字段的编码器方法。这里面还加了有缓存类型的编码器方法的能力,这不重要,我们直接关注它的获取编码器的逻辑:typeFields
。
1func typeFields(t reflect.Type) structFields {
2 // Anonymous fields to explore at the current level and the next.
3 current := []field{}
4 next := []field{{typ: t}}
5
6 // Count of queued names for current level and the next.
7 var count, nextCount map[reflect.Type]int
8
9 // Types already visited at an earlier level.
10 visited := map[reflect.Type]bool{}
11
12 // Fields found.
13 var fields []field
14
15 // Buffer to run HTMLEscape on field names.
16 var nameEscBuf bytes.Buffer
17
18 for len(next) > 0 {
19 current, next = next, current[:0]
20 count, nextCount = nextCount, map[reflect.Type]int{}
21
22 for _, f := range current {
23 if visited[f.typ] {
24 continue
25 }
26 visited[f.typ] = true
27
28 // Scan f.typ for fields to include.
29 for i := 0; i 30 sf := f.typ.Field(i)
31 isUnexported := sf.PkgPath != ""
32 if sf.Anonymous {
33 t := sf.Type
34 if t.Kind() == reflect.Ptr {
35 t = t.Elem()
36 }
37 // 匿名结构体不被导出就打住
38 if isUnexported && t.Kind() != reflect.Struct {
39 // Ignore embedded fields of unexported non-struct types.
40 continue
41 }
42 // Do not ignore embedded fields of unexported struct types
43 // since they may have exported fields.
44 } else if isUnexported {
45 // Ignore unexported non-embedded fields.
46 // 不可导出就打住
47 continue
48 }
49 tag := sf.Tag.Get("json")
50 // tag有"-"则忽略
51 if tag == "-" {
52 continue
53 }
54 name, opts := parseTag(tag)
55 if !isValidTag(name) {
56 name = ""
57 }
58 index := make([]int, len(f.index)+1)
59 copy(index, f.index)
60 index[len(f.index)] = i
61
62 ft := sf.Type
63 if ft.Name() == "" && ft.Kind() == reflect.Ptr {
64 // Follow pointer.
65 ft = ft.Elem()
66 }
67
68 // Only strings, floats, integers, and booleans can be quoted.
69 quoted := false
70 if opts.Contains("string") {
71 switch ft.Kind() {
72 case reflect.Bool,
73 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
74 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
75 reflect.Float32, reflect.Float64,
76 reflect.String:
77 quoted = true
78 }
79 }
80
81 // Record found field and index sequence.
82 // 如果不是一个没有标签的匿名结构体,那么就此打住了,不继续搜索字段
83 if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
84 tagged := name != ""
85 if name == "" {
86 name = sf.Name
87 }
88 field := field{
89 name: name,
90 tag: tagged,
91 index: index,
92 typ: ft,
93 omitEmpty: opts.Contains("omitempty"),
94 quoted: quoted,
95 }
96 field.nameBytes = []byte(field.name)
97 field.equalFold = foldFunc(field.nameBytes)
98
99 // Build nameEscHTML and nameNonEsc ahead of time.
100 // 编码字段的类型
101 nameEscBuf.Reset()
102 nameEscBuf.WriteString(`"`)
103 HTMLEscape(&nameEscBuf, field.nameBytes)
104 nameEscBuf.WriteString(`":`)
105 field.nameEscHTML = nameEscBuf.String()
106 field.nameNonEsc = `"` + field.name + `":`
107
108 fields = append(fields, field)
109 if count[f.typ] > 1 {
110 // If there were multiple instances, add a second,
111 // so that the annihilation code will see a duplicate.
112 // It only cares about the distinction between 1 or 2,
113 // so don't bother generating any more copies.
114 fields = append(fields, fields[len(fields)-1])
115 }
116 continue
117 }
118
119 // 否者就会继续往匿名结构体搜索
120 // Record new anonymous struct to explore in next round.
121 nextCount[ft]++
122 if nextCount[ft] == 1 {
123 next = append(next, field{name: ft.Name(), index: index, typ: ft})
124 }
125 }
126 }
127 }
128
129 sort.Slice(fields, func(i, j int) bool {
130 x := fields
131 // sort field by name, breaking ties with depth, then
132 // breaking ties with "name came from json tag", then
133 // breaking ties with index sequence.
134 if x[i].name != x[j].name {
135 return x[i].name 136 }
137 if len(x[i].index) != len(x[j].index) {
138 return len(x[i].index) len(x[j].index)
139 }
140 if x[i].tag != x[j].tag {
141 return x[i].tag
142 }
143 return byIndex(x).Less(i, j)
144 })
145
146 // Delete all fields that are hidden by the Go rules for embedded fields,
147 // except that fields with JSON tags are promoted.
148
149 // The fields are sorted in primary order of name, secondary order
150 // of field index length. Loop over names; for each name, delete
151 // hidden fields by choosing the one dominant field that survives.
152 out := fields[:0]
153 for advance, i := 0, 0; i len(fields); i += advance {
154 // One iteration per name.
155 // Find the sequence of fields with the name of this first field.
156 fi := fields[i]
157 name := fi.name
158 for advance = 1; i+advance len(fields); advance++ {
159 fj := fields[i+advance]
160 if fj.name != name {
161 break
162 }
163 }
164 if advance == 1 { // Only one field with this name
165 out = append(out, fi)
166 continue
167 }
168 dominant, ok := dominantField(fields[i : i+advance])
169 if ok {
170 out = append(out, dominant)
171 }
172 }
173
174 fields = out
175 sort.Sort(byIndex(fields))
176
177 for i := range fields {
178 f := &fields[i]
179 f.encoder = typeEncoder(typeByIndex(t, f.index))
180 }
181 nameIndex := make(map[string]int, len(fields))
182 for i, field := range fields {
183 nameIndex[field.name] = i
184 }
185 return structFields{fields, nameIndex}
186}
该方法是整个序列化的核心。该方法我准备分为上中下3部分:
整理结构体的各个字段,包括匿名结构体字段。整个18~127行是对结构体字段做广度优先搜索,当遇到没有标签的匿名结构体会继续往下面搜索该匿名结构体的字段,如果不是没有标签的匿名结构体就会停止搜索。58到60行对
field
的index
操作会存储字段的位置,他是一个切片。举例子说明,当结构体是这样的:1type User struct {
2 Name string
3 Profile
4}
5
6type Profile struct {
7 Sex bool
8}那么
Sex
字段的index
就是[1,0],1就表示在User
结构体的位置,0表示在Profile
的位置,这样做是为 了在下面json
序列化对结构体做一些增强的规则(除了go自省的可见性规则),这个说的有点可能不太明白,对照上篇博客,可能好理解些。129到175都是
json
的规则增强部分。129~144行对上面搜索到的字段做排序,排序顺序是字段名、字段的深度、字段是否有标签。当上面3个规则都一样的时候,143行会使用byIndx
的排序,该排序会对该字段在父结构体的顺序排序,也就是如果有下面这种情况:1type User struct {
2 Profile1
3 Profile
4}
5
6type Profile struct {
7 Sex string
8}
9type Profile1 struct {
10 Sex bool
11}那么
Profile1
因为在Profile
的的前面,所以类型为string
的Sex
在类型为bool
的Sex
的前面。
152到172会舍弃一些字段:如果多个字段的名字、字段的深度、要么都标签要么都没被标签的话那么就直接把这几个字段舍弃掉,不编码。否者就取第一个字段编码。175行在从上到下排一次序。因为刚刚可能舍弃了字段,所以顺序可能就会变化了。177到180就是对字段寻找编码器方法,这就是迭代了,逻辑和上面一样。
差不多获取编码器方法的分析就完成了,接下来分析编码。
二、编码字段
接下来就回到了编码调用处:
1func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
2 valueEncoder(v)(e, v, opts)
3}
valueEncoder(v)(e, v, opts)
就是使用编码器方法编码了。上面花了大篇幅介绍结构体的编码器方法获得过程,所以这里先分析结构体的编码处理。直接定位到该一下编码方法:
1func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
2 next := byte('{')
3FieldLoop:
4 for i := range se.fields.list {
5 f := &se.fields.list[i]
6
7 // Find the nested struct field by following f.index.
8 // 找到字段对应结构体的field
9 fv := v
10 for _, i := range f.index {
11 if fv.Kind() == reflect.Ptr {
12 if fv.IsNil() { // 如果是空指针的字段则不做任何处理
13 continue FieldLoop
14 }
15 fv = fv.Elem()
16 }
17 fv = fv.Field(i)
18 }
19
20 // omitEmpty tag 处理,如果为空则不编码
21 if f.omitEmpty && isEmptyValue(fv) {
22 continue
23 }
24 // 开始编码
25 e.WriteByte(next)
26 next = ','
27 if opts.escapeHTML {
28 e.WriteString(f.nameEscHTML)
29 } else {
30 e.WriteString(f.nameNonEsc)
31 }
32 opts.quoted = f.quoted
33 // 用编码方法编码
34 f.encoder(e, fv, opts)
35 }
36 if next == '{' {
37 e.WriteString("{}")
38 } else {
39 e.WriteByte('}')
40 }
41}
编码倒还十分容易理解:
结构体编码的字节肯定要被"{}"包围。
10到18行根据
index
获得字段,关于index的含义在上面已经分析过了。26行表示每个字段用','隔开。
32行
quoted
表示是否要把某些非string
类型的值转为string
结果,也就是""包裹值。34行就会采用每个字段的编码器方法去编码。
下面简单分析下其他的编码器。
三、其他编码器
1.interfaceEncoder
代码如下:
1func interfaceEncoder(e *encodeState, v reflect.Value, opts encOpts) {
2 if v.IsNil() {
3 e.WriteString("null")
4 return
5 }
6 // 如果是指针就迭代调用reflectValue处理
7 e.reflectValue(v.Elem(), opts)
8}
其实就是获得了指针的值,剩余的处理和上面分析的一致。
2.stringEncoder
1func stringEncoder(e *encodeState, v reflect.Value, opts encOpts) {
2 if v.Type() == numberType {
3 numStr := v.String()
4 // In Go1.5 the empty string encodes to "0", while this is not a valid number literal
5 // we keep compatibility so check validity after this.
6 if numStr == "" {
7 numStr = "0" // Number's zero-val
8 }
9 if !isValidNumber(numStr) {
10 e.error(fmt.Errorf("json: invalid number literal %q", numStr))
11 }
12 if opts.quoted {
13 e.WriteByte('"')
14 }
15 e.WriteString(numStr)
16 if opts.quoted {
17 e.WriteByte('"')
18 }
19 return
20 }
21 if opts.quoted {
22 e2 := newEncodeState()
23 // Since we encode the string twice, we only need to escape HTML
24 // the first time.
25 e2.string(v.String(), opts.escapeHTML)
26 e.stringBytes(e2.Bytes(), false)
27 encodeStatePool.Put(e2)
28 } else {
29 e.string(v.String(), opts.escapeHTML)
30 }
31}
注意第二行有个json.Number
类型,该类型表示该字段可以是字符串形式的数。也就是你的结构体可以这么定义:
1type User struct {
2 Name json.Number
3}
如果Name
不是数字的字符串的化,会序列化失败。如没有对该字段作其他限制的话,结果是没有被双引号包围的数字:
1{
2 "Name": 1.0,
3}
3.newMapEncoder
map
需要注意一下:
1func newMapEncoder(t reflect.Type) encoderFunc {
2 switch t.Key().Kind() {
3 case reflect.String,
4 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
5 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
6 default:
7 if !t.Key().Implements(textMarshalerType) {
8 return unsupportedTypeEncoder
9 }
10 }
11 me := mapEncoder{typeEncoder(t.Elem())}
12 return me.encode
13}
只支持string
,int
,int8
,int16
,int32
,int64
,uint
,uint8
,uint16
,uint32
,uint64
,uintptr
,TextMarshaler
。
4.newSliceEncoder
切片的序列化方式:
1func newSliceEncoder(t reflect.Type) encoderFunc {
2 // Byte slices get special treatment; arrays don't.
3 if t.Elem().Kind() == reflect.Uint8 {
4 p := reflect.PtrTo(t.Elem())
5 if !p.Implements(marshalerType) && !p.Implements(textMarshalerType) {
6 return encodeByteSlice
7 }
8 }
9 enc := sliceEncoder{newArrayEncoder(t)}
10 return enc.encode
11}
如果切片的类型是uint8
就采用字节切片的编码器方法encodeByteSlice
,否则采用数组的编码器方法newArrayEncoder
。下面看看encodeByteSlice
的实现:
1func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) {
2 if v.IsNil() {
3 e.WriteString("null")
4 return
5 }
6 s := v.Bytes()
7 e.WriteByte('"')
8 encodedLen := base64.StdEncoding.EncodedLen(len(s))
9 if encodedLen <= len(e.scratch) {
10 // If the encoded bytes fit in e.scratch, avoid an extra
11 // allocation and use the cheaper Encoding.Encode.
12 dst := e.scratch[:encodedLen]
13 base64.StdEncoding.Encode(dst, s)
14 e.Write(dst)
15 } else if encodedLen <= 1024 {
16 // The encoded bytes are short enough to allocate for, and
17 // Encoding.Encode is still cheaper.
18 dst := make([]byte, encodedLen)
19 base64.StdEncoding.Encode(dst, s)
20 e.Write(dst)
21 } else {
22 // The encoded bytes are too long to cheaply allocate, and
23 // Encoding.Encode is no longer noticeably cheaper.
24 enc := base64.NewEncoder(base64.StdEncoding, e)
25 enc.Write(s)
26 enc.Close()
27 }
28 e.WriteByte('"')
29}
注意第6行代码,如果不是字节切片的话,这里会panic,但是这个已经被保证了。
把字节数组给
base64
编码了。这3个
if...else if ... else
是优化的逻辑,scratch
就是为了避免重复分配内存,能利用就利用。
看看编码演示:1type User struct {
2 Des []uint8
3}
4var user = User{
5 Des: []byte("I am a boy!"),
6 }
7json.MarshalIndent(user, "", " ")结果为:
1{
2 "Des": "SSBhbSBhIGJveSE="
3}结果被编码为
base64
编码了。
5.newArrayEncoder
1func newArrayEncoder(t reflect.Type) encoderFunc {
2 enc := arrayEncoder{typeEncoder(t.Elem())}
3 return enc.encode
4}
1func (ae arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
2 e.WriteByte('[')
3 n := v.Len()
4 for i := 0; i 5 if i > 0 {
6 e.WriteByte(',')
7 }
8 ae.elemEnc(e, v.Index(i), opts)
9 }
10 e.WriteByte(']')
11}
比较清晰了,这就是正常的数组编码。
四、encodeState
上面暂时忽略了encodeState
的说明,下面拎出来说下:
1// An encodeState encodes JSON into a bytes.Buffer.
2type encodeState struct {
3 bytes.Buffer // accumulated output
4 scratch [64]byte
5
6 // Keep track of what pointers we've seen in the current recursive call
7 // path, to avoid cycles that could lead to a stack overflow. Only do
8 // the relatively expensive map operations if ptrLevel is larger than
9 // startDetectingCyclesAfter, so that we skip the work if we're within a
10 // reasonable amount of nested pointers deep.
11 // 避免递归调用太深
12 ptrLevel uint
13 ptrSeen map[interface{}]struct{}
14}
bytes.Buffer
会把编码结果放到这里面。1func newEncodeState() *encodeState {
2 if v := encodeStatePool.Get(); v != nil {
3 e := v.(*encodeState)
4 e.Reset()
5 if len(e.ptrSeen) > 0 {
6 panic("ptrEncoder.encode should have emptied ptrSeen via defers")
7 }
8 e.ptrLevel = 0
9 return e
10 }
11 return &encodeState{ptrSeen: make(map[interface{}]struct{})}
12}e.Reset()
可以看出来会利用前面分配的内存,在下次编码的时候,当bytes.Buffer
够大的时候就直接使用该buffer。所以这里应该会越来越大。我还没看到在哪里释放这里的内存。scratch
是一个缓存,在上面字节数组有用到的。ptrLevel
是为了防止递归调用太深了,当递归调用超出一定限制的话就会检测是否是循环依赖,这个在上篇文章有演示该结果。下面的编码方法是指针的编码,startDetectingCyclesAfter
为1000,所以当把该类型被获取编码器方法1000次的话就会报错了,注意只有指针才会有这种情况。1func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
2 if v.IsNil() {
3 e.WriteString("null")
4 return
5 }
6 if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
7 // We're a large number of nested ptrEncoder.encode calls deep;
8 // start checking if we've run into a pointer cycle.
9 // 检测循环指针
10 ptr := v.Interface()
11 if _, ok := e.ptrSeen[ptr]; ok {
12 e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
13 }
14 e.ptrSeen[ptr] = struct{}{}
15 defer delete(e.ptrSeen, ptr)
16 }
17 pe.elemEnc(e, v.Elem(), opts)
18 e.ptrLevel--
19}
总结
以上就简单介绍了json.Marshal
方法,可能还不够仔细,例如某些算法还不够清晰,但是对照我的指导,应该也能容易看懂。后续看看能不能写反序列化的文章,应该和序列化有重叠的地方吧。
如果觉得有用,请关注我的公众号,会持续输出原创云原生相关文章
