Golang encoding/json
包基于Struct类型的Tag
特性,提供了JSON解析方法。这里正是反射的常见用法之一。
包中最重要的两个函数如下:
Marshal(v interface{}) ([]byte, error)
: 将Go数据类型软换为JSON格式数据Unmarshal(data []byte, v interface{})
:将JSON格式数据转换为Go数据类型。
JSON包中的Tag约定
Struct的Tag实际上仅代表一个字符串而矣,至于字符串格式则是各package的约定而矣,encoding/json
包中把json
作为key,标明该tag可以其处理,其他的key值不在其处理范围。
针对Tag的写法,encoding/json
作了如下约定:
json:"Foo"
: Marshal时将结构体该字段名以“Foo”名输出,Unmarshal时将JSON相应字段赋值给结构体字段json:"-"
: 无论Marshal和Unmarshal都忽略该字段json:",omitempty"
:仅用于Marshal,如果结构体该字段为空,则忽略该字段。注意前面的逗号要保留。json:"Foo,omitempty"
:组合写法,含义同上
很简单的几个约定,不是吗。 来看一例子,使用一下上面的约定:
package main
import (
"encoding/json"
"fmt"
)
type Server struct {
ServerName string `json:"ServerName,omitempty"`
ServerIP string `json:"ServerAddr"`
ServerOwner string `json:"-"`
other string `json:"other"`
}
func main() {
str := `{"ServerName": "Shanghai_VPN", "ServerAddr": "127.0.0.1", "ServerOwner": "Rainbow", "other": "You can see me!"}`
var s Server
json.Unmarshal([]byte(str), &s)
fmt.Printf("s.ServerName: %s\n", s.ServerName)
fmt.Printf("s.ServerIP: %s\n", s.ServerIP)
fmt.Printf("s.ServerOwner: %s\n", s.ServerOwner)
fmt.Printf("s.other: %s\n", s.other)
s.ServerName = ""
jsonStr,_ := json.Marshal(s)
fmt.Printf("Marshal: %s", jsonStr)
}
上述代码声明一个Server
结构体,并场景了Tag. 定义一个变量s,Unmarshal将某JSON字符串中的数据填入s中,Marshal将s中的内容,输出为一段JSON数据。
输出如下:
s.ServerName: Shanghai_VPN //源自JSON数据中的"ServerName"
s.ServerIP: 127.0.0.1 //源自JSON数据中的"ServerAddr"
s.ServerOwner: //跟据Tag指示,该字段会被忽略
s.other: //json包不处理未被导出的成员
Marshal: {"ServerAddr":"127.0.0.1"} //跟据Tag指示,如果ServerName为空,则不输出
JSON转换规则
以Unmarshal为例说明规则,假定JSON数据中有个key名为"Foo",那么确定结构体的字段名字遵循如下规则:
- 可导出成员中, 是否存在Tag名为"Foo"的字段
- 可导出成员中,是否存在成员名为"Foo"的字段
- 可导出成员中,是否存在成员名为"FOo"或"FoO"(首字母大小,其余字段忽略大小写)的字段
所以说,如果JSON数据与结构体成员名一一对应,没有Tag也可以实现解析。换句话说,Tag可以方便的处理结构体成员名与JSON数据不一致的情况。
JSON数据类型与Go数据类型对照
Go数据类型 | JSON数据类型 |
---|---|
bool | JSON booleans |
float64 | JSON numbers |
string | JSON strings |
nil | JSON null |
上例中结构体成员都是string类型,其实也可以是其他常见类型,对此,不再细述,参照上表处理即可。
未知JSON数据解析
注意,Unmarshal
方法中将JSON数据填入一个interface{}
变量,所以不管任何JSON数据都可以解析。无非就是再从空interface中提取数据罢了。一般情况下,json包会将JSON解析为一个map[string]interface{}
踩过的坑
关于omitempty
,要注意它会屏蔽一切的初始值和空值。比如,整型,即便赋值为0,其也不会被转换为JSON字符串。 有时,你希望结构体存储的是某个状态,而状态中正好有个值为“零”,这时就要小心了。
官方对omitempty
的解释很清楚The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.
再不清楚,看下面代码:
package main
import (
"fmt"
"encoding/json"
"github.com/bitly/go-simplejson"
)
type device struct {
DeviceID string `json:"DeviceID"`
ServerState int `json:"ServerState,omitempty"`
}
func main() {
var d device
d.DeviceID = "123"
d.ServerState = 0
data, _ := json.Marshal(d)
json, _ := simplejson.NewJson(data)
fmt.Printf("%v", json)
}
上面代码中ServerState即便赋值了,仍然不会输出。
程序输出如下:
&{map[DeviceID:123]}