直接把结构体编码成json数据
package main
import (
"encoding/json"
"fmt"
_ "os"
)
type Address struct {
Type string
City string
Country string
}
type Card struct {
Name string
Age int
Addresses []*Address
}
func main() {
pa := &Address{"private", "Shanghai", "China"}
pu := &Address{"work", "Beijing", "China"}
c := Card{"Xin", 32, []*Address{pa, pu}}
js, _ := json.Marshal(c)
fmt.Printf("Json: %s", js)
}
利用json.Marshal
,可将结构体转为JSON数据:
Json: {"Name":"Xin","Age":32,"Addresses":[{"Type":"private","City":"Shanghai","Country":"China"},{"Type":"work","City":"Beijing","Country":"China"}]}
而json.MarshalIndent(c, "", " ")
可以将json代码格式化:
Json: {
"Name": "Xin",
"Age": 32,
"Addresses": [
{
"Type": "private",
"City": "Shanghai",
"Country": "China"
},
{
"Type": "work",
"City": "Beijing",
"Country": "China"
}
]
}
Tips
- 在 web 应用中最好使用
json.MarshalforHTML()
函数,会对数据执行HTML转码。 - map 的 key 必须是字符串类型。
- Channel,复杂类型和函数类型不能被编码。
- 指针可以被编码,实际上是对指针指向的值进行编码.
解码到数据结构
如果事先知道 JSON 数据的结构,可以事先定义一个结构来存储反序列化后的结果。如,对这样一段 json:
b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`)
我们定义这样一个数据结构:
type FamilyMember struct {
Name string
Age int
Parents []string
}
然后可以将其反序列化:
var m FamilyMember
err := json.Unmarshal(b, &m)
完整的程序:
package main
import (
"encoding/json"
"fmt"
)
type FamilyMember struct {
Name string
Age int
Parents []string
}
func main() {
b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`)
var m FamilyMember
// json.Unmarshal 用于解码 json 串
err := json.Unmarshal(b, &m)
if err == nil {
fmt.Printf("name: %s
Age: %d
Parents: %v
", m.Name, m.Age, m.Parents)
}
}
执行后输出:
name: Wednesday
Age: 6
Parents: [Gomez Morticia]
解码任意数据
json 包使用 map[string]interface{} 和 []interface{} 储存任意的 JSON 对象和数组。同样是上面的 JSON 串:
b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`)
用下面的代码进行解码:
var f interface{}
err := json.Unmarshal(b, &f)
会得到:
map[string]interface{} {
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{} {
"Gomez",
"Morticia",
},
}
对于这类的数据,可以使用switch
来判断节点的类型来遍历数据。
动态更改JSON Sprintf
Golang中使用 fmt.Sprintf 格式化 `JSON Data %s %d %f` 动态变更JSON数据.
其等效作用是, 采用struct转换, 更改结构体数据, 然后Marshal成JSON, 用于 http处理
const jsonRes = `
{
"server": {
"name": "ty",
"imageRef": "f9436296-854f-4fe2-939d-eb667b245b78",
"fmtSprintf": "%s",
"max_count": 1,
"min_count": 1,
"networks": [
{
"uuid": "1e16b87f-ef66-4f0d-ba3d-d93234159076"
}
],
"metadata": {
"ENV1": "100",
"ENV2": "1000",
"entrypoint": "/bin/sh -c 'while true; do echo hello world; sleep 99999d; done'"
}
}
}
`
func fmtSprintfJson() error {
//var datm map[string]interface{}
var datm interface{}
const jsonRes = `
{
"server": {
"name": "ty",
"imageRef": "f9436296-854f-4fe2-939d-eb667b245b78",
"fmtSprintf": "%s",
"max_count": 1,
"min_count": 1,
"networks": [
{
"uuid": "1e16b87f-ef66-4f0d-ba3d-d93234159076"
}
],
"metadata": {
"ENV1": "100",
"ENV2": "1000",
"entrypoint": "/bin/sh -c 'while true; do echo hello world; sleep 99999d; done'"
}
}
}
`
jsonResSprintf := fmt.Sprintf(jsonRes, "test")
fmt.Println("jsonResSprintf = ", jsonResSprintf)
err := json.Unmarshal([]byte(jsonResSprintf), &datm)
if err != nil {
fmt.Println("json.Unmarshal([]byte(json), createRequests) , Error : ", err)
return err
}
fmt.Println("struct request = ", datm)
return nil
}
和 stream 中 JSON 打交道
上面所有的 JSON 数据来源都是预先定义的 []byte
缓存,在很多时候,如果能读取/写入其他地方的数据就好了。encoding/json
库中有两个专门处理这个事情的结构:Decoder
和 Encoder
:
// Decoder 从 r io.Reader 中读取数据,`Decode(v interface{})` 方法把数据转换成对应的数据结构
func NewDecoder(r io.Reader) *Decoder
// Encoder 的 `Encode(v interface{})` 把数据结构转换成对应的 JSON 数据,然后写入到 w io.Writer 中
func NewEncoder(w io.Writer) *Encoder
下面的例子就是从标准输入流中读取数据,解析成数据结构,删除所有键不是 Name
的字段,然后再 encode 成 JSON 数据,打印到标准输出。
package main
import (
"encoding/json"
"log"
"os"
)
func main() {
dec := json.NewDecoder(os.Stdin)
enc := json.NewEncoder(os.Stdout)
for {
var v map[string]interface{}
if err := dec.Decode(&v); err != nil {
log.Println(err)
return
}
for k := range v {
if k != "Name" {
delete(v, k)
}
}
if err := enc.Encode(&v); err != nil {
log.Println(err)
}
}
}
更多控制:Tag
在定义 struct 字段的时候,可以在字段后面添加 tag,来控制 encode/decode 的过程:是否要 decode/encode 某个字段,JSON 中的字段名称是什么。
可以选择的控制字段有三种:
-
:不要解析这个字段omitempty
:当字段为空(默认值)时,不要解析这个字段。比如 false、0、nil、长度为 0 的 array,map,slice,stringFieldName
:当解析 json 的时候,使用这个名字
举例来说吧:
// 解析的时候忽略该字段。默认情况下会解析这个字段,因为它是大写字母开头的
Field int `json:"-"`
// 解析(encode/decode) 的时候,使用 `other_name`,而不是 `Field`
Field int `json:"other_name"`
// 解析的时候使用 `other_name`,如果struct 中这个值为空,就忽略它
Field int `json:"other_name,omitempty"`
延迟解析:json.RawMessage
在解析的时候,还可以把某部分先保留为 JSON 数据不要解析,等到后面得到更多信息的时候再去解析。继续拿 User
举例,比如我们要添加认证的信息,认证可以是用户名和密码,也可以是 token 认证。
type BasicAuth struct {
Email string
Password string
}
type TokenAuth struct {
Token string
}
type User struct {
Name string
IsAdmin bool
Followers uint
Auth json.RawMessage
}
我们在定义 User
结构体的时候,把认证字段的类型定义为 json.RawMessage
,这样解析 JSON 数据的时候,对应的字段会先不急着转换成 Go 数据结构。然后我们可以自己去再次调用 Unmarshal
去读取里面的值:
data := []byte(`{"Name":"cizixs","IsAdmin":true,"Followers":36}`)
err := json.Unmarshal(data, &basicAuth)
if basicAuth.Email != "" {
// 这是用户名/密码认证方式,在这里继续做一些处理
} else {
json.Unmarshal(data, &tokenAuth)
if tokenAuth.Token != "" {
// 这是 token 认证方法
}
}
自定义解析方法
如果希望自己控制怎么解析成 JSON,或者把 JSON 解析成自定义的类型,只需要实现对应的接口(interface)。encoding/json
提供了两个接口:Marshaler
和 Unmarshaler
:
// Marshaler 接口定义了怎么把某个类型 encode 成 JSON 数据
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
// Unmarshaler 接口定义了怎么把 JSON 数据 decode 成特定的类型数据。如果后续还要使用 JSON 数据,必须把数据拷贝一份
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
标准库 time.Time
就实现了这两个接口。另外一个简单的例子(这个例子来自于参考资料中 Go and JSON 文章):
type Month struct {
MonthNumber int
YearNumber int
}
func (m Month) MarshalJSON() ([]byte, error){
return []byte(fmt.Sprintf("%d/%d", m.MonthNumber, m.YearNumber)), nil
}
func (m *Month) UnmarshalJSON(value []byte) error {
parts := strings.Split(string(value), "/")
m.MonthNumber = strconv.ParseInt(parts[0], 10, 32)
m.YearNumber = strconv.ParseInt(parts[1], 10, 32)
return nil
}