Go Json

本文最后更新于:2024年3月18日 凌晨

Go Json

基本的序列化

首先我们来看一下 Go 语言中 json.Marshal(序列化)与 json.Unmarshal(反序列化)的基本用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Person struct {
Name string
Age int64
Weight float64
}

func main() {
p1 := Person{
Name: "Test",
Age: 18,
Weight: 71.5,
}
// struct -> json string
b, err := json.Marshal(p1)
if err != nil {
fmt.Printf("json.Marshal failed, err:%v\n", err)
return
}
fmt.Printf("str:%s\n", b)
// json string -> struct
var p2 Person
err = json.Unmarshal(b, &p2)
if err != nil {
fmt.Printf("json.Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf("p2:%#v\n", p2)
}
1
2
str:{"Name":"Test","Age":18,"Weight":71.5}
p2:main.Person{Name:"Test", Age:18, Weight:71.5}

结构体 tag 介绍

Tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag 在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

1
`key1:"value1" key2:"value2"`

结构体 tag 由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对 tag,不同的键值对之间使用空格分隔。

使用 json tag 指定字段名

序列化与反序列化默认情况下使用结构体的字段名,我们可以通过给结构体字段添加 tag 来指定 json 序列化生成的字段名。

1
2
3
4
5
6
// 使用json tag指定序列化与反序列化时的行为。
type Person struct {
Name string `json:"name"` // 指定json序列化/反序列化时使用小写name
Age int64
Weight float64
}

忽略某个字段

如果你想在 json 序列化/反序列化的时候忽略掉结构体中的某个字段,可以按如下方式在 tag 中添加 -

1
2
3
4
5
6
// 使用json tag指定json序列化与反序列化时的行为。
type Person struct {
Name string `json:"name"` // 指定json序列化/反序列化时使用小写name
Age int64
Weight float64 `json:"-"` // 指定json序列化/反序列化时忽略此字段。
}

忽略空值字段

当 struct 中的字段没有值时, json.Marshal() 序列化的时候不会忽略这些字段,而是默认输出字段的类型零值(例如 intfloat 类型零值是 0,string 类型零值是 "",对象类型零值是 nil)。如果想要在序列序列化时忽略这些没有值的字段时,可以在对应字段添加 omitempty tag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Hobby []string `json:"hobby"`
}

func omitemptyDemo() {
u1 := User{
Name: "Test",
}
// struct -> json string
b, err := json.Marshal(u1)
if err != nil {
fmt.Printf("json.Marshal failed, err:%v\n", err)
return
}
fmt.Printf("str:%s\n", b)
}
1
str:{"name":"Test","email":"","hobby":null}

如果想要在最终的序列化结果中去掉空值字段,可以像下面这样定义结构体:

1
2
3
4
5
6
7
// 在tag中添加omitempty忽略空值。
// 注意这里 hobby,omitempty 合起来是json tag值,中间用英文逗号分隔。
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
}

此时,再执行上述的 omitemptyDemo,输出结果如下:

1
str:{"name":"Test"} // 序列化结果中没有email和hobby字段。

忽略嵌套结构体空值字段

首先来看几种结构体嵌套的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
Profile
}

type Profile struct {
Website string `json:"site"`
Slogan string `json:"slogan"`
}

func nestedStructDemo() {
u1 := User{
Name: "Test",
Hobby: []string{"足球", "双色球"},
}
b, err := json.Marshal(u1)
if err != nil {
fmt.Printf("json.Marshal failed, err:%v\n", err)
return
}
fmt.Printf("str:%s\n", b)
}

匿名嵌套 Profile 时序列化后的 json 串为单层的:

1
str:{"name":"Test","hobby":["足球","双色球"],"site":"","slogan":""}

想要变成嵌套的 json 串,需要改为具名嵌套或定义字段 tag:

1
2
3
4
5
6
7
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
Profile `json:"profile"`
}
// str:{"name":"Test","hobby":["足球","双色球"],"profile":{"site":"","slogan":""}}

想要在嵌套的结构体为空值时,忽略该字段,仅添加 omitempty 是不够的:

1
2
3
4
5
6
7
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
Profile `json:"profile,omitempty"`
}
// str:{"name":"Test","hobby":["足球","双色球"],"profile":{"site":"","slogan":""}}

还需要使用嵌套的结构体指针:

1
2
3
4
5
6
7
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
*Profile `json:"profile,omitempty"`
}
// str:{"name":"Test","hobby":["足球","双色球"]}

不修改原结构体忽略空值字段

我们需要 json 序列化 User,但是不想把密码也序列化,又不想修改 User 结构体,这个时候我们就可以使用创建另外一个结构体 PublicUser 匿名嵌套原 User,同时指定 Password 字段为匿名结构体指针类型,并添加 omitempty tag,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type User struct {
Name string `json:"name"`
Password string `json:"password"`
}

