理解 Go 中的 JSON

本文是基于 Go 官方和 https://eager.io/blog/go-and-json/ 進(jìn)行翻譯整理的

JSON 是一種輕量級(jí)的數(shù)據(jù)交換格式萍膛,常用作前后端數(shù)據(jù)交換,Go 在 encoding/json 包中提供了對(duì) JSON 的支持嚷堡。

序列化

把 Go struct 序列化成 JSON 對(duì)象蝗罗,Go 提供了 Marshal 方法艇棕,正如其含義所示表示編排序列化,函數(shù)簽名如下:

func Marshal(v interface{}) ([]byte, error)

舉例來(lái)說(shuō)串塑,比如下面的 Go struct:

type Message struct {
 Name string
 Body string
 Time int64
}

使用 Marshal 序列化:

m := Message{"Alice", "Hello", 1294706395881547000}
b, err := json.Marshal(m) 
fmt.Println(b) //{"Name":"Alice","Body":"Hello","Time":1294706395881547000}

在 Go 中并不是所有的類型都能進(jìn)行序列化:

  • JSON object key 只支持 string
  • Channel沼琉、complex、function 等 type 無(wú)法進(jìn)行序列化
  • 數(shù)據(jù)中如果存在循環(huán)引用桩匪,則不能進(jìn)行序列化打瘪,因?yàn)樾蛄谢瘯r(shí)會(huì)進(jìn)行遞歸
  • Pointer 序列化之后是其指向的值或者是 nil

還需要注意的是:只有 struct 中支持導(dǎo)出的 field 才能被 JSON package 序列化,即首字母大寫的 field傻昙。

反序列化

反序列化函數(shù)是 Unmarshal 闺骚,其函數(shù)簽名如下:

func Unmarshal(data []byte, v interface{}) error

如果要進(jìn)行反序列化,我們首先需要?jiǎng)?chuàng)建一個(gè)可以接受序列化數(shù)據(jù)的 Go struct:

var m Message
err := json.Unmarshal(b, &m)

JSON 對(duì)象一般都是小寫表示妆档,Marshal 之后 JSON 對(duì)象的首字母依然是大寫僻爽,如果序列化之后名稱想要改變?nèi)绾螌?shí)現(xiàn),答案就是 struct tags贾惦。

Struct Tag

Struct tag 可以決定 Marshal 和 Unmarshal 函數(shù)如何序列化和反序列化數(shù)據(jù)胸梆。

指定 JSON filed name

JSON object 中的 name 一般都是小寫,我們可以通過(guò) struct tag 來(lái)實(shí)現(xiàn):

type MyStruct struct {
 SomeField string `json:"some_field"`
}

SomeField 序列化之后會(huì)變成 some_field须板。

指定 field 是 empty 時(shí)的行為

使用 omitempty 可以告訴 Marshal 函數(shù)如果 field 的值是對(duì)應(yīng)類型的 zero-value碰镜,那么序列化之后的 JSON object 中不包含此 field:

type MyStruct struct {
 SomeField string `json:"some_field,omitempty"`
}

m := MyStruct{}
b, err := json.Marshal(m) //{}

如果 SomeField == “” ,序列化之后的對(duì)象就是 {}逼纸。

跳過(guò) field

Struct tag “-” 表示跳過(guò)指定的 filed:

type MyStruct struct {
 SomeField string `json:"some_field"`
 Passwd string `json:"-"`
}
m := MyStruct{}
b, err := json.Marshal(m) //{"some_feild":""}

即序列化的時(shí)候不輸出洋措,這樣可以有效保護(hù)需要保護(hù)的字段不被序列化。

反序列化任意 JSON 數(shù)據(jù)

默認(rèn)的 JSON 只支持以下幾種 Go 類型:

  • bool for JSON booleans

  • float64 for JSON numbers

  • string for JSON strings

  • nil for JSON null

在序列化之前如果不知道 JSON 數(shù)據(jù)格式杰刽,我們使用 interface{} 來(lái)存儲(chǔ)菠发。interface {} 的作用詳見本博的其他文章。

有如下的數(shù)據(jù)格式:

b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

如果我們序列化之前不知道其數(shù)據(jù)格式贺嫂,我們可以使用 interface{} 來(lái)存儲(chǔ)我們的 decode 之后的數(shù)據(jù):

var f interface{}
err := json.Unmarshal(b, &f)

反序列化之后 f 應(yīng)該是像下面這樣:

f = map[string]interface{}{
 "Name": "Wednesday",
 "Age":  6,
 "Parents": []interface{}{
 "Gomez",
 "Morticia",
 },
}

key 是 string滓鸠,value 是存儲(chǔ)在 interface{} 內(nèi)的。想要獲得 f 中的數(shù)據(jù)第喳,我們首先需要進(jìn)行 type assertion糜俗,然后通過(guò) range 迭代獲得 f 中所有的 key :

