跳到主要内容

Go语言之json使用

Go 语言中,官方提供了一个专门的包 encoding/json

json 反序列(解析)

假设有这样一个 json 数据,我要将其解析为 go 结构体

{ "name": "wenhao", "age": 20 }

首先需要定义结构体,通常可以使用 json 转 go 结构体的在线工具,如下图

json-to-go.png (1582×248) (wenhao.cn)

其转化代码如下

import (
"encoding/json"
"fmt"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
var p Person

jsonString := `{"name": "wenhao", "age" : 20}`

err := json.Unmarshal([]byte(jsonString), &p)

if err == nil {
fmt.Println(p.Name)
fmt.Println(p.Age)
} else {
fmt.Println(err)
}
}

主要步骤

  • 定义结构体(一般结构体的每个字段第一个字母大写),并新建结构体变量
  • 核心代码err := json.Unmarshal([]byte(jsonString), &p),如果解析有效那么 err 为 nil,并将 p 赋值其 json 数据。
  • 访问结构体 p 的成员

解析 json 数组

如果要解析 json 数组,其步骤同上,演示代码如下

import (
"encoding/json"
"fmt"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
var persons []Person

jsonString := `[{"name": "wenhao", "age" : 20},{"name": "文浩", "age" : 22}]`

err := json.Unmarshal([]byte(jsonString), &persons)

if err == nil {
for _, p := range persons {
fmt.Print("\t\n", p.Name)
fmt.Print("\t", p.Age)
}
} else {
fmt.Println(err)
}
}

哪怕对于再复杂的 json 数据,都要先将 json 转为 go 结构体,然后执行json.Unmarshal

自定义属性名称的映射

假设 json 的 key 值存在空格(一般情况下不可能,以我多年读写 json 的经历都没看到过),由于 go 中无法将空格做为变量标识符(貌似没有语言支持空格当标识符),而 json-to-go 工具会将空格清楚,并将下个单词首字母大写。

{
"user name": "wenhao"
}
type AutoGenerated struct {
UserName string `json:"user name"`
}

当然,这里的 UserName 可以随便命名 go 变量规范的名字,只要json:"user name"不变,结构体映射的还是user name属性。

map[string]interface{}

对于未知属性,并且不想定义结构体的话,go 有个很典型的解决方法是采用 map[string]interface{} ,在一些情况(仅一层 json 数据)下确实会比较方便,但也会存在 json 属性不存在导致读取错误的情况。

import (
"encoding/json"
"fmt"
)

func main() {
jsonString := `
{
"code": 200,
"data": {
"username": "wenhao",
"age": 20
}
}`

var result map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &result)
if err == nil {
fmt.Println(result["code"])
username := result["data"].(map[string]interface{})["username"]
fmt.Println(username)
} else {
fmt.Println(err)
}
}
提示

在 Go1.18 更新中,any 作为一个新的关键字出现。any 本质上是 interface{} 的别名

type any = interface{}

map[string]interface{} 也可写为 map[string]any

json 序列化

既然前面是 json 转 go 结构体,那这肯定是将 go 结构体转为 json。

同样的,既然想要生成 json 数据,那么也可将要生成的 json 数据放到 json-to-go 里转为 go 结构体。如

{
"id": 1,
"username": "wenhao",
"hobby": ["敲代码", "吃饭", "睡觉"]
}
type AutoGenerated struct {
ID int `json:"id"`
Username string `json:"username"`
Hobby []string `json:"hobby"`
}

对应的 go 代码如下

import (
"encoding/json"
"fmt"
)

type User struct {
ID int `json:"id"`
Username string `json:"username"`
Hobby []string `json:"hobby"`
}

func main() {
user := &User{
ID: 1,
Username: "wenhao",
Hobby: []string{"敲代码", "吃饭", "睡觉"},
}

result, _ := json.Marshal(user)
fmt.Println(string(result))
}

主要步骤

  • 定义结构体,并新建结构体指针,同时赋值
  • 调用result, _ := json.Marshal(user),此时的 result 为字节数组。
  • string(result) 将其转为 json 字符串

json 序列化没什么过多补充的,上面这里例子实际中已经足够使用了。

相关 json 库

tidwall/gjson: Get JSON values quickly - JSON parser for Go (github.com) 强烈推荐(还支持 jsonpath 语法)

总结

和 js 相比,go 对 json 的操作可以说是比较繁琐,但这并不是只有 go 这样,静态类型的语言都相对繁琐。在 go 是定义结构体,而在 java 则是定义类,每种语言都有数据格式的规范。不过 json 序列化与反序列化还是 js 最为方便,毕竟 json(JavaScript Object Notation)本身就作为 js 的对象数据表达形式。