type PublicUser struct {
*User // 匿名嵌套。
Password *struct{} `json:"password,omitempty"`
}

func omitPasswordDemo() {
u1 := User{
Name: "Test",
Password: "123456",
}
b, err := json.Marshal(PublicUser{User: &u1})
if err != nil {
fmt.Printf("json.Marshal u1 failed, err:%v\n", err)
return
}
fmt.Printf("str:%s\n", b) // str:{"name":"Test"}
}

优雅处理字符串格式的数字

有时候,前端在传递来的 json 数据中可能会使用字符串类型的数字,这个时候可以在结构体 tag 中添加 string 来告诉 json 包从字符串中解析相应字段的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Card struct {
ID int64 `json:"id,string"` // 添加string tag
Score float64 `json:"score,string"` // 添加string tag
}

func intAndStringDemo() {
jsonStr1 := `{"id": "1234567","score": "88.50"}`
var c1 Card
if err := json.Unmarshal([]byte(jsonStr1), &c1); err != nil {
fmt.Printf("json.Unmarsha jsonStr1 failed, err:%v\n", err)
return
}
fmt.Printf("c1:%#v\n", c1) // c1:main.Card{ID:1234567, Score:88.5}
}

整数变浮点数

因为在 JSON 协议中是没有整型和浮点型之分的,它们统称为 number。json 字符串中的数字经过 Go 语言中的 json 包反序列化之后都会成为 float64 类型。

通常这并不会有什么问题,但是在某些特殊场景下就会产生意想不到的结果。比如,将 JSON 格式的数据反序列化为 map[string]interface{} 时,数字都变成科学计数法表示的浮点数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// useNumberDemo 使用json.UseNumber
// 解决将JSON数据反序列化成map[string]interface{}时。
// 数字变为科学计数法表示的浮点数问题。
func useNumberDemo(){
type student struct {
ID int64 `json:"id"`
Name string `json:"q1mi"`
}
s := student{ID: 123456789,Name: "q1mi"}
b, _ := json.Marshal(s)
var m map[string]interface{}
// decode
json.Unmarshal(b, &m)
fmt.Printf("id:%#v\n", m["id"]) // 1.23456789e+08
fmt.Printf("id type:%T\n", m["id"]) //float64

// use Number decode
decoder := json.NewDecoder(bytes.NewReader(b))
decoder.UseNumber ()
decoder.Decode (&m)
fmt.Printf ("id:% #v \n", m["id"]) // "123456789"
fmt.Printf ("id type:%T\n", m["id"]) // json. Number
}

这种问题通常出现在将 JSON 格式数据反序列化为map[string]interface{}时,再来一个示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func jsonDemo () {
// map[string]interface{} -> json string
var m = make (map[string]interface{}, 1)
m["count"] = 1 // int
b, err := json.Marshal (m)
if err != nil {
fmt.Printf ("marshal failed, err:%v\n", err)
}
fmt.Printf ("str:% #v \n", string (b))
// json string -> map[string]interface{}
var m 2 map[string]interface{}
err = json.Unmarshal (b, &m 2)
if err != nil {
fmt.Printf ("unmarshal failed, err:%v\n", err)
return
}
fmt.Printf ("value:%v\n", m 2["count"]) // 1
fmt.Printf ("type:%T\n", m 2["count"]) // float 64
}

这种场景下如果想更合理的处理数字就需要使用decoder去反序列化,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func decoderDemo () {
// map[string]interface{} -> json string
var m = make (map[string]interface{}, 1)
m["count"] = 1 // int
b, err := json.Marshal (m)
if err != nil {
fmt.Printf ("marshal failed, err:%v\n", err)
}
fmt.Printf ("str:% #v \n", string (b))
// json string -> map[string]interface{}
var m 2 map[string]interface{}
// 使用 decoder 方式反序列化,指定使用 number 类型。
decoder := json.NewDecoder (bytes.NewReader (b))
decoder.UseNumber ()
err = decoder.Decode (&m 2)
if err != nil {
fmt.Printf ("unmarshal failed, err:%v\n", err)
return
}
fmt.Printf ("value:%v\n", m 2["count"]) // 1
fmt.Printf ("type:%T\n", m 2["count"]) // json. Number
// 将 m 2["count"]转为 json. Number 之后调用 Int 64 ()方法获得 int 64 类型的值。
count, err := m 2["count"]. (json. Number). Int 64 ()
if err != nil {
fmt.Printf ("parse to int 64 failed, err:%v\n", err)
return
}
fmt.Printf ("type:%T\n", int (count)) // int
}

json. Number的源码定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// A Number represents a JSON number literal.
type Number string

