最全golang struct json tag的使用及深入理解_go struct json tag(1),29岁vivo员工吐槽

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

此处为了简洁,具体调用过程略过不讲,直接查看核心代码部分,有兴趣的话,可以查看下完整过程。

1.typeFields

在typeFields中详细的对上面提到的各种用法的tag做了处理,处理后的数据存入fileds,最后在进行编码。

// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type) structFields {
    // Anonymous fields to explore at the current level and the next.
    current := []field{}
    next := []field{{typ: t}}

    // Count of queued names for current level and the next.
    var count, nextCount map[reflect.Type]int

    // Types already visited at an earlier level.
    visited := map[reflect.Type]bool{}

    // Fields found.
    var fields []field

    // Buffer to run HTMLEscape on field names.
    var nameEscBuf bytes.Buffer

    for len(next) > 0 {
        current, next = next, current[:0]
        count, nextCount = nextCount, map[reflect.Type]int{}

        for \_, f := range current {
            if visited[f.typ] {//已处理的过类型跳过
                continue
            }
            visited[f.typ] = true

            // Scan f.typ for fields to include.
            for i := 0; i < f.typ.NumField(); i++ {
                sf := f.typ.Field(i)
                isUnexported := sf.PkgPath != ""
                if sf.Anonymous {//内嵌类型的处理
                    t := sf.Type
                    if t.Kind() == reflect.Ptr {
                        t = t.Elem()
                    }
                    if isUnexported && t.Kind() != reflect.Struct {
                        // Ignore embedded fields of unexported non-struct types.
                        continue//非struct结构的不能导出的key直接跳过
                    }
                    // Do not ignore embedded fields of unexported struct types
                    // since they may have exported fields.
                } else if isUnexported {
                    // Ignore unexported non-embedded fields.
                    continue//不能导出的key直接跳过
                }
                tag := sf.Tag.Get("json")
                if tag == "-" {
                    continue//tag为"-"直接跳过
                }
                name, opts := parseTag(tag)
                if !isValidTag(name) {
                    name = ""//包含特殊字符的无效name
                }
                index := make([]int, len(f.index)+1)
                copy(index, f.index)
                index[len(f.index)] = i

                ft := sf.Type
                if ft.Name() == "" && ft.Kind() == reflect.Ptr {
                    // Follow pointer.
                    ft = ft.Elem()
                }

                // Only strings, floats, integers, and booleans can be quoted.
                quoted := false
                if opts.Contains("string") {//此处为"string" opt的特殊处理,支持的类型如下:
                    switch ft.Kind() {
                    case reflect.Bool,
                        reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
                        reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
                        reflect.Float32, reflect.Float64,
                        reflect.String:
                        quoted = true
                    }
                }

                // Record found field and index sequence.
                if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
                    tagged := name != ""
                    if name == "" {
                        name = sf.Name//未指定或者指定name无效的使用原field的name
                    }
                    field := field{
                        name:      name,
                        tag:       tagged,
                        index:     index,
                        typ:       ft,
                        omitEmpty: opts.Contains("omitempty"),//omitempty确认
                        quoted:    quoted,//是否支持"string" opt
                    }
                    field.nameBytes = []byte(field.name)
                    field.equalFold = foldFunc(field.nameBytes)

                    // Build nameEscHTML and nameNonEsc ahead of time.
                    //两种格式的构建
                    nameEscBuf.Reset()
                    nameEscBuf.WriteString(`"`)
                    HTMLEscape(&nameEscBuf, field.nameBytes)
                    nameEscBuf.WriteString(`":`)
                    field.nameEscHTML = nameEscBuf.String()
                    field.nameNonEsc = `"` + field.name + `":`

                    fields = append(fields, field)//存入fields
                    if count[f.typ] > 1 {
                        // If there were multiple instances, add a second,
                        // so that the annihilation code will see a duplicate.
                        // It only cares about the distinction between 1 or 2,
                        // so don't bother generating any more copies.
                        fields = append(fields, fields[len(fields)-1])
                    }
                    continue
                }

                // Record new anonymous struct to explore in next round.
                nextCount[ft]++
                if nextCount[ft] == 1 {
                    next = append(next, field{name: ft.Name(), index: index, typ: ft})
                }
            }
        }
    }

    ...

    for i := range fields {
        f := &fields[i]
        f.encoder = typeEncoder(typeByIndex(t, f.index))//设置fields的encoder
    }
    nameIndex := make(map[string]int, len(fields))
    for i, field := range fields {
        nameIndex[field.name] = i
    }
    return structFields{fields, nameIndex}
}

2.encode
func newStructEncoder(t reflect.Type) encoderFunc {
    se := structEncoder{fields: cachedTypeFields(t)}
    return se.encode
}

