這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。Go 語言是靜態類型語言,雖然它也可以表現出動態類型,但是使用一個嵌套的 `map[string]interface{}` 在那裡亂叫會讓代碼變得特別醜。通過掌握語言的靜態特性,我們可以做的更好。通過同一通道交換多種資訊的時候,我們經常需要 JSON 具有動態,或者更合適的參數內容。首先,讓我們來討論一下訊息封裝(message envelopes),JSON 在這裡看起來就像這樣:```json{"type": "this part tells you how to interpret the message","msg": ...the actual message is here, in some kind of json...}```## 通過不同的訊息類型產生 JSON通過 `interface{}`,我們可以很容易的將資料結構編碼成為獨立封裝的,具有多種類型的訊息體的 JSON 資料。為了產生下面的 JSON :```json{"type": "sound","msg": {"description": "dynamite","authority": "the Bruce Dickinson"}}``````json{"type": "cowbell","msg": {"more": true}}```我們可以使用這些 Go 類型:```gopackage mainimport ("encoding/json""fmt""log")type Envelope struct {Type stringMsg interface{}}type Sound struct {Description stringAuthority string}type Cowbell struct {More bool}func main() {s := Envelope{Type: "sound",Msg: Sound{Description: "dynamite",Authority: "the Bruce Dickinson",},}buf, err := json.Marshal(s)if err != nil {log.Fatal(err)}fmt.Printf("%s\n", buf)c := Envelope{Type: "cowbell",Msg: Cowbell{More: true,},}buf, err = json.Marshal(c)if err != nil {log.Fatal(err)}fmt.Printf("%s\n", buf)}```輸出的結果是:```json{"Type":"sound","Msg":{"Description":"dynamite","Authority":"the Bruce Dickinson"}}{"Type":"cowbell","Msg":{"More":true}}```這些並沒有什麼特殊的。## 解析 JSON 到動態類型如果你想將上面的 JSON 對象解析成為一個 `Envelope` 類型的對象,最終你會將 `Msg` 欄位解析成為一個 `map[string]interface{}`。 這種方式不是很好用,會使你後悔你的選擇。```gopackage mainimport ("encoding/json""fmt""log")const input = `{"type": "sound","msg": {"description": "dynamite","authority": "the Bruce Dickinson"}}`type Envelope struct {Type stringMsg interface{}}func main() {var env Envelopeif err := json.Unmarshal([]byte(input), &env); err != nil {log.Fatal(err)}// for the love of Gopher DO NOT DO THISvar desc string = env.Msg.(map[string]interface{})["description"].(string)fmt.Println(desc)}```輸出:```dynamite```## 明確的解析方式就像前面說的,我推薦修改 `Envelope` 類型,就像這樣:```gotype Envelope {Type stringMsg *json.RawMessage}```[`json.RawMessage`](http://golang.org/pkg/encoding/json/#RawMessage) 非常有用,它可以讓你延遲解析相應的 JSON 資料。它會將未處理的資料存放區為 `[]byte`。這種方式可以讓你顯式控制 `Msg` 的解析。從而延遲到擷取到 `Type` 的值之後,依據 `Type` 的值進行解析。這種方式不好的地方在於你需要先明確解析 `Msg`,或者你需要單獨分為 `EnvelopeIn` 和 `EnvelopeOut` 兩種類型,其中 `EnvelopeOut` 仍然有 `Msg interface{}`。## 結合 `*json.RawMessage` 和 `interface{}` 的優點那麼如何將上述兩者好的一面結合起來呢?通過在 `interface{}` 欄位中放入 `*json.RawMessage`!```gopackage mainimport ("encoding/json""fmt""log")const input = `{"type": "sound","msg": {"description": "dynamite","authority": "the Bruce Dickinson"}}`type Envelope struct {Type stringMsg interface{}}type Sound struct {Description stringAuthority string}func main() {var msg json.RawMessageenv := Envelope{Msg: &msg,}if err := json.Unmarshal([]byte(input), &env); err != nil {log.Fatal(err)}switch env.Type {case "sound":var s Soundif err := json.Unmarshal(msg, &s); err != nil {log.Fatal(err)}var desc string = s.Descriptionfmt.Println(desc)default:log.Fatalf("unknown message type: %q", env.Type)}}```輸出:```dynamite```## 如何把所有資料都放在最外層(頂層)雖然我極其推薦你將動態可變的部分放在一個單獨的 key 下面,但是有時你可能需要處理一些預先存在的資料,它們並沒有用這樣的方式進行格式化。如果可以的話,請使用文章前面提到的風格。```json{"type": "this part tells you how to interpret the message",...the actual message is here, as multiple keys...}```我們可以通過解析兩次資料的方式來解決。```gopackage mainimport ("encoding/json""fmt""log")const input = `{"type": "sound","description": "dynamite","authority": "the Bruce Dickinson"}`type Envelope struct {Type string}type Sound struct {Description stringAuthority string}func main() {var env Envelopebuf := []byte(input)if err := json.Unmarshal(buf, &env); err != nil {log.Fatal(err)}switch env.Type {case "sound":var s struct {EnvelopeSound}if err := json.Unmarshal(buf, &s); err != nil {log.Fatal(err)}var desc string = s.Descriptionfmt.Println(desc)default:log.Fatalf("unknown message type: %q", env.Type)}}``````dynamite```
via: http://eagain.net/articles/go-dynamic-json/
作者:Tommi Virtanen 譯者:jliu666 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
1036 次點擊