31.Go JSON

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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔗喂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子高帖,更是在濱河造成了極大的恐慌缰儿,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件散址,死亡現(xiàn)場離奇詭異乖阵,居然都是意外死亡,警方通過查閱死者的電腦和手機预麸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門瞪浸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吏祸,你說我怎么就攤上這事默终。” “怎么了犁罩?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵齐蔽,是天一觀的道長。 經(jīng)常有香客問我床估,道長含滴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任丐巫,我火速辦了婚禮谈况,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘递胧。我一直安慰自己碑韵,他們只是感情好,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布缎脾。 她就那樣靜靜地躺著祝闻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遗菠。 梳的紋絲不亂的頭發(fā)上联喘,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天华蜒,我揣著相機與錄音,去河邊找鬼豁遭。 笑死叭喜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蓖谢。 我是一名探鬼主播捂蕴,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼闪幽!你這毒婦竟也來了启绰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤沟使,失蹤者是張志新(化名)和其女友劉穎委可,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腊嗡,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡着倾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了燕少。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卡者。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖客们,靈堂內(nèi)的尸體忽然破棺而出崇决,到底是詐尸還是另有隱情,我是刑警寧澤底挫,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布恒傻,位于F島的核電站,受9級特大地震影響建邓,放射性物質(zhì)發(fā)生泄漏盈厘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一官边、第九天 我趴在偏房一處隱蔽的房頂上張望沸手。 院中可真熱鬧,春花似錦注簿、人聲如沸契吉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捐晶。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間租悄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工恩袱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泣棋,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓畔塔,卻偏偏與公主長得像潭辈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子澈吨,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

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

  • 前言 本文主要根據(jù)Go語言Json包[1]把敢、官方提供的Json and Go[2]和go-and-json[3]整...
    TOMOCAT閱讀 164評論 0 0
  • 本文是基于 Go 官方和 https://eager.io/blog/go-and-json/ 進行翻譯整理的 J...
    小蝸牛爬樓梯閱讀 499評論 0 0
  • JSON(JavaScript Object Notation,JS對象標記)是一種比XML更為輕量的【數(shù)據(jù)交換格...
    JunChow520閱讀 897評論 0 1
  • 參考:https://sanyuesha.com/2018/05/07/go-json/[https://sany...
    天空藍雨閱讀 4,226評論 0 3
  • fmt格式化字符串 格式:%[旗標][寬度][.精度][arg索引]動詞旗標有以下幾種:+: 對于數(shù)值類型總是輸出...
    皮皮v閱讀 1,096評論 0 3