func (se structEncoder) encode(e \*encodeState, v reflect.Value, opts encOpts) {
    next := byte('{')
FieldLoop:
    for i := range se.fields.list {
        f := &se.fields.list[i]

        // Find the nested struct field by following f.index.
        fv := v
        for \_, i := range f.index {
            if fv.Kind() == reflect.Ptr {
                if fv.IsNil() {
                    continue FieldLoop
                }
                fv = fv.Elem()
            }
            fv = fv.Field(i)
        }

        if f.omitEmpty && isEmptyValue(fv) {//"omitempty"的忽略处理,需要值为零值
            continue
        }
        e.WriteByte(next)
        next = ','
        if opts.escapeHTML {
            e.WriteString(f.nameEscHTML)
        } else {
            e.WriteString(f.nameNonEsc)
        }
        opts.quoted = f.quoted
        f.encoder(e, fv, opts)//根据具体类型的编码处理
    }
    if next == '{' {
        e.WriteString("{}")
    } else {
        e.WriteByte('}')
    }
}

以下以int类型intEncoder为例:

func intEncoder(e \*encodeState, v reflect.Value, opts encOpts) {
    b := strconv.AppendInt(e.scratch[:0], v.Int(), 10)
    if opts.quoted {//带有"string" opt添加引号
        e.WriteByte('"')
    }
    e.Write(b)
    if opts.quoted {
        e.WriteByte('"')
    }
}

对于数字类型,如果带有**“string”**则在写入正式值前后添加引号。

对于字符串类型,如果带有**“string”**,原string值再编码时会添加引号,再对结果添加引号,则格式异常,因此需要先对原值进行编码。

func stringEncoder(e \*encodeState, v reflect.Value, opts encOpts) {
    if v.Type() == numberType {
        numStr := v.String()
        // In Go1.5 the empty string encodes to "0", while this is not a valid number literal
        // we keep compatibility so check validity after this.
        if numStr == "" {
            numStr = "0" // Number's zero-val
        }
        if !isValidNumber(numStr) {
            e.error(fmt.Errorf("json: invalid number literal %q", numStr))
        }
        e.WriteString(numStr)
        return
    }
    if opts.quoted {
        sb, err := Marshal(v.String())//注意此处处理
        if err != nil {
            e.error(err)
        }
        e.string(string(sb), opts.escapeHTML)
    } else {
        e.string(v.String(), opts.escapeHTML)
    }
}

func (e \*encodeState) string(s string, escapeHTML bool) {
    e.WriteByte('"')//添加引号
    start := 0
    for i := 0; i < len(s); {
        if b := s[i]; b < utf8.RuneSelf {//字符串中存在特殊的字符时的转义处理
            if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) {
                i++
                continue
            }
            if start < i {
                e.WriteString(s[start:i])
            }
            e.WriteByte('\\')
            switch b {
            case '\\', '"':
                e.WriteByte(b)
            case '\n':
                e.WriteByte('n')
            case '\r':
                e.WriteByte('r')
            case '\t':
                e.WriteByte('t')
            default:
                // This encodes bytes < 0x20 except for \t, \n and \r.
                // If escapeHTML is set, it also escapes <, >, and &
                // because they can lead to security holes when
                // user-controlled strings are rendered into JSON
                // and served to some browsers.
                e.WriteString(`u00`)
                e.WriteByte(hex[b>>4])
                e.WriteByte(hex[b&0xF])
            }
            i++
            start = i
            continue
        }
        c, size := utf8.DecodeRuneInString(s[i:])
        if c == utf8.RuneError && size == 1 {
            if start < i {
                e.WriteString(s[start:i])
            }
            e.WriteString(`\ufffd`)
            i += size
            start = i
            continue
        }
        // U+2028 is LINE SEPARATOR.
        // U+2029 is PARAGRAPH SEPARATOR.
        // They are both technically valid characters in JSON strings,
        // but don't work in JSONP, which has to be evaluated as JavaScript,
        // and can lead to security holes there. It is valid JSON to
        // escape them, so we do so unconditionally.
        // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
        if c == '\u2028' || c == '\u2029' {
            if start < i {
                e.WriteString(s[start:i])
            }
            e.WriteString(`\u202`)
            e.WriteByte(hex[c&0xF])
            i += size
            start = i
            continue


![img](https://img-blog.csdnimg.cn/img_convert/da0119c7dcc152a43c34583f64833b41.png)
![img](https://img-blog.csdnimg.cn/img_convert/5490ec3552358037d8743b765151e8d6.png)
![img](https://img-blog.csdnimg.cn/img_convert/a6f85008fd7c47a3399ac7cd287e69cb.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

[外链图片转存中...(img-C66RuJhM-1715555469579)]
[外链图片转存中...(img-MgXOUMBE-1715555469579)]
[外链图片转存中...(img-0Oe38X7p-1715555469580)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值