golang Json Tag 详细用法

2021/06/06 golang json tag 共 4144 字,约 12 分钟

Go 中使用 json 很多,玩法也很多,整理了一下 go 中关于 json 的 tag 使用 包含了一些遇到的问题与解决方案

A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. The tags are made visible through a reflection interface but are otherwise ignored.

官方的解释是这个标签信息可以通过反射获取,自定义一些规则,比如常见的 db json Tag 是一个以反引号 `包围, 空格分隔的 k:v 对,常用于数据解析,关系映射等

Go 中数据类型与 Json 支持的类型对应关系

bool                    ==> JSON booleans
float64                 ==> JSON numbers
string                  ==> JSON strings
[]interface{}           ==> JSON arrays
map[string]interface{}  ==> JSON objects
nil                     ==> JSON null
package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type User struct {
	Age      uint8     `json:"age"`
	Name     string    `json:"name"`
	Address  string    `json:"address,omitempty"`
	Phone    []string  `json:"phone"`
	Skill    []string  `json:"skill"`
	Birthday time.Time `json:"birthday,omitempty"`
	Password string    `json:"-"`
}

func main() {
    l,_ := time.LoadLocation("Asia/Shanghai")
	birthday := time.Date(2000, 1, 1, 10, 8, 0, 0, l)
	someOne := User{
		Age:      20,
		Name:     "老王",
		Address:  "",
		Skill:    []string{},
		Phone:    []string{"18888888888","15555555555"},
		Birthday: birthday,
		Password: "admin123",
	}
	b, err := json.Marshal(someOne)
	if err != nil {
		fmt.Printf("json.Marshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("%s\n", b)
}

指定 json 字段名

User 结构体中,json 标签指定了它的输出字段名,执行将输出

{
    "age":20,
    "name":"老王",
    "phone":[
        "18888888888",
        "15555555555"
    ],
    "skill":null,
    "birthday":"2000-01-01T10:08:00+08:00"
}

输出了 null

切片、map、等零值为 nil,对应 json 的值为 null
如果要输出[] 则要赋值为空切片

// someOne 赋值为
someOne := User{
		Age:      20,
		Name:     "老王",
		Address:  "",
		Skill:    []string{},
		Phone:    []string{"18888888888","15555555555"},
		Birthday: birthday,
		Password: "admin123",
	}
// 对应的输出
{
    "age":20,
    "name":"老王",
    "skill":[

    ],
    "phone":[
        "18888888888",
        "15555555555"
    ],
    "birthday":"2000-01-01T10:08:00+08:00"
}

要注意的是空 map 对应的 json 输出为 {} 而非 []

忽略零值字段

通过设置tag添加omitempty,当字段值为其类型对应的零值时,可达到忽略零值字段的目的。
在输出结果中Address被设置为空字符串,也未被输出,因为string类型的零值即为""

忽略指定字段

User结构体中Passwordtag被指定为-所以,即便被设置了具体的值,在 json 中也未被输出

结构体嵌套输出

比如将用户的技能和新增的个人主页地址,并将一些展示信息独立为Profile结构

type Profile struct {
    Phone    []string  `json:"phone,omitempty"`
    Skill    []string  `json:"skill,omitempty"`
    Birthday time.Time `json:"birthday,omitempty"`
    HomePage string    `json:"url"`
    Address  string    `json:"address,omitempty"`
}

type User struct {
    Profile

	Age      uint8     `json:"age"`
	Name     string    `json:"name"`
	Password string    `json:"-"`
}

// someOne 赋值为
someOne := User{
    Profile: Profile{
        Skill: []string{"吃","喝","玩","乐"},
        Birthday: birthday,
    },

    Age:      20,
    Name:     "老王",
    Password: "admin123",
}
// 对应输出为
{
    "skill":[
        "吃",
        "喝",
        "玩",
        "乐"
    ],
    "birthday":"2000-01-01T10:08:00+08:00",
    "url":"",
    "age":20,
    "name":"老王"
}

由于是匿名嵌套,所以 Profile 中的字段与 User 同级输出

层级输出

type User struct {
    Profile `json:"profile"`

	Age      uint8     `json:"age"`
	Name     string    `json:"name"`
	Password string    `json:"-"`
}

输出为

{
    "profile":{
        "skill":[
            "吃",
            "喝",
            "玩",
            "乐"
        ],
        "birthday":"2000-01-01T10:08:00+08:00",
        "url":""
    },
    "age":20,
    "name":"老王"
}

忽略嵌套结构体

仅设置omitempty是无法屏蔽嵌套字段的输出的

type User struct {
    Profile `json:"profile,omitempty"`

	Age      uint8     `json:"age"`
	Name     string    `json:"name"`
	Password string    `json:"-"`
}

输出为

{
    "profile":{
        "birthday":"0001-01-01T00:00:00Z",
        "url":""
    },
    "age":20,
    "name":"老王"
}

解决方案为使用嵌套结构体指针

type User struct {
    *Profile `json:"profile,omitempty"`

	Age      uint8     `json:"age"`
	Name     string    `json:"name"`
	Password string    `json:"-"`
}

输出为

{"age":20,"name":"老王"}

忽略字段输出(不修改原结构体)

比如要忽略Name输出,利用的是定义相同 json tag 来完成 可使用任意类型,空 struct 有利于节省内存及统一

type Omit *struct{}
type JsonUser struct {
	*User
	Name Omit `json:"name,omitempty"`
}

临时使用的话,一个匿名结构体也可以

json.RawMessage 延迟解析

可通过一个结构体灵活匹配多种类型定义

type MultiType struct {
	Type string `json:"type"`
	Data json.RawMessage `json:"data"`
}

可以通过先匹配 type 再对应解析数据,如

{
	"type":"array",
	"data": [1,2,3,4]
}
{
	"type":"string",
	"data": "any"
}

json 传递数字

json 的数字,对应的是 go 的 float64 类型 这就意味着只有显示的指定字段类型,go 才会将数字转换,所以如果超出 float64 范围的数字,就需要特殊处理
另外,如果字段定义为数字类型,int int8 等,但入参为字符串”42”这种,会解析错误
以上问题可通过使用json.Number类型解决,见参考链接

type User struct {
	Age      json.Number     `json:"age"`
	Name     string          `json:"name"`
	Password string          `json:"-"`
}

如果确定入参类型只为字符串类型数字,也可通过 tag 中指定类型来解决

通过这种定义,输出的数字也将是字符串类型

type User struct {
	Age      uint8     `json:"age,string"`
	Name     string    `json:"name"`
	Password string    `json:"-"`
}

自定义规则

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}
type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

通过实现以上两个接口,达到自定义 json 格式或解析的目的
比如修改 time 的输出格式以及兼容各种格式的时间字符串解析,时间戳的转换等
扩展类型的方式或者内嵌方式

type CustomTime struct {
    time.Time
}

// MarshalJSON 输出常用的时间格式
func (t CustomTime) MarshalJSON() ([]byte, error) {
    tune := t.Format(`"2006-01-02 15:04:05"`)
    return []byte(tune), nil
}

文档信息

Search

    Table of Contents