m := f.(map[string]interface{})
for k, v := range m {
 switch vv := v.(type) {
 case string:
 fmt.Println(k, "is string", vv)
 case float64:
 fmt.Println(k, "is float64", vv)
 case []interface{}:
 fmt.Println(k, "is an array:")
 for i, u := range vv {
 fmt.Println(i, u)
 }
 default:
 fmt.Println(k, "is of a type I don't know how to handle")
 }
}

反序列化對(duì) slice、map曲饱、pointer 的處理

我們定義一個(gè) struct 繼續(xù)對(duì)上面例子中的 b 進(jìn)行反序列化:

type FamilyMember struct {
 Name    string
 Age     int
 Parents []string
}

var m FamilyMember
err := json.Unmarshal(b, &m)

這個(gè)例子是能夠正常工作的悠抹,你一定也注意到了,struct 中包含一個(gè) slice Parents 扩淀,slice 默認(rèn)是 nil楔敌,之所以反序列化可以正常進(jìn)行就是因?yàn)?Unmarshal 在序列化時(shí)進(jìn)行了對(duì) slice Parents 做了初始化,同理驻谆,對(duì) map 和 pointer 都會(huì)做類似的工作卵凑,比如序列化如果 Pointer 不是 nil 首先進(jìn)行 dereference 獲得其指向的值庆聘,然后再進(jìn)行序列化,反序列化時(shí)首先對(duì) nil pointer 進(jìn)行初始化

Stream JSON

除了 marshal 和 unmarshal 函數(shù)勺卢,Go 還提供了 Decoder 和 Encoder 對(duì) stream JSON 進(jìn)行處理伙判,常見 request 中的 Body、文件等:

jsonFile, err := os.Open("post.json")
if err != nil {
 fmt.Println("Error opening json file:", err)
 return
}

defer jsonFile.Close()
decoder := json.NewDecoder(jsonFile)
for {
 var post Post
 err := decoder.Decode(&post)
 if err == io.EOF {
 break
 }

 if err != nil {
 fmt.Println("error decoding json:", err)
 return
 }

 fmt.Println(post)
}

嵌入式 struct 的序列化

Go 支持對(duì) nested struct 進(jìn)行序列化和反序列化:

type App struct {
 Id string `json:"id"`
}

type Org struct {
 Name string `json:"name"`
}

type AppWithOrg struct {
 App
 Org
}

func main() {
 data := []byte(`
 {
 "id": "k34rAT4",
 "name": "My Awesome Org"
 }
 `)

 var b AppWithOrg

 json.Unmarshal(data, &b)
 fmt.Printf("%#v", b)

 a := AppWithOrg{
 App: App{
 Id: "k34rAT4",
 },
 Org: Org{
 Name: "My Awesome Org",
 },
 }
 data, _ = json.Marshal(a)
 fmt.Println(string(data))
}

Nested struct 雖然看起來(lái)有點(diǎn)怪異黑忱,有些時(shí)候它將非常有用宴抚。

自定義序列化函數(shù)

Go JSON package 中定了兩個(gè) Interface MarshalerUnmarshaler ,實(shí)現(xiàn)這兩個(gè) Interface 可以讓你定義的 type 支持序列化操作杨何。

錯(cuò)誤處理

總是記得檢查序列或反序列化的錯(cuò)誤酱塔,可以讓你的程序更健壯,而不是在出錯(cuò)之后帶著錯(cuò)誤繼續(xù)執(zhí)行下去危虱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市唐全,隨后出現(xiàn)的幾起案子埃跷,更是在濱河造成了極大的恐慌,老刑警劉巖邮利,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弥雹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡延届,警方通過(guò)查閱死者的電腦和手機(jī)剪勿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)方庭,“玉大人厕吉,你說(shuō)我怎么就攤上這事⌒的睿” “怎么了头朱?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)龄减。 經(jīng)常有香客問(wèn)我项钮,道長(zhǎng),這世上最難降的妖魔是什么希停? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任烁巫,我火速辦了婚禮,結(jié)果婚禮上宠能,老公的妹妹穿的比我還像新娘亚隙。我一直安慰自己,他們只是感情好棍潘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布恃鞋。 她就那樣靜靜地躺著崖媚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恤浪。 梳的紋絲不亂的頭發(fā)上畅哑,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音水由,去河邊找鬼荠呐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛砂客,可吹牛的內(nèi)容都是我干的泥张。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鞠值,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼媚创!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起彤恶,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钞钙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后声离,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芒炼,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年术徊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了本刽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赠涮,死狀恐怖子寓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情世囊,我是刑警寧澤别瞭,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站株憾,受9級(jí)特大地震影響蝙寨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嗤瞎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一墙歪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贝奇,春花似錦虹菲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浪漠。三九已至,卻和暖如春霎褐,著一層夾襖步出監(jiān)牢的瞬間址愿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工冻璃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留响谓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓省艳,卻偏偏與公主長(zhǎng)得像娘纷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子跋炕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容