// String returns the literal text of the number.
func (n Number) String () string { return string (n) }

// Float 64 returns the number as a float 64.
func (n Number) Float 64 () (float 64, error) {
return strconv.ParseFloat (string (n), 64)
}

// Int 64 returns the number as an int 64.
func (n Number) Int 64 () (int 64, error) {
return strconv.ParseInt (string (n), 10, 64)
}

我们在处理 number 类型的 json 字段时需要先得到json. Number类型,然后根据该字段的实际类型调用Float 64 ()Int 64 ()

自定义解析时间字段

Go 语言内置的 json 包使用 RFC 3339 标准中定义的时间格式,对我们序列化时间字段的时候有很多限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Post struct {
CreateTime time. Time `json: "create_time"`
}

func timeFieldDemo () {
p 1 := Post{CreateTime: time.Now ()}
b, err := json.Marshal (p 1)
if err != nil {
fmt.Printf ("json. Marshal p 1 failed, err:%v\n", err)
return
}
fmt.Printf ("str:%s\n", b)
jsonStr := `{"create_time": "2020-04-05 12:25:42"}`
var p 2 Post
if err := json.Unmarshal ([]byte (jsonStr), &p 2); err != nil {
fmt.Printf ("json. Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf ("p 2:% #v \n", p 2)
}

上面的代码输出结果如下:

1
2
str:{"create_time": "2020-04-05 T 12:28:06.799214+ 08:00"}
json. Unmarshal failed, err: parsing time ""2020-04-05 12:25:42"" as ""2006-01-02 T 15:04:05Z 07:00"": cannot parse " 12:25:42"" as "T"

也就是内置的 json 包不识别我们常用的字符串时间格式,如2020-04-05 12:25:42

不过我们通过实现 json. Marshaler/json. Unmarshaler 接口实现自定义的事件格式解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
type CustomTime struct {
time. Time
}

const ctLayout = "2006-01-02 15:04:05"

var nilTime = (time. Time{}). UnixNano ()

func (ct *CustomTime) UnmarshalJSON (b []byte) (err error) {
s := strings.Trim (string (b), "\"")
if s == "null" {
ct. Time = time. Time{}
return
}
ct. Time, err = time.Parse (ctLayout, s)
return
}

func (ct *CustomTime) MarshalJSON () ([]byte, error) {
if ct.Time.UnixNano () == nilTime {
return []byte ("null"), nil
}
return []byte (fmt.Sprintf ("\"%s\"", ct.Time.Format (ctLayout))), nil
}

func (ct *CustomTime) IsSet () bool {
return ct.UnixNano () != nilTime
}

type Post struct {
CreateTime CustomTime `json: "create_time"`
}

func timeFieldDemo () {
p 1 := Post{CreateTime: CustomTime{time.Now ()}}
b, err := json.Marshal (p 1)
if err != nil {
fmt.Printf ("json. Marshal p 1 failed, err:%v\n", err)
return
}
fmt.Printf ("str:%s\n", b)
jsonStr := `{"create_time": "2020-04-05 12:25:42"}`
var p 2 Post
if err := json.Unmarshal ([]byte (jsonStr), &p 2); err != nil {
fmt.Printf ("json. Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf ("p 2:% #v \n", p 2)
}

自定义 MarshalJSON 和 UnmarshalJSON 方法

上面那种自定义类型的方法稍显啰嗦了一点,下面来看一种相对便捷的方法。

首先你需要知道的是,如果你能够为某个类型实现了MarshalJSON ()([]byte, error)UnmarshalJSON (b []byte) error方法,那么这个类型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)时就会使用你定制的相应方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
type Order struct {
ID int `json: "id"`
Title string `json: "title"`
CreatedTime time. Time `json: "created_time"`
}

const layout = "2006-01-02 15:04:05"

// MarshalJSON 为 Order 类型实现自定义的 MarshalJSON 方法。
func (o *Order) MarshalJSON () ([]byte, error) {
type TempOrder Order // 定义与 Order 字段一致的新类型。
return json.Marshal (struct {
CreatedTime string `json: "created_time"`
*TempOrder // 避免直接嵌套 Order 进入死循环。
}{
CreatedTime: o.CreatedTime.Format (layout),
TempOrder: (*TempOrder)(o),
})
}

// UnmarshalJSON 为 Order 类型实现自定义的 UnmarshalJSON 方法。
func (o *Order) UnmarshalJSON (data []byte) error {
type TempOrder Order // 定义与 Order 字段一致的新类型。
ot := struct {
CreatedTime string `json: "created_time"`
*TempOrder // 避免直接嵌套 Order 进入死循环。
}{
TempOrder: (*TempOrder)(o),
}
if err := json.Unmarshal (data, &ot); err != nil {
return err
}
var err error
o.CreatedTime, err = time.Parse (layout, ot. CreatedTime)
if err != nil {
return err
}
return nil
}

// 自定义序列化方法。
func customMethodDemo () {
o 1 := Order{
ID: 123456,
Title: "《Test 的 Go 学习笔记》",
CreatedTime: time.Now (),
}
// 通过自定义的 MarshalJSON 方法实现 struct -> json string
b, err := json.Marshal (&o 1)
if err != nil {
fmt.Printf ("json. Marshal o 1 failed, err:%v\n", err)
return
}
fmt.Printf ("str:%s\n", b)
// 通过自定义的 UnmarshalJSON 方法实现 json string -> struct
jsonStr := `{"created_time": "2020-04-05 10:18:20","id": 123456,"title": "《Test 的 Go 学习笔记》"}`
var o 2 Order
if err := json.Unmarshal ([]byte (jsonStr), &o 2); err != nil {
fmt.Printf ("json. Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf ("o 2:% #v \n", o 2)
}
1
2
str:{"created_time": "2020-04-05 10:32:20","id": 123456,"title": "《Test 的 Go 学习笔记》"}
o 2: main. Order{ID: 123456, Title: "《Test 的 Go 学习笔记》", CreatedTime: time. Time{wall: 0 x 0, ext: 63721678700, loc: (*time. Location)(nil)}}

使用匿名结构体添加字段

使用内嵌结构体能够扩展结构体的字段,但有时候我们没有必要单独定义新的结构体,可以使用匿名结构体简化操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type UserInfo struct {
ID int `json: "id"`
Name string `json: "name"`
}

func anonymousStructDemo () {
u 1 := UserInfo{
ID: 123456,
Name: "Test",
}
// 使用匿名结构体内嵌 User 并添加额外字段 Token
b, err := json.Marshal (struct {
*UserInfo
Token string `json: "token"`
}{
&u 1,
"91 je 3 a 4 s 72 d 1 da 96 h",
})
if err != nil {
fmt.Printf ("json. Marsha failed, err:%v\n", err)
return
}
fmt.Printf ("str:%s\n", b)
// str:{"id": 123456,"name": "Test","token": "91 je 3 a 4 s 72 d 1 da 96 h"}
}

使用匿名结构体组合多个结构体

同理,也可以使用匿名结构体来组合多个结构体来序列化与反序列化数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
type Comment struct {
Content string
}

type Image struct {
Title string `json: "title"`
URL string `json: "url"`
}

func anonymousStructDemo 2 () {
c 1 := Comment{
Content: "TestMsg",
}
i 1 := Image{
Title: "赞赏码",
URL: "https://www.liwenzhou.com/images/zanshang_qr.jpg",
}
// struct -> json string
b, err := json.Marshal (struct {
*Comment
*Image
}{&c 1, &i 1})
if err != nil {
fmt.Printf ("json. Marshal failed, err:%v\n", err)
return
}
fmt.Printf ("str:%s\n", b)
// json string -> struct
jsonStr := `{"Content": "TestMsg","title": "赞赏码","url": "https://www.liwenzhou.com/images/zanshang_qr.jpg"}`
var (
c 2 Comment
i 2 Image
)
if err := json.Unmarshal ([]byte (jsonStr), &struct {
*Comment
*Image
}{&c 2, &i 2}); err != nil {
fmt.Printf ("json. Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf ("c 2:% #v i 2:% #v \n", c 2, i2)
}
1
2
str:{"Content": "TestMsg","title": "赞赏码","url": "https://www.liwenzhou.com/images/zanshang_qr.jpg"}
c 2: main. Comment{Content: "TestMsg"} i 2: main. Image{Title: "赞赏码", URL: "https://www.liwenzhou.com/images/zanshang_qr.jpg"}

处理不确定层级的 json

如果 json 串没有固定的格式导致不好定义与其相对应的结构体时,我们可以使用json. RawMessage原始字节数据保存下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type sendMsg struct {
User string `json: "user"`
Msg string `json: "msg"`
}

func rawMessageDemo () {
jsonStr := `{"sendMsg":{"user": "q 1 mi","msg": "TestMsg"},"say": "Hello"}`
// 定义一个 map,value 类型为 json. RawMessage,方便后续更灵活地处理。
var data map[string]json. RawMessage
if err := json.Unmarshal ([]byte (jsonStr), &data); err != nil {
fmt.Printf ("json. Unmarshal jsonStr failed, err:%v\n", err)
return
}
var msg sendMsg
if err := json.Unmarshal (data["sendMsg"], &msg); err != nil {
fmt.Printf ("json. Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf ("msg:% #v \n", msg)
// msg: main. sendMsg{User: "q 1 mi", Msg: "TestMsg"}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!