Go中的標準庫encoding/json提供JSON格式的序列化和反序列化功能.
序列化struct為JSON
type Person struct {
fullName string
Name string
Age int `json:"age"`
City string `json:"city"`
}
p := Person{
Name: "John",
Age: 37,
City: "SF",
}
d, err := json.Marshal(&p)
if err != nil {
log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in compact JSON: %s\n", string(d))
d, err = json.MarshalIndent(p, "", " ")
if err != nil {
log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in pretty-printed JSON:\n%s\n", string(d))
Person in compact JSON: {"Name":"John","age":37,"city":"SF"}
Person in pretty-printed JSON:
{
"Name": "John",
"age": 37,
"city": "SF"
}
json.Marshal和json.MarshalIndent都將interface {}作為第一個參數(shù)。我們可以傳遞任何Go值谜喊,并將其類型包裝到interface {}中锡宋。
Marshaller將使用反射來檢查傳遞的值并將其編碼為JSON字符串牧牢。
在序列化struct時若贮,僅對導出的字段(其名稱以大寫字母開頭)進行序列化/反序列化嗦玖。
在我們的示例中硬梁,未對fullName進行序列化合武。
struct被序列化為JSON字典临梗。默認情況下,字典鍵與struct字段名稱相同稼跳。
struct字段名稱在字典鍵名稱下序列化盟庞。
可以提供帶有struct標簽的自定義映射。
可以將任意的struct標簽字符串附加到struct字段汤善。
json:"age"指示JSON編碼器/解碼器使用名稱age作為表示字段Age的字典關鍵字什猖。
序列化struct時票彪,將值和指針傳遞給它會產(chǎn)生相同的結(jié)果。
傳遞指針效率更高不狮,因為按值傳遞會創(chuàng)建不必要的副本降铸。
json.MarshallIndent格式化打印嵌套struct, 這樣會占用更多空間但更易于閱讀。
把JSON轉(zhuǎn)為struct
type Person struct {
Name *string `json:"name"`
Age int `json:"age"`
City string
Occupation string
}
var jsonStr = `{
"name": "Jane",
"age": 24,
"city": "ny"
}`
var p Person
err := json.Unmarshal([]byte(jsonStr), &p)
if err != nil {
log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
fmt.Printf("Person struct parsed from JSON: %#v\n", p)
fmt.Printf("Name: %#v\n", *p.Name)
Person struct parsed from JSON: main.Person{Name:(*string)(0xc000010330), Age:24, City:"ny", Occupation:""}
Name: "Jane"
解析與序列化相反
與序列化不同摇零,在解析為結(jié)構(gòu)時推掸,必須將指針傳遞給struct。否則json.Unmarshal將接收并修改該結(jié)構(gòu)的副本驻仅,而不是結(jié)構(gòu)本身谅畅。從json.Unmarshal返回后,該副本將被丟棄噪服。
請注意毡泻,即使名稱不匹配,JSON元素city也被解碼為City struct字段芯咧,并且我們沒有使用json struct標簽提供顯式映射牙捉。
發(fā)生這種情況是因為在將字典關鍵字名稱與結(jié)構(gòu)字段名稱進行匹配時,JSON解碼器具有一些技巧敬飒。最好不要依賴這種智能邪铲,而是明確定義映射。
所有struct字段都是可選的无拗,并且當不以JSON文本形式出現(xiàn)時带到,其值將保持不變。當解碼為新初始化的struct時英染,對于給定類型揽惹,其值為零。
字段名稱顯示JSON解碼器還可以自動將其解碼為指向值的指針四康。
當您需要知道JSON中是否存在值時搪搏,這很有用。如果我們使用字符串作為Name闪金,我們將不知道空字符串的值是否意味著JSON具有以空字符串作為值的名稱鍵疯溺,或者是因為該值根本不存在。
通過使用指向字符串的指針哎垦,我們知道nil表示沒有值囱嫩。
JSON和Go類型映射:
- JSON類型 Go類型
- boolean bool
- number float64 or int
- string string
- array slice
- dictionary map[struct]interface{} or struct
- null nil
解析任意JSON
解析為一個結(jié)構(gòu)非常方便,但有時我們不知道JSON的結(jié)構(gòu)漏设。
對于任意JSON墨闲,我們可以解碼為map[string]interface{},它可以表示任意有效的JSON郑口。
var jsonStr = `{
"name": "Jane",
"age": 24,
"city": "ny"
}`
var doc map[string]interface{}
err := json.Unmarshal([]byte(jsonStr), &doc)
if err != nil {
log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
fmt.Printf("doc: %#v\n", doc)
name, ok := doc["name"].(string)
if !ok {
log.Fatalf("doc has no key 'name' or its value is not string\n")
}
fmt.Printf("name: %#v\n", name)
doc: map[string]interface {}{"age":24, "city":"ny", "name":"Jane"}
name: "Jane"
對于基本JSON類型鸳碧,映射中的值為bool盾鳞,int,float64或string瞻离。
對于JSON數(shù)組雁仲,該值為[] interface {}。
對于JSON字典琐脏,該值是(再次)map [string] interface {}攒砖。
這種方法很靈活,但是處理map [string] interface {}以訪問值是很痛苦的日裙。
反序列化為匿名struct
解析為結(jié)構(gòu)時吹艇,我們可以使用匿名struct來避免聲明結(jié)構(gòu)類型。
var jsonBlob = []byte(`
{
"_total": 1,
"_links": {
"self": "https://api.twitch.tv/kraken/channels/foo/subscriptions?direction=ASC&limit=25&offset=0",
"next": "https://api.twitch.tv/kraken/channels/foo/subscriptions?direction=ASC&limit=25&offset=25"
},
"subscriptions": [
{
"created_at": "2011-11-23T02:53:17Z",
"_id": "abcdef0000000000000000000000000000000000",
"_links": {
"self": "https://api.twitch.tv/kraken/channels/foo/subscriptions/bar"
},
"user": {
"display_name": "bar",
"_id": 123456,
"name": "bar",
"created_at": "2011-06-16T18:23:11Z",
"updated_at": "2014-10-23T02:20:51Z",
"_links": {
"self": "https://api.twitch.tv/kraken/users/bar"
}
}
}
]
}
`)
var js struct {
Total int `json:"_total"`
Links struct {
Next string `json:"next"`
} `json:"_links"`
Subs []struct {
Created string `json:"created_at"`
User struct {
Name string `json:"name"`
ID int `json:"_id"`
} `json:"user"`
} `json:"subscriptions"`
}
err := json.Unmarshal(jsonBlob, &js)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%+v", js)
{Total:1 Links:{Next:https://api.twitch.tv/kraken/channels/foo/subscriptions?direction=ASC&limit=25&offset=25} Subs:[{Created:2011-11-23T02:53:17Z User:{Name:bar ID:123456}}]}
從文件反序列化JSON
我們可以從磁盤上的文件,或者任何io.Reader,比如網(wǎng)絡連接來反序列化JSON.
下面的例子從文件讀取JSON并反序列化:
type Student struct {
Name string
Standard int `json:"Standard"`
}
func decodeFromReader(r io.Reader) ([]*Student, error) {
var res []*Student
dec := json.NewDecoder(r)
err := dec.Decode(&res)
if err != nil {
return nil, err
}
return res, nil
}
func decodeFromString(s string) ([]*Student, error) {
r := bytes.NewBufferString(s)
return decodeFromReader(r)
}
func decodeFromFile(path string) ([]*Student, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return decodeFromReader(f)
}
Student: John Doe, standard: 4
Student: Peter Parker, standard: 11
Student: Bilbo Baggins, standard: 150
通過編寫幫助函數(shù)decodeFromReader昂拂,我們可以輕松編寫可用于文件受神,字符串或網(wǎng)絡連接的包裝器。
配置JSON序列化
隱藏/跳過某些字段
要導出Revenue和sales但不對它們進行編碼/解碼格侯,請使用json:“-”或重命名變量以小寫字母開頭鼻听。 請注意,這將防止變量在包外部可見联四。
type Company struct {
Name string `json:"name"`
Location string `json:"location"`
Revenue int `json:"-"`
sales int
}
忽略空字段
為了防止Location設置為零值時將其包含在JSON中撑碴,請將,omitempty添加到json標記中朝墩。
type Company struct {
Name string `json:"name"`
Location string `json:"location,omitempty"`
}
自定義JSON序列化
編寫自定義JSON序列化
有時醉拓,類型沒有明顯的JSON映射。
如何序列化time.Time收苏? 有很多可能性亿卤。
Go為time.Time提供了默認的JSON映射。 我們可以為用戶定義的類型(例如struct)實現(xiàn)自定義序列化鹿霸。
對于現(xiàn)有類型排吴,我們可以定義一個新的(但兼容)類型。
這是時間的自定義序列化懦鼠。時間僅序列化年/月/日部分:
type Event struct {
What string
When time.Time
}
e := Event{
What: "earthquake",
When: time.Now(),
}
d, err := json.Marshal(&e)
if err != nil {
log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Standard time JSON: %s\n", string(d))
type customTime time.Time
const customTimeFormat = `"2006-02-01"`
func (ct customTime) MarshalJSON() ([]byte, error) {
t := time.Time(ct)
s := t.Format(customTimeFormat)
return []byte(s), nil
}
func (ct *customTime) UnmarshalJSON(d []byte) error {
t, err := time.Parse(customTimeFormat, string(d))
if err != nil {
return err
}
*ct = customTime(t)
return nil
}
type Event2 struct {
What string
When customTime
}
e := Event2{
What: "earthquake",
When: customTime(time.Now()),
}
d, err := json.Marshal(&e)
if err != nil {
log.Fatalf("json.Marshal failed with '%s'\n", err)
}
fmt.Printf("\nCustom time JSON: %s\n", string(d))
var decoded Event2
err = json.Unmarshal(d, &decoded)
if err != nil {
log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
t := time.Time(decoded.When)
fmt.Printf("Decoded custom time: %s\n", t.Format(customTimeFormat))
notCustom()
custom()
Standard time JSON: {"What":"earthquake","When":"2019-11-06T02:18:11.203193337Z"}
Custom time JSON: {"What":"earthquake","When":"2019-06-11"}
Decoded custom time: "2019-06-11"
請注意钻哩,UnmashalJSON的接收者類型是指向該類型的指針。
這對于將更改保留在函數(shù)本身之外是必要的葛闷。
帶私有字段的封送結(jié)構(gòu)
考慮具有已導出和未導出字段的結(jié)構(gòu):
type MyStruct struct {
uuid string
Name string
}
想象一下憋槐,您想將該結(jié)構(gòu)Marshal()轉(zhuǎn)換為有效的JSON双藕,以便存儲在etcd之類的文件中淑趾。
但是,由于未導入uuid忧陪,因此json.Marshal()跳過了它扣泊。
要封送私有字段而不將其公開近范,我們可以使用自定義封送程序:
type MyStruct struct {
uuid string
Name string
}
func (m MyStruct) MarshalJSON() ([]byte, error) {
j, err := json.Marshal(struct {
Uuid string
Name string
}{
Uuid: m.uuid,
Name: m.Name,
})
if err != nil {
return nil, err
}
return j, nil
}
s := MyStruct{
uuid: "uid-john",
Name: "John",
}
d, err := json.Marshal(&s)
if err != nil {
log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in compact JSON: %s\n", string(d))
Person in compact JSON: {"Uuid":"uid-john","Name":"John"}
幕后的自定義封送處理
自定義封送處理如何工作?
包JSON定義了2個接口:Marshaler和Unmarshaler延蟹。
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
通過實現(xiàn)這些功能评矩,我們使我們的類型符合Marshaler或Unmarshaler接口。
JSON編碼器/解碼器檢查被編碼的值是否符合那些接口阱飘,并將調(diào)用這些函數(shù)而不執(zhí)行默認邏輯斥杜。
Go和JSON類型映射
JSON type Go type
null nil
boolean bool
number float64 or int
string string
array slice
dictionary map[struct]interface{} or struct
使用示例:
func printSerialized(v interface{}, w io.Writer) {
d, err := json.Marshal(v)
if err != nil {
log.Fatalf("json.Marshal failed with '%s'\n", err)
}
fmt.Fprintf(w, "%T\t%s\n", v, string(d))
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 5, 0, 1, ' ', 0)
fmt.Fprint(w, "Go type:\tJSON value:\n")
fmt.Fprint(w, "\t\n")
printSerialized(nil, w)
printSerialized(5, w)
printSerialized(8.23, w)
printSerialized("john", w)
ai := []int{5, 4, 18}
printSerialized(ai, w)
a := []interface{}{4, "string"}
printSerialized(a, w)
d := map[string]interface{}{
"i": 5,
"s": "foo",
}
printSerialized(d, w)
s := struct {
Name string
Age int
}{
Name: "John",
Age: 37,
}
printSerialized(s, w)
w.Flush()
Go type: JSON value:
<nil> null
int 5
float64 8.23
string "john"
[]int [5,4,18]
[]interface {} [4,"string"]
map[string]interface {} {"i":5,"s":"foo"}
struct { Name string; Age int } {"Name":"John","Age":37}
輕松生成JSON結(jié)構(gòu)定義
編寫映射JSON文件結(jié)構(gòu)的結(jié)構(gòu)定義很繁瑣。
如果您有示例JSON文件沥匈,則可以使用在線工具自動生成Go定義:
https://app.quicktype.io/
https://mholt.github.io/json-to-go/