Go 处理 json
# Json
JSON(JavaScript Object Notation,JavaScript对象表示法)是一种轻量级的数据交换格式,易于人和机器读取和编写。它最初源于JavaScript,但现在已经成为一种独立于语言的格式,被广泛应用于各种编程语言和系统中。
# 特点
简单易读:JSON使用键值对的形式,语法简洁,类似于许多编程语言中的对象或字典。
轻量:相比XML等格式,JSON更紧凑,数据传输效率更高。
跨语言支持:JSON是文本格式,几乎所有编程语言(如Python、Java、C++等)都有解析和生成JSON的工具。
用途广泛:常用于Web开发中的数据传输(如API响应)、配置文件存储等。
# 基本结构
JSON主要由以下几种数据类型组成:
对象(Object):
用大括号 {} 包裹,包含键值对。
键是字符串,值可以是字符串、数字、布尔值、数组、对象或null。
示例:
{ "name": "Alice", "age": 25, "isStudent": false }
1
2
3
4
5
数组(Array):
用方括号 [] 包裹,可以包含多个值。
示例:
["apple", "banana", "orange"]
1
基本数据类型:
字符串
(String):用双引号包裹,例如 "hello"。数字
(Number):可以是整数或浮点数,例如 42 或 3.14。布尔值
(Boolean):true 或 false。空值
(Null):null,表示无值。
示例: 一个更复杂一些的JSON示例:
{
"person": {
"name": "Bob",
"age": 30,
"hobbies": ["reading", "gaming"],
"address": {
"street": "123 Main St",
"city": "Shanghai"
},
"active": true,
"score": null
}
2
3
4
5
6
7
8
9
10
11
12
# 优缺点
优点:
- 易于解析和生成。
- 结构化数据表达能力强。
- 广泛支持,适合跨平台使用。
缺点:
- 不支持注释(官方标准中),不便于添加说明。
- 数据类型较少(例如没有日期类型)。
# 基本的序列化和反序列化
在Go语言(Golang)中,操作JSON非常简单且高效,标准库 encoding/json 提供了强大的工具来编码(marshal)和解码(unmarshal)JSON数据。
# 基本概念
- Marshal:将Go数据结构(如结构体、切片、映射等)转换为JSON字符串。
- Unmarshal:将JSON字符串解析为Go数据结构。
- Go通过结构体标签(struct tags)来控制JSON字段的名称和行为。
# 常用函数
- json.Marshal(v interface{}) ([]byte, error):将数据编码为JSON字节切片。
- json.Unmarshal(data []byte, v interface{}) error:将JSON字节切片解码到Go变量中。
- json.NewEncoder(w io.Writer) 和 json.NewDecoder(r io.Reader):用于流式编码和解码(如文件或网络数据)。
# 简单示例
# 序列化:
func Marshal(v interface{}) ([]byte, error)
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"` // 字段名在JSON中为 "name"
Age int `json:"age"` // 字段名在JSON中为 "age"
Email string `json:"email,omitempty"` // omitempty 表示如果为空则忽略该字段
Private string `json:"-"` // "-" 表示忽略该字段
}
func main() {
p := Person{
Name: "Alice",
Age: 25,
Email: "", // 空值,输出时忽略
Private: "secret", // 被忽略
}
// 转换为JSON
data, err := json.Marshal(p)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(data)) // 输出: {"name":"Alice","age":25}
}
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
# 反序列化:
func Unmarshal(data []byte, v interface{}) error
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
func main() {
// JSON字符串
jsonStr := `{"name":"Bob","age":30,"email":"bob@example.com"}`
// 定义接收数据的变量
var p Person
// 解析JSON
err := json.Unmarshal([]byte(jsonStr), &p)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Name: %s, Age: %d, Email: %s\n", p.Name, p.Age, p.Email)
// 输出: Name: Bob, Age: 30, Email: bob@example.com
}
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
# 结构体 tag 介绍
Tag
是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag
在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。
# 流式处理 JSON
对于大文件或网络传输,可以使用 json.Encoder 和 json.Decoder。
序列化:
package main
import (
"encoding/json"
"os"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
p := Person{Name: "David", Age: 40}
file, _ := os.Create("person.json")
defer file.Close()
encoder := json.NewEncoder(file)
encoder.Encode(p) // 写入JSON到文件
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
反序列化:
package main
import (
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
file, _ := os.Open("person.json")
defer file.Close()
var p Person
decoder := json.NewDecoder(file)
decoder.Decode(&p)
fmt.Printf("Name: %s, Age: %d\n", p.Name, p.Age)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 第三方json库
# Sonic
Sonic(github.com/bytedance/sonic)是一个由字节跳动(ByteDance)开发的高性能JSON序列化和反序列化库。它通过使用JIT(即时编译)和SIMD(单指令多数据)技术显著提升了JSON处理的效率,特别适合需要处理大量JSON数据的场景,比如微服务或高吞吐量的API。
# Sonic的特点
- 高性能:相比标准库 encoding/json,Sonic在编码和解码速度上快得多,尤其是在处理大JSON数据时。
- 兼容性:基本兼容标准库的API,可以作为drop-in replacement(直接替换),只需更改导入路径。
- 技术亮点
- JIT:动态生成针对特定数据结构的优化代码,减少反射和函数调用开销。
- SIMD:利用CPU的并行指令集加速字符串处理和数值转换。
- 适用场景:Sonic在各种JSON大小(小到几字节,大到几MB)和使用场景(通用解析、绑定解析、并行处理)中都表现出色。
package main
import (
"fmt"
"github.com/bytedance/sonic"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
p := Person{Name: "Alice", Age: 25}
// 编码
data, err := sonic.Marshal(p)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(data)) // {"name":"Alice","age":25}
// 解码
var p2 Person
err = sonic.Unmarshal(data, &p2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(p2.Name, p2.Age) // Alice 25
}
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
# 对比标准库
goos: linux
goarch: amd64
pkg: test-Sonic
cpu: AMD Ryzen 7 PRO 6850H with Radeon Graphics
BenchmarkJSONMarshal-16 1000000 1019 ns/op 496 B/op 9 allocs/op
BenchmarkSonicMarshal-16 2376646 489.0 ns/op 394 B/op 4 allocs/op
BenchmarkJSONUnmarshal-16 460765 2526 ns/op 424 B/op 22 allocs/op
BenchmarkSonicUnmarshal-16 2747349 437.9 ns/op 229 B/op 2 allocs/op
PASS
ok test-Sonic 5.558s
2
3
4
5
6
7
8
9
10
# 注意事项
- 环境支持:Sonic依赖特定的CPU架构(如x86_64)和操作系统(Linux、macOS、Windows),在不支持的环境中会回退到标准库 encoding/json。
- 内存使用:Sonic在解析时可能引用原始JSON缓冲区以提升性能,这可能增加内存占用(通常是解码对象的20%-80%)。
- 预编译(Pretouch):对于大结构体,建议使用 sonic.Pretouch 预编译以避免首次运行时的JIT延迟。
# gjson
gjson 是一个轻量级、高性能的Go语言JSON解析库,由 github.com/tidwall/gjson
提供。它专注于快速读取JSON数据,而不需要将整个JSON反序列化到结构体中。相比标准库 encoding/json,gjson 的设计目标是简单、快速和灵活,特别适合只需要从JSON中提取部分数据而非完整解析的场景。
# gjson 的特点
- 无需结构体:
- 不需要预定义Go结构体,直接通过路径(path)访问JSON中的字段。
- 适合处理动态或未知结构的JSON。
- 高性能:
- 通过避免反射和完整反序列化,gjson 的解析速度通常比标准库快得多。
- 使用零拷贝(zero-copy)技术,直接操作原始JSON字节切片,减少内存分配。
- 简单易用:
- 提供直观的路径查询语法,类似于JavaScript中的对象访问。
- 支持嵌套字段、数组索引和通配符。
- 只读:
- gjson 专注于读取JSON,不支持序列化(Marshal)或修改JSON(如果需要修改,可以搭配 sjson 使用)。
- 轻量:
- 代码库小巧,依赖少,易于集成。
# 基本用法
gjson 的核心函数是 Get,它接受JSON字符串(或字节切片)和路径,返回一个 Result 类型的值。
示例1:简单字段访问:
package main
import (
"fmt"
"github.com/tidwall/gjson"
)
func main() {
jsonStr := `{"name": "Alice", "age": 25, "city": "Shanghai"}`
// 获取字段
name := gjson.Get(jsonStr, "name").String()
age := gjson.Get(jsonStr, "age").Int()
fmt.Println("Name:", name) // Name: Alice
fmt.Println("Age:", age) // Age: 25
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
示例2:嵌套字段和数组
func main() {
jsonStr := `{
"person": {
"name": "Bob",
"hobbies": ["reading", "gaming"]
}
}`
// 嵌套字段
name := gjson.Get(jsonStr, "person.name").String()
// 数组索引
hobby1 := gjson.Get(jsonStr, "person.hobbies.0").String()
fmt.Println("Name:", name) // Name: Bob
fmt.Println("Hobby 1:", hobby1) // Hobby 1: reading
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
路径语法
gjson 使用点号(.
)和数组索引访问JSON字段,支持以下特性:
- 基本字段:
field
(如 "name
")。 - 嵌套字段:
parent.child
(如 "person.name
")。 - 数组索引:
array.index
(如 "hobbies.0
")。 - 通配符:
#
:表示数组长度或匹配所有元素。*
:匹配所有字段。
- 修饰符:在路径后添加
|
和修饰符(如#(condition)
)进行高级查询。
示例3:通配符和修饰符
func main() {
jsonStr := `[
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]`
// 获取数组长度
count := gjson.Get(jsonStr, "#").Int()
fmt.Println("Count:", count) // Count: 2
// 获取所有name字段
names := gjson.Get(jsonStr, "*.name")
names.ForEach(func(key, value gjson.Result) bool {
fmt.Println("Name:", value.String()) // Name: Alice, Name: Bob
return true
})
// 查找id为2的name
name := gjson.Get(jsonStr, "#(id==2)#.name").String()
fmt.Println("Name with id 2:", name) // Name with id 2: Bob
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
返回值类型(Result)
gjson.Get 返回一个 Result 结构体,支持多种类型转换:
- .String():转换为字符串。
- .Int():转换为int64。
- .Float():转换为float64。
- .Bool():转换为布尔值。
- .Array():转换为 []gjson.Result。
- .Map():转换为 map[string]gjson.Result。
- .Raw:获取原始JSON字符串。
如果路径不存在,Result.Exists() 返回 false,可以用来检查有效性。
jsonStr := `{"name": "Charlie"}`
result := gjson.Get(jsonStr, "age")
if !result.Exists() {
fmt.Println("Age does not exist") // Age does not exist
}
2
3
4
5
# 小结
在单键查找场景中, gjson (opens new window)具有巨大的优势。这是因为它的查找是通过惰性加载机制实现的,巧妙地跳过了传递的值,并有效的减少了许多不必要的解析。实际应用证明,在产品中充分利用这个特性确实能带来收益。但是,当涉及到多键查找时,Gjson甚至比标准库还要差,这是其跳过机制的副作用——搜索相同路径会导致重复解析(跳过解析也是一种轻量的解析)因此,根据实际情况准确的做出调整是关键问题。
多键查找:
BenchmarkJSONMarshal-16 1280576 941.7 ns/op 496 B/op 9 allocs/op
BenchmarkSonicMarshal-16 2474157 488.0 ns/op 394 B/op 4 allocs/op
BenchmarkJSONUnmarshal-16 466876 2476 ns/op 424 B/op 22 allocs/op
BenchmarkSonicUnmarshal-16 2798126 427.5 ns/op 228 B/op 2 allocs/op
BenchmarkGJSONUnmarshal-16 1263700 956.3 ns/op 1368 B/op 8 allocs/op
2
3
4
5
单键查找:
BenchmarkJSONMarshal-16 1273489 941.5 ns/op 496 B/op 9 allocs/op
BenchmarkSonicMarshal-16 2430908 491.3 ns/op 394 B/op 4 allocs/op
BenchmarkJSONUnmarshal-16 480385 2474 ns/op 424 B/op 22 allocs/op
BenchmarkSonicUnmarshal-16 2791281 429.5 ns/op 228 B/op 2 allocs/op
BenchmarkGJSONUnmarshal-16 15421743 76.26 ns/op 8 B/op 1 allocs/op
2
3
4
5