如何使用 Go 語言解析 JSON
當靜態程式語言使用到 JSON 的時候,總是會有些費力。一方面,JSON 資料可能是任何形式的,從一個簡單的數字,到一個複雜的包含內嵌物件的陣列。當使用 Go 語言的時候,這意味著你要將這些變化多端的 JSON 資料放入結構化的變數中去。
幸運地是,Go 盡力嘗試幫我們降低編碼難度,為我們提供了許多方式來解析 JSON 資料。
概述
Go 標準庫的 json 包為我們提供了我們想要的功能。對於任意的 JSON 字串,標準的解析方法如下:
import "encoding/json" //... // ... myJsonString := `{"some":"json"}` // `&myStoredVariable` is the address of the variable we want to store our // parsed data in json.Unmarshall([]byte(myJsonString), &myStoredVariable) //...
本文中我們要討論的是,對於myStoredVariable
變數的型別,你可以有哪些不同的選擇,以及決定何時採用何種型別。
在處理 JSON 資料時,你會遇到兩種情況:
- 結構化資料
- 非結構化資料
結構化資料
我們先從結構化資料開始,因為它們處理起來相對容易一些。對於這類資料我們需要提前知曉其結構。比如,你有一個Bird
物件,每種鳥都有species
欄位和一個description
欄位:
{ "species": "pigeon", "decription": "likes to perch on rocks" }
讀取此類資料,只要建立一個struct
結構體,作為你想要解析的資料的映象。對於這個例子,我們會建立一個包含Species
和Description
欄位的Bird
結構體:
type Bird struct { Species string Description string }
然後將資料解析出來,如下:
birdJson := `{"species": "pigeon","description": "likes to perch on rocks"}` var bird Bird json.Unmarshal([]byte(birdJson), &bird) fmt.Printf("Species: %s, Description: %s", bird.Species, bird.Description) //Species: pigeon, Description: likes to perch on rocks
轉換時,Go 不區分名稱大小寫問題,使用 JSON 屬性與欄位都轉為小寫後相同作為對映依據。所以,Bird
結構的Species
欄位會對映到名為species
,或者Species
或者sPeCiEs
的 JSON 屬性。
JSON 陣列
當遇到一個Bird
陣列的時候,又會發生什麼呢?
[ { "species": "pigeon", "decription": "likes to perch on rocks" }, { "species":"eagle", "description":"bird of prey" } ]
既然這個陣列的每個元素都是一個Bird
,你可以建立一個Bird
型別的陣列來解析它:
birdJson := `[{"species":"pigeon","decription":"likes to perch on rocks"},{"species":"eagle","description":"bird of prey"}]` var birds []Bird json.Unmarshal([]byte(birdJson), &birds) fmt.Printf("Birds : %+v", birds) //Birds : [{Species:pigeon Description:} {Species:eagle Description:bird of prey}]
巢狀物件
現在,考慮這種情況,Bird
有一個Dimensions
屬性,用來描述它的Height
(高度)和Length
(身長):
{ "species": "pigeon", "decription": "likes to perch on rocks", "dimensions": { "height": 24, "width": 10 } }
和上一個例子類似,我們需要將這個新的問題物件對映到我們的 Go 程式碼中。在機構體中加入一個內嵌的dimensions
欄位,讓我們先申明一個dimensions
的結構體型別:
type Dimensions struct { Height int Width int }
然後,Bird
結構體可以包含一個Dimensions
欄位:
type Bird struct { Species string Description string Dimensions Dimensions }
如此就能用與之前一樣的方法進行資料解析了:
birdJson := `{"species":"pigeon","description":"likes to perch on rocks", "dimensions":{"height":24,"width":10}}` var birds Bird json.Unmarshal([]byte(birdJson), &birds) fmt.Printf(bird) // {pigeon likes to perch on rocks {24 10}}
自定義屬性名稱
前面提到過 Go 會進行大小寫轉換來確定結構體欄位和 JSON 屬性的對映關係。不過很多時候,你會需要一個和 JSON 資料屬性不同名的欄位名稱。
{ "birdType": "pigeon", "what it does": "likes to perch on rocks" }
對於上面的 JSON 資料,我想讓birdType
屬性對映到 Go 程式碼中的Species
欄位。同時我也沒法給"what it does"
提供一個合適的欄位名稱。
為了解決這些情況,我們可以使用結構體欄位標籤:
type Bird struct { Species string `json:"birdType"` Description string `json:"what it does"` }
現在,我們可以明確地告訴程式碼,某個 JSON 屬性應該對映到哪個欄位上了。
birdJson := `{"birdType": "pigeon","what it does": "likes to perch on rocks"}` var bird Bird json.Unmarshal([]byte(birdJson), &bird) fmt.Println(bird) // {pigeon likes to perch on rocks}
非結構化資料
如果你遇到一些資料,它們的結構和屬性名都不確定,那麼你就無法使用結構體來解析這些資料了。取而代之地,你可以使用對映(maps)型別。考慮以下的 JSON 形式:
{ "birds": { "pigeon":"likes to perch on rocks", "eagle":"bird of prey" }, "animals": "none" }
我們沒法構建一個結構體來描述上面的資料,因為鳥兒的名字作為物件的鍵值總是可以變換,而這會導致資料結構的變化。
為了處理這種情況,我們建立一個字串對應空介面的 map:
birdJson := `{"birds":{"pigeon":"likes to perch on rocks","eagle":"bird of prey"},"animals":"none"}` var result map[string]interface{} json.Unmarshal([]byte(birdJson), &result) // The object stored in the "birds" key is also stored as // a map[string]interface{} type, and its type is asserted from // the interface{} type birds := result["birds"].(map[string]interface{}) for key, value := range birds { // Each value is an interface{} type, that is type asserted as a string fmt.Println(key, value.(string)) }
每個字串對應一個 JSON 屬性的名稱,interface{}
型別對應它的值,這個值可以是任意型別。在程式碼中,其資料型別通過對interface{}
進行斷言就可以獲取到。而且這些對映解析動作可以迭代執行,如此一來,可變數量的鍵通過一次迴圈中就能夠處理完成。