golang struct json tag的使用以及深入讲解

目录
  • 一、sturct json tag的使用
    • 1.tag格式说明
    • 2.具体使用格式说明
  • 二、源码角度的设计处理过程
    • 1.typeFields
    • 2.encode
  • 三、总结

一、sturct json tag的使用

1.tag格式说明

struct json tag主要在struct与json数据转换的过程(Marshal/Unmarshal)中使用。

json的tag格式如下:

Key type  `json:"name,opt1,opt2,opts..."`

说明:

  • 变量必须是可导出的(Key首字母必须大写),否则会被忽略处理。
  • 没有json tag或者tag中name省略(但不能少了","),默认使用字段名。
  • name要注意命名的有效性。
  • opt1、opt2等项为可选项,必须使用有限的几个限定的opt的一个或组合,如"omitempty"、"string",使用非限定的opt会发生错误。

2.具体使用格式说明

我们先介绍下源码文档中提供的几种使用方式:

因Marshal与Unmarshal是相反的过程,两者规则是一致的,以下介绍中仅说明了Marshal时的处理。

(1)不指定tag

Field int // “Filed”:0

不指定tag,默认使用变量名称。转换为json时,key为Filed。

(2)直接忽略

Field int json:"-" //注意:必须为"-",不能带有opts

转换时不处理。

(3)指定key名

Field int json:"myName" // “myName”:0

转换为json时,key为myName

(4)"omitempty"零值忽略

Field int json:",omitempty"

转换为json时,值为零值则忽略,否则key为myName

(5)指定key且零值忽略

Field int json:"myName,omitempty"

转换为json时,值为零值则忽略,否则key为myName

(6)指定key为"-"

Field int json:"-," // “-”:0

此项与忽略的区别在于多了个”,“。

(7)“string” opt

以上提到的用法都是常见的,这个比较特殊。

"string"仅适用于字符串、浮点、整数或布尔类型,表示的意思是:将字段的值转换为字符串;解析时,则是将字符串解析为指定的类型。主要用于与javascript通信时数据的转换。

注意:

仅且仅有"string",没有int、number之类的opt。即带"string" opt的字段,编码时仅能将字符串、浮点、整数或布尔类型转换为string类型,反之则不然;解码时可以将string转换为其他类型,反之不然。因为"string"有限制。

Int64String int64 json:",string" // “Int64String”:“0”

“string” opt的使用可以在Marshal/Unmarshal时自动进行数据类型的转换,减少了手动数据转换的麻烦,但是一定要注意使用的范围,对不满足的类型使用,是会报错的。

猜下对string使用"string" opt的结果会是如何呢?

Int64String string json:",string"

我们在了解源码后解答。

二、源码角度的设计处理过程

一切的使用方式肯定在设计时就已限定,我们现在看看源码中的处理过程。

在看实现的过程中,可以思考下使用的方式对不对,还有要注意的地方吗?

对某些地方非常好的实现思路,我们也可以借鉴下,对以后的编程学习大有裨益。

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

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
        }
        i += size
    }
    if start < len(s) {
        e.WriteString(s[start:])
    }
    e.WriteByte('"')
}

在了解完源码的处理过程后,我们对之前提到的问题做个解答。对string类型的字段添加"string" opt,得到的是:

Int64String string json:",string" // “Int64String”: "“1234"”

三、总结

本文主要从源码的角度说明struct json tag的为什么这么使用,以及使用时需要注意的地方。最后重复下重要的几点:

  • 字段必须可导出,tag才有意义
  • 忽略必须使用json:"-",不得带有opts,否则key将会变成"-"
  • "string" opt仅适用于字符串、浮点、整数及布尔类型,意思是可以将这些类型的数据Marshal为string类型,或者将string类型的数据Unmarshal为这些类型。

请勿滥用,尤其是对已经是string类型的数据使用。

