json 字段变成小写 序列化_json:源码解密序列化(二)

本文深入剖析JSON序列化过程中字段如何变为小写,主要涉及编码器方法获取、排序过滤及编码过程。通过源码分析,揭示序列化逻辑,包括结构体字段编码、interface、string、map、slice和array的处理,以及encodeState在内存管理和递归防护上的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

上一篇博客《json:你或许还不知道的使用的坑(一)》说了一些可能被误用的方式,主要是讲了结构体的匿名字段的使用。现在就分析下jsonMarshal,这部分代码整体逻辑比较简单,主要的流程有三步:

  1. 反射获取结构体的每个字段的编码方法,如果字段是结构体就递归获取该字段结构体的每个编码器方法;

  2. 获取完之后对结构体的字段做一些排序,过滤(json的序列化一些处理规则,这里就是第一篇文章讲的一些莫名其妙的现象);

  3. 然后就调用每个字段的编码方法对每个字段进行编码;

一、获取编码器方法和排序、过滤

首先从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}

说明下上面代码的意思:

  1. 17~44行就是根据序列化值的类型,获得相应的编码器方法。所以可以知道当给类型取别名的时候是不支持的,除非自定义序列化。后面会着重分析newStructEncoder(t),其他的编码器方法会选择性分析。

  2. 第4行,当序列化对象的指针类型实现了Marshaler,但是此时序列化对象却是值类型,那么就最好获得序列化对象的地址,这样就可以调用自定义的MarshalerMarshalJSON方法,当然前提是该序列化对象是可以寻址的。如果是不能寻址的就会普通的获取到结构体的各个字段的编码器方法,此时自定义的序列化就无效了。这个坑在上篇文章有提到。

  3. 第8行判断该序列化对象是否实现了接口Marshaler,如果实现了接口就走自定义的序列化逻辑。序列化对象是指针的时候,会成功进入到这个判断。

  4. 11行~16行和上面的Marshaler逻辑差不多,只是这里是判断TextMarshaler接口,也是用来自定义序列化的。在上一篇文章也介绍过该自定义序列方式的使用。

  5. addrMarshalerEncodermarshalerEncoder主要逻辑就是调用自定义序列化方式。

下面分析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部分:

  1. 整理结构体的各个字段,包括匿名结构体字段。整个18~127行是对结构体字段做广度优先搜索,当遇到没有标签的匿名结构体会继续往下面搜索该匿名结构体的字段,如果不是没有标签的匿名结构体就会停止搜索。58到60行对 fieldindex操作会存储字段的位置,他是一个切片。举例子说明,当结构体是这样的:

    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自省的可见性规则),这个说的有点可能不太明白,对照上篇博客,可能好理解些。

  2. 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的的前面,所以类型为stringSex在类型为boolSex的前面。
    152到172会舍弃一些字段:如果多个字段的名字、字段的深度、要么都标签要么都没被标签的话那么就直接把这几个字段舍弃掉,不编码。否者就取第一个字段编码。175行在从上到下排一次序。因为刚刚可能舍弃了字段,所以顺序可能就会变化了。

  3. 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}

编码倒还十分容易理解:

  1. 结构体编码的字节肯定要被"{}"包围。

  2. 10到18行根据index获得字段,关于index的含义在上面已经分析过了。

  3. 26行表示每个字段用','隔开。

  4. 32行quoted表示是否要把某些非string类型的值转为string结果,也就是""包裹值。

  5. 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}
  1. 注意第6行代码,如果不是字节切片的话,这里会panic,但是这个已经被保证了。

  2. 把字节数组给base64编码了。

  3. 这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}
  1. 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。所以这里应该会越来越大。我还没看到在哪里释放这里的内存。

  2. scratch是一个缓存,在上面字节数组有用到的。

  3. 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方法,可能还不够仔细,例如某些算法还不够清晰,但是对照我的指导,应该也能容易看懂。后续看看能不能写反序列化的文章,应该和序列化有重叠的地方吧。

如果觉得有用,请关注我的公众号,会持续输出原创云原生相关文章

e0d856d35e4ad42bf4b844f07415f8c8.png
在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值