在go语言中,处理json数据通常涉及编码(将go结构体转换为json字符串)和解码(将json字符串转换为go结构体)。go标准库中的encoding/json
包提供了这些功能。第三方插件可以使用"github.com/goccy/go-json"也有同样的功能
marshal
函数将会递归遍历整个对象,依次按成员类型对这个对象进行编码,类型转换规则如下:
bool
类型 转换为json
的boolean
整数,浮点数等数值类型 转换为
json
的number
string
转换为json
的字符串(带""引号)struct
转换为json
的object
,再根据各个成员的类型递归打包数组或切片 转换为
json
的array
[]byte
会先进行base64
编码然后转换为json
字符串map
转换为json
的object
,key
必须是string
interface{}
按照内部的实际类型进行转换nil
转为json
的null
channel
,func
等类型 会返回unsupportedtypeerror
1、使用标准库中的encoding/json包
字符串输出&格式化输出&解码
package main import ( "encoding/json" "fmt" ) type colorgroup struct { id int name string colors []string } // 创建一个colorgroup类型的变量来保存解码后的数据 var decodedgroup colorgroup func main() { group := colorgroup{ id: 1, name: "reds", colors: []string{"crimson", "red", "ruby", "maroon"}, } // 将结构体编码为json字符串 jsondata1, err := json.marshal(group) jsondata2, err := json.marshalindent(group, "", " ") if err != nil { fmt.println("error:", err) return } // 打印json字符串 fmt.println(string(jsondata1)) fmt.println(string(jsondata2)) // output: //{"id":1,"name":"reds","colors":["crimson","red","ruby","maroon"]} // { // "id": 1, // "name": "reds", // "colors": [ // "crimson", // "red", // "ruby", // "maroon" // ] // } // 将json字符串解码到colorgroup结构体中 err = json.unmarshal([]byte(jsondata1), &decodedgroup) if err != nil { fmt.println("error:", err) return } // 打印解码后的数据 fmt.printf("id: %d, name: %s, colors: %v\n", decodedgroup.id, decodedgroup.name, decodedgroup.colors) // output: id: 1, name: reds, colors: [crimson red ruby maroon] fmt.println(decodedgroup.colors[0]) fmt.println(decodedgroup.colors[1]) }
2、使用第三方包
标准输出&格式化输出&解码
package main import ( "fmt" "github.com/goccy/go-json" "os" ) type colorgroup struct { id int name string colors []string } func main() { group := colorgroup{ id: 1, name: "reds", colors: []string{"crimson", "red", "ruby", "maroon"}, } b1, err := json.marshal(group) if err != nil { fmt.println("error:", err) } println(os.stdout.write(b1)) //os.stdout.write(b1)将字节切片b(即json字符串的字节表示)写入到标准输出 fmt.println("---------------格式化输出----------------") // 使用 marshalindent 来格式化输出 b2, err := json.marshalindent(group, "", " ") // 第二个参数是空字符串,表示不添加前缀;第三个参数是缩进字符串 if err != nil { fmt.println("error:", err) return } // 使用 fmt.println 来打印字符串 // marshalindent返回的是字节切片,我们需要使用string(b2)来将其转换为字符串 fmt.println(string(b2)) // 将字节切片转换为字符串并打印 // 输出将会是格式化后的 json 字符串 // 创建一个colorgroup类型的变量来保存解码后的数据 var decodedgroup colorgroup // 将json字符串解码到colorgroup结构体中 err = json.unmarshal([]byte(b1), &decodedgroup) if err != nil { fmt.println("error:", err) return } // 打印解码后的数据 fmt.printf("id: %d, name: %s, colors: %v\n", decodedgroup.id, decodedgroup.name, decodedgroup.colors) // output: id: 1, name: reds, colors: [crimson red ruby maroon] fmt.println(decodedgroup.colors[0]) fmt.println(decodedgroup.colors[1]) }
请注意,在解码时,你需要将json字符串转换为[]byte
,并且传入结构体的指针(使用&
)。这样,解码后的数据才会被写入到结构体中
3、decode
package main import ( "fmt" "github.com/goccy/go-json" ) // animal 定义结构体来表示单个json对象 type animal struct { name string order string } func main() { //创建一个json字节切片 var jsonblob = []byte(`[ {"name": "platypus", "order": "monotremata"}, {"name": "quoll", "order": "dasyuromorphia"} ]`) var animals []animal err := json.unmarshal(jsonblob, &animals) if err != nil { fmt.println("error:", err) } fmt.printf("% v", animals) fmt.println() // 打印解码后的数据 for _, animal := range animals { fmt.printf("name: %s, order: %s\n", animal.name, animal.order) } }
4、注意
结构体
结构体必须是大写字母开头的成员才会被json
处理到,小写字母开头的成员不会有影响。
mashal
时,结构体的成员变量名将会直接作为json
object
的key
打包成json
;unmashal
时,会自动匹配对应的变量名进行赋值,大小写不敏感。
unmarshal
时,如果json
中有多余的字段,会被直接抛弃掉;如果json
缺少某个字段,则直接忽略不对结构体中变量赋值,不会报错。
package main import ( "encoding/json" "fmt" ) type message struct { name string body string time int64 inner string } func main() { var m = message{ name: "alice", body: "hello", time: 1294706395881547000, inner: "ok", } b := []byte(`{"name":"bob","food":"pickle", "inner":"changed"}`) err := json.unmarshal(b, &m) if err != nil { fmt.printf(err.error()) return } fmt.printf("%v", m) //output: {bob hello 1294706395881547000 ok} }
structtag/结构体标签
如果希望手动配置结构体的成员和json
字段的对应关系,可以在定义结构体的时候给成员打标签:
使用omitempty
熟悉,如果该字段为nil
或0值(数字0,字符串"",空数组[]等),则打包的json
结果不会有这个字段。
案例一
package main import ( "encoding/json" "fmt" ) type message struct { name string `json:"msg_name"` // 对应json的msg_name body string `json:"body,omitempty"` // 如果为空置则忽略字段 time int64 `json:"-"` // 直接忽略字段 } func main() { var m = message{ name: "alice", body: "", time: 1294706395881547000, } data, err := json.marshal(m) if err != nil { fmt.printf(err.error()) return } fmt.println(string(data)) //output:{"msg_name":"alice"} }
案例二
package main import ( "encoding/json" "fmt" "log" "time" ) // 定义一个用于json映射的结构体 type user struct { name string `json:"username"` // 自定义字段名称映射 email string `json:"email"` lastseen customtime `json:"last_seen"` // 嵌套对象 active bool `json:"-"` // 忽略此字段,即使json中存在也不解码 } // customtime 是一个用于表示时间的结构体 type customtime struct { time.time } // 实现 json.unmarshaler 接口的 unmarshaljson 方法 func (ct *customtime) unmarshaljson(data []byte) error { var s string if err := json.unmarshal(data, &s); err != nil { return err } // 解析自定义时间格式 parsedtime, err := time.parse(time.rfc3339, s) if err != nil { return err } ct.time = parsedtime return nil } func main() { // 模拟从http请求中获取的json数据 jsondata := []byte(`{ "username": "johndoe", "email": "john.doe@example.com", "last_seen": "2023-04-01t12:34:56z", "active": true }`) // 创建一个 user 实例 var user user // 使用 json.unmarshal 解码 json 数据 if err := json.unmarshal(jsondata, &user); err != nil { log.fatal("error unmarshaling json:", err) } // 打印解码后的信息 fmt.printf("name: %s\n", user.name) fmt.printf("email: %s\n", user.email) fmt.printf("last seen: %v\n", user.lastseen) // active 字段将不会被解码,即使json中存在 fmt.printf("active: %v\n", user.active) //输出: //name: johndoe //email: john.doe@example.com // last seen: 2023-04-01 12:34:56 0000 utc //active: false }
5、更灵活地使用json
使用json.rawmessage
json.rawmessage
其实就是[]byte
类型的重定义。可以进行强制类型转换。
现在有这么一种场景,结构体中的其中一个字段的格式是未知的:
type command struct { id int cmd string args *json.rawmessage }
使用json.rawmessage
的话,args
字段在unmarshal
时不会被解析,直接将字节数据赋值给args
。我们可以能先解包第一层的json
数据,然后根据cmd
的值,再确定args
的具体类型进行第二次unmarshal
。
这里要注意的是,一定要使用指针类型*json.rawmessage
,否则在args
会被认为是[]byte
类型,在打包时会被打包成base64
编码的字符串。
案例一
package main import ( "encoding/json" "fmt" "log" ) type command struct { id int cmd string args *json.rawmessage // 未解析的json片段 } func main() { //json字节切片 jsondata := []byte(`{ "id": 1, "cmd": "example", "args": ["arg1", "arg2"] }`) var cmd command //解码/反序列化 if err := json.unmarshal(jsondata, &cmd); err != nil { log.fatalf("error unmarshaling json: %v", err) } fmt.printf("command: % v\n", cmd) // 如果需要,可以进一步处理cmd.args字段 // 例如,将其解析为特定的go类型 var args []string if err := json.unmarshal(*cmd.args, &args); err != nil { log.printf("解析错误: %v", err) } else { fmt.printf("args: %v\n", args) } //输出 //command: {id:1 cmd:example args:0xc0000080f0} //args: [arg1 arg2] }
案例二
package main import ( "encoding/json" "fmt" "log" ) type command struct { id int cmd string args *json.rawmessage // 未解析的json片段 } // unmarshaljson 自定义json解码方法,command实现了unmarshaler接口 func (c *command) unmarshaljson(data []byte) error { fmt.println("--------------使用自定义解码--------------") // 定义一个辅助结构体,用于解码除args外的其他字段 type alias command var aux struct { alias // 嵌入别名类型以获取其他字段 } // 先解码除args外的所有字段 if err := json.unmarshal(data, &aux); err != nil { return err } fmt.printf("command id: % v, cmd: % v\n", aux.alias.id, aux.alias.cmd) // 将别名结构体中的字段复制到c中 *c = command(aux.alias) // 检查json中是否有args字段,并处理它 var m map[string]json.rawmessage if err := json.unmarshal(data, &m); err != nil { // 如果这里出错,可能是因为json格式不正确,但我们可能仍然想要保留已经解析的字段 // 因此,我们可以只记录一个错误,但不返回它 log.printf("error parsing args field: %v", err) } else { // 如果args字段存在,将其赋值给c.args if rawargs, ok := m["args"]; ok { c.args = &rawargs // 注意这里我们取了rawargs的地址 var args []string if err := json.unmarshal(*c.args, &args); err != nil { log.printf("error parsing args contents: %v", err) } else { fmt.printf("args: %v\n", args) } } } // 如果没有错误,返回nil return nil } func main() { //json字节切片 jsondata := []byte(`{ "id": 1, "cmd": "example", "args": ["arg1", "arg2"] }`) var cmd command //解码/反序列化 if err := json.unmarshal(jsondata, &cmd); err != nil { log.fatalf("error unmarshaling json: %v", err) } }
案例三
package main import ( "encoding/json" "fmt" "log" ) type command struct { id int cmd string args *json.rawmessage // 未解析的json片段 } // unmarshaljson 自定义json解码方法,command实现了unmarshaler接口 func (c *command) unmarshaljson(data []byte) error { fmt.println("--------------使用自定义解码--------------") // 检查json中是否有args字段,并处理它 var m map[string]json.rawmessage if err := json.unmarshal(data, &m); err != nil { // 如果这里出错,可能是因为json格式不正确,但我们可能仍然想要保留已经解析的字段 // 因此,我们可以只记录一个错误,但不返回它 log.printf("error parsing args field: %v", err) } else { // 如果args字段存在,将其赋值给c.args if rawargs, ok := m["args"]; ok { c.args = &rawargs // 注意这里我们取了rawargs的地址 var args []string if err := json.unmarshal(*c.args, &args); err != nil { log.printf("error parsing args contents: %v", err) } else { fmt.printf("args: %v\n", args) } } } // 如果没有错误,返回nil return nil } func main() { //json字节切片 jsondata := []byte(`{ "id": 1, "cmd": "example", "args": ["arg1", "arg2"] }`) var cmd command //解码/反序列化 if err := json.unmarshal(jsondata, &cmd); err != nil { log.fatalf("error unmarshaling json: %v", err) } }
调用的json.unmarshal,并不是调用json.unmarshaler,为什么会调用unmarshaljson
调用 json.unmarshal
函数时,您并没有直接调用 json.unmarshaler
接口的方法。但是,json.unmarshal
函数在内部会检查目标类型是否实现了 json.unmarshaler
接口。如果实现了该接口,json.unmarshal
就会使用您为该类型定义的 unmarshaljson
方法来解码 json 数据。
这是 json.unmarshal
函数内部逻辑的一部分,用于确定如何解码 json 数据。具体步骤如下:
json.unmarshal
接收一个字节切片(包含 json 数据)和一个目标值的指针。- 它首先会检查目标值的类型是否实现了
json.unmarshaler
接口。 - 如果实现了
json.unmarshaler
接口,json.unmarshal
就会调用该类型的unmarshaljson
方法,并将 json 数据的字节切片作为参数传递给它。 - 如果目标值没有实现
json.unmarshaler
接口,json.unmarshal
就会使用默认的解码逻辑来填充目标值的字段。
这种机制使得开发者能够灵活地控制 json 数据到 go 结构体之间的转换过程。通过实现 json.unmarshaler
接口,您可以:
- 处理 json 数据中不存在的字段。
- 自定义字段名称的映射规则。
- 处理 json 数据中的嵌套对象或数组。
- 执行额外的验证或数据处理逻辑。
以下是简单的示例,展示了如何为一个类型实现 json.unmarshaler
接口
处理 json 数据中不存在的字段
假设我们有一个结构体,它能够处理json中可能缺失的字段,并且为这些字段提供默认值。
在这个例子中,age
字段在json中不存在,因此它将被赋予其类型的零值(对于int
类型是0
)。
package main import ( "encoding/json" "fmt" "log" ) type user struct { name string `json:"name"` age int `json:"age"` email string `json:"email,omitempty"` } func main() { var user user // json 中没有 "age" 字段,将使用 age 的零值 0 jsondata := []byte(`{"name": "john", "email": "john@example.com"}`) if err := json.unmarshal(jsondata, &user); err != nil { log.fatal(err) } fmt.printf("name: %s, age: %d, email: %s\n", user.name, user.age, user.email) //name: john, age: 0, email: john@example.com }
自定义字段名称的映射规则
使用结构体标签中的json
键来指定json字段名。
在这个例子中,结构体的字段名和json字段名不匹配,我们通过在结构体标签中指定json
来实现映射。
package main import ( "encoding/json" "fmt" "log" ) type user struct { username string `json:"user_name"` password string `json:"pass"` } func main() { var user user jsondata := []byte(`{"user_name": "johndoe", "pass": "secret"}`) if err := json.unmarshal(jsondata, &user); err != nil { log.fatal(err) } fmt.printf("username: %s, password: %s\n", user.username, user.password) //username: johndoe, password: secret }
处理 json 数据中的嵌套对象或数组
解码一个包含嵌套结构体的json数据。
在这个例子中,address
是一个嵌套在 user
结构体中的对象。
package main import ( "encoding/json" "fmt" "log" ) type address struct { city string `json:"city"` country string `json:"country"` } type user struct { name string `json:"name"` address address `json:"address"` // 嵌套对象 } func main() { var user user jsondata := []byte(`{"name": "jane", "address": {"city": "new york", "country": "usa"}}`) if err := json.unmarshal(jsondata, &user); err != nil { log.fatal(err) } fmt.printf("name: %s, lives in %s, %s\n", user.name, user.address.city, user.address.country) //name: jane, lives in new york, usa }
执行额外的验证或数据处理逻辑
在unmarshaljson
方法中添加额外的验证逻辑。
在这个例子中,我们为user
类型实现了自定义的unmarshaljson
方法。在解码过程中,如果age
字段的值是负数,将返回一个错误,这是一个额外的验证逻辑。
package main import ( "encoding/json" "fmt" "log" ) type user struct { name string `json:"name"` age int `json:"age"` } func (u *user) unmarshaljson(data []byte) error { type alias user // 影子类型,避免递归调用 unmarshaljson aux := &alias{name: u.name, age: u.age} // 使用辅助结构体来解耦 if err := json.unmarshal(data, aux); err != nil { return err } *u = user(*aux) // 将解耦的结构体赋值给当前结构体 if u.age < 0 { //年龄不能为负数 return fmt.errorf("age cannot be negative") } return nil } func main() { var user user jsondata := []byte(`{"name": "alice", "age": -5}`) if err := json.unmarshal(jsondata, &user); err != nil { log.fatal(err) } fmt.printf("name: %s, age: %d\n", user.name, user.age) }
在上面的示例中,user
类型实现了 json.unmarshaler
接口的 unmarshaljson
方法,使得 json.unmarshal
函数在解码 json 数据时会调用这个方法,而不是使用默认的解码逻辑。这允许我们自定义解码逻辑,例如只接受特定格式的 json 数据。
使用interface{}
interface{}
类型在unmarshal
时,会自动将json
转换为对应的数据类型:
json的boolean 转换为bool
json的数值 转换为float64
json的字符串 转换为string
json的array 转换为[]interface{}
json的object 转换为map[string]interface{}
json的null 转换为nil
需要注意的有两个。一个是所有的json
数值自动转换为float64
类型,使用时需要再手动转换为需要的int
,int64
等类型。第二个是json
的object
自动转换为map[string]interface{}
类型,访问时直接用json ``object
的字段名作为key
进行访问。再不知道json
数据的格式时,可以使用interface{}
。