Go:使用json時的陷阱

本文是關于使用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
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末拾氓,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子底哥,更是在濱河造成了極大的恐慌咙鞍,老刑警劉巖房官,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異续滋,居然都是意外死亡易阳,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門吃粒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拒课,你說我怎么就攤上這事徐勃。” “怎么了早像?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵僻肖,是天一觀的道長。 經常有香客問我卢鹦,道長臀脏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任冀自,我火速辦了婚禮揉稚,結果婚禮上,老公的妹妹穿的比我還像新娘熬粗。我一直安慰自己搀玖,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布驻呐。 她就那樣靜靜地躺著灌诅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪含末。 梳的紋絲不亂的頭發(fā)上猜拾,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音佣盒,去河邊找鬼挎袜。 笑死,一個胖子當著我的面吹牛沼撕,可吹牛的內容都是我干的宋雏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼务豺,長吁一口氣:“原來是場噩夢啊……” “哼磨总!你這毒婦竟也來了?” 一聲冷哼從身側響起笼沥,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚪燕,失蹤者是張志新(化名)和其女友劉穎娶牌,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體馆纳,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡诗良,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了鲁驶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鉴裹。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钥弯,靈堂內的尸體忽然破棺而出径荔,到底是詐尸還是另有隱情,我是刑警寧澤脆霎,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布总处,位于F島的核電站,受9級特大地震影響睛蛛,放射性物質發(fā)生泄漏鹦马。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一忆肾、第九天 我趴在偏房一處隱蔽的房頂上張望荸频。 院中可真熱鬧,春花似錦难菌、人聲如沸试溯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遇绞。三九已至,卻和暖如春燎窘,著一層夾襖步出監(jiān)牢的瞬間摹闽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工褐健, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留付鹿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓蚜迅,卻偏偏與公主長得像舵匾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谁不,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容

  • Go中的標準庫encoding/json提供JSON格式的序列化和反序列化功能. 序列化struct為JSON P...
    asdzxc閱讀 298評論 0 0
  • 標準庫中的encoding/xml包提供了XML格式的序列化功能. 將XML解析為struct 解析XML與解析J...
    asdzxc閱讀 240評論 0 0
  • 以下內容是我在學習和研究Go時坐梯,對Go的特性、重點和注意事項的提取刹帕、精練和總結吵血,還有一些學習筆記(注:部分筆記是摘...
    科研者閱讀 615評論 0 1
  • 很多程序都需要處理或者發(fā)布數據谎替,不管這個程序是要使用數據庫,進行網絡調用蹋辅,還是與分布式系統(tǒng)打交道钱贯。如果程序需要處理...
    Go語言由淺入深閱讀 1,238評論 0 1
  • 原文地址:https://www.liwenzhou.com/posts/Go/json_tricks_in_go...
    李小斌_2018閱讀 278評論 0 2