go专题-JSON操作

2022/6/11 go

🌙 go操作json

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。通过从存储区中读取对象的状态,重新创建该对象,则为反序列化。

简单地说把某种数据结构转为指定数据格式为“序列化”或“编码”(传输之前);而把“指定数据格式”转为某种数据结构则为“反序列化”或“解码”(传输之后)。

在Go语言中,encoding/json标准包处理JSON数据的序列化与反序列化问题。

🌙 1.json序列化

  • json.Marshal源码:
func Marshal(v interface{}) ([]byte, error) {
	e := newEncodeState()
        // 默认编码
	err := e.marshal(v, encOpts{escapeHTML: true})
	if err != nil {
		return nil, err
	}
	buf := append([]byte(nil), e.Bytes()...)

	encodeStatePool.Put(e)

	return buf, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13

从上面的Marshal()函数我们可以看到,数据结构序列化后返回的是字节数组,而字节数组很容易通过网络传输或写入文件存储。

而且在Go中,Marshal()默认是设置escapeHTML = true的,会自动把'<', '>', 以及 '&' 等转化为"\u003c""\u003e"以及 "\u0026"

func testMarshal() {
	h := Human{"JEEK", "男", 18, Lesson{[]string{"Math", "Art"}}}
	data, err := json.Marshal(&h)
	if err != nil {
		fmt.Println("json序列化失败", err)
		return
	}
	fmt.Println("json序列化成功", string(data))
}

// json序列化成功 {"name":"JEEK","gender":"男","age":18,"lessons":["Math","Art"]}
1
2
3
4
5
6
7
8
9
10
11

🌙 2.json反序列化

  • json.Unmarshal源码:
func Unmarshal(data []byte, v interface{}) error {
	// Check for well-formedness.
	// Avoids filling out half a data structure
	// before discovering a JSON syntax error.
	var d decodeState
	err := checkValid(data, &d.scan)
	if err != nil {
		return err
	}

	d.init(data)
	return d.unmarshal(v)
}

func checkValid(data []byte, scan *scanner) error {
	scan.reset()
	for _, c := range data {
		scan.bytes++
		if scan.step(scan, c) == scanError {
			return scan.err
		}
	}
	if scan.eof() == scanError {
		return scan.err
	}
	return 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
26
27

从上面的UnMarshal()函数我们可以看到,反序列化是读取字节数组,进而解析为对应的数据结构。

func testUnmarshal1() {
	jsonStr := `{"name": "JEEK", "age": 18, "gender": "男", "lessons": ["English", "Art"], "room": 205, "test": null, "isAdult": true}`

	var hu Human
	err := json.Unmarshal([]byte(jsonStr), &hu)
	if err != nil {
		fmt.Println("json反序列化失败", err)
		return
	}
	fmt.Println("json反序列化成功", hu)
}

// json反序列化成功 {JEEK 男 18 {[English Art]}}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 反序列化任意JSON数据:

encoding/json 包使用 map[string]interface{}[]interface{}储存任意的 JSON 对象和数组;其可以被反序列化为任何的 JSON blob 存储到接口值中。

直接使用 Unmarshal 把这个数据反序列化,并保存在map[string]interface{} 中,要访问这个数据,我们可以使用类型断言

func testUnmarshal2() {
	jsonStr := `{"name": "JEEK", "age": 18, "gender": "男", "lessons": ["English", "Art"], "room": 205, "test": null, "isAdult": true}`

	var data map[string]interface{}
	if err := json.Unmarshal([]byte(jsonStr), &data); err == nil {
		fmt.Println("map结构")
		fmt.Println(data)
	}
	for k, v := range data {
		switch vv := v.(type) {
		case string:
			fmt.Println(k, "是string", vv)
		case bool:
			fmt.Println(k, "是bool", vv)
		case float64:
			fmt.Println(k, "是float64", vv)
		case nil:
			fmt.Println(k, "是nil", vv)
		case []interface{}:
			fmt.Println(k, "是array:")
			for i, u := range vv {
				fmt.Println(i, u)
			}
		default:
			fmt.Println(k, "未知数据类型")
		}
	}
}
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

结果

map结构
map[age:18 gender:男 isAdult:true lessons:[English Art] name:JEEK room:205 test:<nil>]
name 是string JEEK
age 是float64 18
gender 是string 男
lessons 是array:
0 English
1 Art
room 是float64 205
test 是nil <nil>
isAdult 是bool true

Process finished with the exit code 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14

🌙 3.json与go数据类型

json go
object map[string]interface{}
array []interface{}
string string
boolean bool
number float64
null nill

不是所有的数据都可以编码为 JSON 格式,只有验证通过的数据结构才能被编码:

  • json 对象只支持字符串类型的 key;要编码一个 Go map 类型,map 必须是 map[string]T(T是 json 包中支持的任何类型)
  • channel,复杂类型和函数类型不能被编码
  • 不支持循环数据结构;它将引起序列化进入一个无限循环
  • 指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil)

🌙 4.结构体tag和json

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

  • 结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。

  • 同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

type Person struct {
	// 1.指定json序列化/反序列化时使用小写name
	Name string `json:"name"`

	Age int `json:"age"`

	// 2.指定json序列化/反序列化时忽略此字段
	Height float64 `json:"-"`

	// 3.在tag中添加omitempty忽略空值
	// 注意这里 hobby,omitempty 合起来是json tag值,中间用英文逗号分隔
	Hobby []string `json:"hobby,omitempty"`

	P Profile `json:"p"`
	P1 Profile `json:"p1,omitempty"` // 不会忽略空值
	// 4.忽略嵌套结构体空值字段,必须指针
	P2 *Profile `json:"p2,omitempty"`
}

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


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

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

func testDemo() {
	p := Person{
		Name: "JEEK",
		Age: 18,
		Height: 175.6,
		Hobby: []string{"Soccer", "Music"},
		P: Profile{Website: "blog.zkkysqs.top", Slogan: "stay hungry, stay foolish"},
	}

	data, err := json.Marshal(p)
	if err != nil {
		fmt.Println("json parse err", err)
		return
	}
	fmt.Println(string(data))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

输出:

{
  "name": "JEEK",
  "age": 18,
  "hobby": [
    "Soccer",
    "Music"
  ],
  "p": {
    "site": "blog.zkkysqs.top",
    "slogan": "stay hungry, stay foolish"
  },
  "p1": {
    "site": "",
    "slogan": ""
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16