本文是關于使用Go的encoding/json包時需要注意的一些會讓人迷惑的內容先壕。如果您仔細地閱讀官方包文檔,就會發(fā)現其中有許多內容都提到了炉奴,所以從理論上講育韩,這些內容應該不會讓您感到驚訝克蚂。但其中有一些根本沒有在文檔中提到,或者至少沒有明確指出-值得注意!
1筋讨、json序列化map的內容是按照字母排序的
當將一個map編碼為json埃叭,其內容將根據鍵值以字母順序排列,例如:
func main() {
m := map[string]int{
"z": 123,
"0": 123,
"a": 123,
"_": 123,
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
結果:
{"0":123,"_":123,"a":123,"z":123}
2悉罕、byte切片將編碼為base64字符串
當將任何[]byte切片編碼為JSON時赤屋,它們將被轉換為base64編碼的字符串。base64字符串使用填充和標準編碼字符壁袄,如RFC4648中定義的那樣类早。例如,下面的map:
func main() {
m := map[string][]byte{
"foo": []byte("bar baz"),
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
結果為:
{"foo":"YmFyIGJheg=="}
3嗜逻、Nil和空切片編碼結果不一樣
Go中的空切片將被編碼為null JSON值涩僻。相反,空的(但不是nil的)切片將被編碼為空JSON數組栈顷。例如:
func main() {
var nilSlice []string
emptySlice := []string{}
m := map[string][]string{
"nilSlice": nilSlice,
"emptySlice": emptySlice,
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
編碼結果:
{"emptySlice":[],"nilSlice":null}
4逆日、整數、time.Time和net.IP值可以作為map的key
map以整數值為key可以被序列化為json萄凤。這些整數將被自動轉換為JSON中的字符串(因為JSON對象中的鍵必須總是字符串)室抽。例如:
func main() {
m := map[int]string{
123: "foo",
456_000: "bar",
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
輸出結果:
{"123":"foo","456000":"bar"}
此外,Go還允許實現了encoding.TextMarshaler接口的鍵對map序列化靡努。這意味著你可以直接使用time.Time和net.IP值作為map的key坪圾。例如:
func main() {
t1 := time.Now()
t2 := t1.Add(24 * time.Hour)
m := map[time.Time]string{
t1: "foo",
t2: "bar",
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
輸出結果:
{"2021-09-19T07:26:03.938939+08:00":"foo","2021-09-20T07:26:03.938939+08:00":"bar"}
注意,如果使用其他類型作為map的鍵進行編碼將會得到一個json.UnsupportedTypeError錯誤颤难。
5神年、字符串中的尖括號和&符號被轉義
如果一個字符串包含尖括號<>,在JSON中將轉義為\u003c和\u003e行嗤。同樣已日,&字符將轉義為\u0026。這是為了防止某些web瀏覽器不小心將JSON解釋為HTML栅屏。例如:
func main() {
m := []string{
"<foo>",
"bar & baz",
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
輸出結果:
["\u003cfoo\u003e","bar \u0026 baz"]
如果你需要將特殊符號保持原來格式編碼飘千,可以使用json.Encoder對象并調用setEscapeHTML(false)即可。
6栈雳、浮點數末尾零被刪除
當編碼一個以0結尾的小數部分的浮點數時护奈,JSON中不會出現任何尾隨的0。例如:
func main() {
m := []float64{
123.0,
456.100,
789.990,
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
輸出結果:
[123,456.1,789.99]
6哥纫、使用omitempty在結構體類型時會失效霉旗。
omitempty指令從不認為struct類型是空的-即使所有的struct字段都有零值,并且在這些字段上使用了omitempty。它將始終以JSON中的對象形式出現厌秒。例如:
func main() {
m := struct {
Foo struct {
Bar string `json:",omitempty"`
} `json:",omitempty"`
}{}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
結果:
{"Foo":{}}
如果要實現結構體輸出空读拆,可以使用指針來定義,omitempty對nil會生效鸵闪。
func main() {
m := struct {
Foo *struct {
Bar string `json:",omitempty"`
} `json:",omitempty"`
}{}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
輸出結果為:
{}
7檐晕、使用omitempty在time.Time的零值也會失效
在零值時間上使用omitempty。time.Time字段不會在編碼的JSON中隱藏蚌讼。這是因為時間time.Time是一個struct類型辟灰,如上所述,omitempty從不將一個結構類型視為空篡石。因此芥喇,字符串"0001-01-01 t00:00:00 - 00z "將出現在JSON中(這是在零值time.Time上調用MarshalJSON()方法返回的值。例如:
func main() {
m := struct {
Foo time.Time `json:",omitempty"`
}{}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
輸出結果:
{"Foo":"0001-01-01T00:00:00Z"}
8夏志、string標簽
Go提供了一個字符串結構標記乃坤,它強制將單個字段中的數據編碼為JSON中的字符串。例如沟蔑,如果你想強制將一個整數表示為字符串而不是JSON數字湿诊,你可以使用string指令,如下所示:
func main() {
m := struct {
Foo int `json:",string"`
}{
Foo: 123,
}
marshal, _ := json.Marshal(m)
fmt.Println(string(marshal))
}
輸出結果:
{"Foo":"123"}
注意瘦材,string標記只對包含float厅须、integer或bool類型的字段有效。對于任何其他類型都沒有效果食棕。
9朗和、將json的number反序列化到interface{}會轉為float64類型
當將JSON數字解碼為interface{}類型時,該值將被轉為float64類型簿晓,即使原始JSON中是整數眶拉。如果要保持整數輸出可以使用json.Decoder實例并調用UseNumber函數如下所示:
func main() {
js := `{"foo": 123, "bar": true}`
var m map[string]interface{}
dec := json.NewDecoder(strings.NewReader(js))
dec.UseNumber()
err := dec.Decode(&m)
if err != nil {
log.Fatal(err)
}
i, err := m["foo"].(json.Number).Int64()
if err != nil {
log.Fatal(err)
}
fmt.Printf("foo: %d", i)
}
輸出結果:
foo: 123
10、自定義MarshalJSON()方法返回的字符串值必須加引號
如果您正在創(chuàng)建一個返回字符串值的自定義MarshalJSON()方法憔儿,則必須在返回字符串之前用雙引號包裝該字符串忆植,否則它將不會被解釋為JSON字符串,并將導致運行時錯誤谒臼。例如:
type Age int
func (age Age) MarshalJSON() ([]byte, error) {
encodedAge := fmt.Sprintf("%d years", age)
encodedAge = strconv.Quote(encodedAge) // 返回之前用引號將字符串括起來
return []byte(encodedAge), nil
}
func main() {
users := map[string]Age{
"alice": 21,
"bob": 84,
}
js, err := json.Marshal(users)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", js)
}
輸出結果:
{"alice":"21 years","bob":"84 years"}
如果朝刊,在上面的代碼中,MarshalJSON()的返回值沒有使用strconv.Quote蜈缤,你會得到錯誤:
2021/09/19 08:04:25 json: error calling MarshalJSON for type main.Age: invalid character 'y' after top-level value