到此这篇关于golang struct json tag使用的文章就介绍到这了,更多相关golang struct json tag的使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • golang中json和struct的使用说明

    1.返回json响应结果 在struct的字段后面加入json:"key"可以进行json格式输出,其中key为json的键名 type SuccessResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } func SuccessRsp(ctx *gin.Context, data in

  • golang 实现struct、json、map互相转化

    一.Json和struct互换 (1)Json转struct例子: package main import ( "fmt" "encoding/json" ) type People struct { Name string `json:"name_title"` Age int `json:"age_size"` } func JsonToStructDemo(){ jsonStr := ` { "name_tit

  • golang struct json tag的使用以及深入讲解

    目录 一.sturct json tag的使用 1.tag格式说明 2.具体使用格式说明 二.源码角度的设计处理过程 1.typeFields 2.encode 三.总结 一.sturct json tag的使用 1.tag格式说明 struct json tag主要在struct与json数据转换的过程(Marshal/Unmarshal)中使用. json的tag格式如下: Key type  `json:"name,opt1,opt2,opts..."` 说明: 变量必须是可导出

  • GoLang中Json Tag用法实例总结

    目录 前言 Json中Tag用法汇总 官方 Marshal 函数说明 参考 总结 前言 GoLang中结构体的 JSON Tag 标识(英文名backquote或backtick,反引号 ` 符号包裹的部分内容)一直未明确看过完整规范和使用说明,存在模棱两可,系统整理如下: JSON Tag标签的完整语法,包含哪些选项 不同选项(输出名/-/omitempty/string)的作用及使用范围 特殊注意事项补充 Json中Tag用法汇总 JSON Tag标签格式为:json:"FieldName/

  • golang struct, map, json之间的相互转换

    本文用于记录我在 golang 学习阶段遇到的类型转换问题,针对的是 json .map.struct 之间相互转换的问题,用到的技术 json .mapstructure.reflect 三个类库 公共代码区域 package main import ( "encoding/json" "fmt" "testing" ) type UserInfoVo struct { Id string `json:"id"` UserN

  • golang使用json格式实现增删查改的实现示例

    需求和思路 在一般的小项目或者一个小软件,例如客户端之类的小程序中,可能会需要数据的持久化.但是使用一般的数据库(Mysql)之类的不合适.使用sqlite3这种嵌入式的是个较好的方法,但是Go语言中sqlite3的库是C语言的,Cgo不支持跨平台编译.正是由于这种需求,才想到使用json格式将数据直接保存在文件中. 具体的思路是怎么样呢? 在Go语言中如果要将数据转化成json格式的话,有两种格式 struct 和 map. 如果同时需要增删查改功能的话,将map作为中间格式是比较合适的.接下

  • golang中json小谈之字符串转浮点数的操作

    有时会有这种需求,将一个json数据形如: {"x":"golang", "y":"520.1314"} 中的y反序列化为浮点类型,如果这样写: package main import ( "encoding/json" "fmt" ) type JsonTest struct { X string `json:"x"` Y float64 `json:"y

  • golang中json操作的完全指南

    目录 前言 1. 结构体与JSON互转 2. map与JSON互转 3. 结构体的变量不加tag标签能否正常转成json数据 4. JSON操作的一些小技巧 (1)忽略掉 struct 指定字段 (2)添加额外的字段 (3)合并两个 struct (4)字符串传递给 int类型 (5)一个 json 分成两个struct 补充:GoLang json格式化输出 总结 前言 JSON是一种轻量级的数据交换格式.易于阅读和编写. golang 提供了 encoding/json 包来操作JSON数据

  • 利用Golang解析json数据的方法示例

    本文主要给大家介绍的是关于Golang解析json数据的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 使用 Golang 解析 json 数据,这种 json 格式是对象的数组,官方文档有一个示例: var jsonBlob = []byte(`[ {"Name": "Platypus", "Order": "Monotremata"}, {"Name": "Quoll

  • Golang语言JSON解码函数Unmarshal的使用

    目录 前言 func Unmarshal 普通JSON JSON内嵌普通JSON JSON内嵌数组JSON JSON内嵌具有动态Key的JSON 总结 前言 工作中我们经常会遇到解码JSON格式的数据,本文通过4个示例介绍工作中常用到的四种 JSON格式.在 Golang语言中,通常是使用标准库的encoding/json包中的函数Unmarshal解码JSON格式的数据,下面我们先介绍一下该函数如何使用,再通过4个示例代码来演示一下如何在Golang中解码常见的4种JSON格式的数据. fun

  • Golang中Json的序列化和反序列化的使用

    目录 JSON: 创建格式: 基本数据类型序列化: map序列化: 切片序列化: 反序列化为结构体: 反序列化为map: 反序列化为切片: JSON: JSON(JavaScript Object Notation):是一种轻量级的数据交换格式. 它是基于 ECMAScript 规范的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据. 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言.易于人阅读和编写,同时也易于计算机解析和生成,并有效的 提升网络传输效率. Json易于机器

  • Golang实现Json分级解析及数字解析实践详解

    目录 一.背景介绍 二.解决方案 (1)将Json直接解析为map (2)解析部分json struct的方法 (json.RawMessage的用法) (3) json.Number类型的使用 一.背景介绍 在go语言开发过程中经常需要将json字符串解析为struct,通常我们都是根据json的具体层级关系定义对应的struct,然后通过json.Unmarshal()命令实现json到struct对象的转换,然后再根据具体逻辑处理相应的数据. 你是否遇到过在无法准确确定json层级关系的情

随机推荐