在Go中使用JSON。帶例子的指南

語言: CN / TW / HK

Golang(又稱Go)是一種靜態型別的編譯程式語言,具有類似C語言的語法。Go為通用程式設計提供了一個最小的語法,只有25個關鍵詞。

現在,程式設計師使用Go來構建開發者工具、雲端計算工具、CLI程式以及桌面和網路應用。Go在構建高效能軟體系統方面非常受歡迎,在這些系統中,併發性起著關鍵作用。

Go開發人員經常需要處理JSON內容。例如,我們經常要讀取JSON檔案來填充Go物件,並從現有的Go物件中寫入JSON檔案。像其他現代程式語言一樣,Go提供了一個標準庫模組來處理JSON結構。

在本教程中,我將通過實際例子解釋如何在Go中處理JSON。此外,我還將解釋一些高階概念,如自定義JSON編碼和解碼。

Go編碼/json包

Go提供了encoding/json包,通過標準庫的編碼名稱空間處理JSON內容。encoding/json包提供了API函式,用於從Go物件生成JSON文件--以及從JSON文件中填充Go物件。此外,它還允許你定製JSON到Go和Go到JSON的翻譯過程。

JSON規範同時支援格式化和內聯(minified)文件。因此,Go encoding/json包可以讓開發者同時生成格式化和最小化的JSON文件。

編碼。將 Go 物件轉換為 JSON

什麼是Go中的marshaling?

將Go物件編碼為JSON格式被稱為marshaling。我們可以使用Marshal 函式來將 Go 物件轉換為 JSON。Marshal 函式的語法如下。

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

```

它接受一個空介面。換句話說,你可以向該函式提供任何Go資料型別--整數、浮點、字串、結構體、地圖等--因為所有Go資料型別定義都可以用空介面表示。它返回兩個值:一個編碼JSON的位元組片和error

裝載簡單物件

如上所述,我們可以用原始的Go資料型別生成JSON。例如,你可以將Go字串轉換為JSON字串。

但由於轉換基元在現實世界的軟體開發專案中沒有幫助,讓我們從轉換一些簡單物件開始。下面的程式碼片斷將從一個map資料結構中編碼JSON。

``` package main import ( "fmt" "encoding/json" ) func main() { fileCount := map[string]int{ "cpp": 10, "js": 8, "go": 10, } bytes, _ := json.Marshal(fileCount) fmt.Println(string(bytes)) }

```

這裡我們使用string() ,將位元組轉換為字串。Go將map資料結構編碼為JSON鍵值物件。一旦你執行上述程式碼,你將得到如下所示的輸出。

The output when converting a Go map data struct to a JSON key-value object

你也可以從一個結構中對JSON進行編碼,如下面的示例程式碼所示。

``` package main import ( "fmt" "encoding/json" ) type Book struct { Title string Author string Year int } func main() { myBook := Book{"Hello Golang", "John Mike", 2021} bytes, _ := json.Marshal(myBook) fmt.Println(string(bytes)) }

```

在這裡,我們必須以大寫的英文字母開始結構欄位名,以使這些欄位可以匯出到其他軟體包。如果你的結構包含一個以小寫字母開頭的欄位,那麼編碼/json包在編碼過程中不會包含這個特定的欄位,也不會出現任何錯誤。

上述程式碼將輸出以下JSON結構。

``` {"Title":"Hello Golang","Author":"John Mike","Year":2021}

```

對複雜物件進行編碼

在前面的例子中,我們從Go物件中編碼了JSON,比如簡單的map和structs。如果你試圖對整數陣列、字串陣列和原始變數進行編碼,Go將為這些元素產生簡單的JSON結構。

但大多數時候,我們必須從Go程式中的複雜物件中生成JSON檔案,如產品列表、產品詳情和各種巢狀資料記錄。

首先,讓我們從一個產品列表中對JSON進行編碼。請看下面的示例程式碼。

``` package main import ( "fmt" "encoding/json" ) type Seller struct { Id int Name string CountryCode string } type Product struct { Id int Name string Seller Seller Price int } func main() { products := []Product{ Product { Id: 50, Name: "Writing Book", Seller: Seller {1, "ABC Company", "US"}, Price: 100, }, Product { Id: 51, Name: "Kettle", Seller: Seller {20, "John Store", "DE"}, Price: 500, }, } bytes, _ := json.Marshal(products) fmt.Println(string(bytes)) }

```

上面的程式碼初始化了一個有兩個專案的產品列表。Product 結構有一個Seller 結構作為巢狀物件--所有的產品都放在一個產品片中。接下來,我們將最終的產品列表傳送到Marshal 函式,將其編碼為JSON結構。

一旦你執行上述程式碼片斷,你將得到以下輸出。

``` [{"Id":50,"Name":"Writing Book","Seller":{"Id":1,"Name":"ABC Company","CountryCode":"US"},"Price":100},{"Id":51,"Name":"Kettle","Seller":{"Id":20,"Name":"John Store","CountryCode":"DE"},"Price":500}]

```

正如你所看到的,Go可以從任何複雜的Go資料結構中編碼JSON。但是現在,當我們看上面的輸出時,我們有兩個問題。

  • 輸出的JSON結構的鍵總是以大寫的英文字母開始--我們怎樣才能重新命名JSON欄位?
  • 當我們對大型複雜的結構進行編碼時,輸出結果變得簡直無法閱讀--我們如何才能美化JSON的輸出?

Go encoding/json軟體包通過額外的庫功能回答了上述問題。

集合功能

Go提供了幾個功能,通過額外的API函式和結構標籤改善和定製JSON輸出。

重新命名欄位

你必須以大寫英文字母開始結構欄位的宣告,以便讓JSON包訪問它們。因此,你將永遠得到大寫的英文字母作為JSON鍵。Go編碼/json包允許開發人員通過JSON結構標籤隨意重新命名JSON欄位。

下面的程式碼片斷對產品物件的JSON進行編碼,並使用蛇形大小寫的JSON鍵。

`` package main import ( "fmt" "encoding/json" ) type Seller struct { Id intjson:"id"Name stringjson:"name"CountryCode stringjson:"country_code"} type Product struct { Id intjson:"id"Name stringjson:"name"Seller Sellerjson:"seller"Price intjson:"price"` } func main() { book := Product{ Id: 50, Name: "Writing Book", Seller: Seller {1, "ABC Company", "US"}, Price: 100, } bytes, _ := json.Marshal(book) fmt.Println(string(bytes)) }

```

正如你所看到的,上面的程式碼使用結構標籤來重新命名每個匯出的欄位。結構標籤不是JSON編碼過程中的必選元素--它是一個可選的元素,在JSON編碼過程中重新命名一個特定的結構欄位。

一旦你執行上述程式碼,你將得到以下輸出。

``` {"id":50,"name":"Writing Book","seller":{"id":1,"name":"ABC Company","country_code":"US"},"price":100}

```

生成具有縮排功能的JSON(pretty-print)

Marshal 函式生成最小的內聯JSON內容,沒有任何格式化。你可以使用MarshalIndent 函式來編碼具有縮排功能的可讀JSON。下面的程式碼為上一個結構物件生成了prettified JSON。

`` package main import ( "fmt" "encoding/json" ) type Seller struct { Id intjson:"id"Name stringjson:"name"CountryCode stringjson:"country_code"} type Product struct { Id intjson:"id"Name stringjson:"name"Seller Sellerjson:"seller"Price intjson:"price"` } func main() { book := Product{ Id: 50, Name: "Writing Book", Seller: Seller {1, "ABC Company", "US"}, Price: 100, } bytes, _ := json.MarshalIndent(book, "", "\t") fmt.Println(string(bytes)) }

```

一旦你執行上述程式碼,它將打印出一個格式化的JSON結構,如下圖所示。

The formatted JSON structure output

這裡我們使用Tab字元(\t)進行縮排。你可以根據你的要求使用四個空格、兩個空格、八個空格等進行格式化。

忽略JSON輸出中的特定欄位

早些時候,我們使用結構標籤來重新命名JSON鍵。我們也可以使用結構標籤來省略特定欄位。如果我們使用json:”-” 作為標籤,相關的結構欄位將不會被用於編碼。另外,如果我們在結構標籤名稱字串中使用,omitempty ,如果相關欄位的值為空,則不會被用於編碼。

下面的程式碼省略了產品識別符號的編碼。此外,它還從輸出中省略了空的國家程式碼值。

`` package main import ( "fmt" "encoding/json" ) type Seller struct { Id intjson:"id"Name stringjson:"name"CountryCode stringjson:"country_code,omitempty"} type Product struct { Id intjson:"-"Name stringjson:"name"Seller Sellerjson:"seller"Price intjson:"price"` } func main() { products := []Product{ Product { Id: 50, Name: "Writing Book", Seller: Seller {Id: 1, Name: "ABC Company", CountryCode: "US"}, Price: 100, }, Product { Id: 51, Name: "Kettle", Seller: Seller {Id: 20, Name: "John Store"}, Price: 500, }, } bytes, _ := json.MarshalIndent(products, "", "\t") fmt.Println(string(bytes)) }

```

上述程式碼產生以下輸出。注意,它不包含產品識別符號和第二項的國家程式碼鍵。

The output omitting the product identifier and the second object's country code key

解除偽裝。將JSON轉換為Go物件

在Go環境中,JSON文件的解碼過程被稱為unmarshaling。我們可以使用Unmarshal 函式來將JSON轉換為Go物件。Unmarshal 函式的語法如下。

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

```

它接受兩個引數:一個JSON內容的位元組片和一個空的介面引用。如果在解碼過程中出現錯誤,該函式可能會返回一個錯誤。Unmarshal 函式不建立和返回Go物件,所以我們必須傳遞一個引用來儲存解碼後的內容。

解除對簡單JSON結構的封存

類似於JSON的marshaling,我們可以解封Go的原始資料型別,如整數、字串、浮點數和布林。但同樣的,由於原始資料解密在大多數軟體開發專案中沒有真正的使用案例,我們先將下面的鍵值結構解碼為Go結構。

``` { "width": 500, "height": 200, "title": "Hello Go!" }

```

下面的程式碼將上述JSON結構解碼成一個結構。

`` package main import ( "fmt" "encoding/json" ) type Window struct { Width intjson:"width"Height intjson:"height"Title stringjson:"title"} func main() { jsonInput :={ "width": 500, "height": 200, "title": "Hello Go!" }` var window Window err := json.Unmarshal([]byte(jsonInput), &window)

if err != nil {
    fmt.Println("JSON decode error!")
    return
}

fmt.Println(window) // {500 200 Hello Go!}

}

```

jsonInput 變數將JSON內容作為一個多行字串儲存。因此,在用byte[]() 型別轉換語法將其傳遞給Unmarshal 函式之前,我們必須將其轉換為位元組片狀。在這裡,我們檢查了返回的錯誤物件的值以檢測解析錯誤。

在這種情況下,上述JSON標籤是可選的,因為Go編碼/json包通常將JSON欄位對映為結構欄位,並進行不區分大小寫的匹配。

同樣地,我們也可以將JSON結構解碼為Go對映。請看下面的示例程式碼。

`` package main import ( "fmt" "encoding/json" ) func main() { jsonInput :={ "apples": 10, "mangos": 20, "grapes": 20 }` var fruitBasket map[string] int err := json.Unmarshal([]byte(jsonInput), &fruitBasket)

if err != nil {
    fmt.Println("JSON decode error!")
    return
}

fmt.Println(fruitBasket) // map[apples:10 grapes:20 mangos:20]

}

```

解除複雜資料結構的束縛

之前的解密例子向你展示瞭如何對簡單的JSON結構進行解密。我們在軟體開發專案中經常要對複雜的巢狀JSON結構進行解碼。下面的例子演示瞭如何從一個JSON格式的產品列表中填充Go物件。

`` package main import ( "fmt" "encoding/json" ) type Product struct { Id intjson:"id"Name stringjson:"name"Seller struct { Id intjson:"id"Name stringjson:"name"CountryCode stringjson:"country_code"}json:"seller"Price intjson:"price"} func main() { jsonInput :=[ { "id":50, "name":"Writing Book", "seller":{ "id":1, "name":"ABC Company", "country_code":"US" }, "price":100 }, { "id":51, "name":"Kettle", "seller":{ "id":20, "name":"John Store", "country_code":"DE" }, "price":500 }] ` var products []Product err := json.Unmarshal([]byte(jsonInput), &products)

if err != nil {
    fmt.Println("JSON decode error!")
    return
}

fmt.Println(products)
// [{50 Writing Book {1 ABC Company US} 100} {51 Kettle {20 John Store DE} 500}]

}

```

如上面的程式碼所示,我們需要先通過檢查JSON輸入來定義一個結構。當你處理大型複雜的JSON結構時,這個過程是一個耗時的任務。因此,你可以使用JSON-to-Go這樣的線上工具,根據JSON輸入建立結構定義。

也有一種方法可以在Go中不建立結構而訪問解析後的值。你可以通過為JSON物件建立map[string]interface{} 型別物件來動態訪問任何值,但這種方法會導致非常複雜、質量較差的原始碼。

不過,你可以通過下面的示例程式碼檢查動態JSON值的訪問,以達到實驗目的。但是,在沒有建立適當的Go結構的情況下,不要在生產軟體系統中使用這種方法,因為它會產生複雜和難以測試的程式碼。

`` package main import ( "fmt" "encoding/json" ) func main() { jsonInput :=[ { "id":50, "name":"Writing Book", "seller":{ "id":1, "name":"ABC Company", "country_code":"US" }, "price":100 }, { "id":51, "name":"Kettle", "seller":{ "id":20, "name":"John Store", "country_code":"DE" }, "price":500 }] ` var objMap []map[string]interface{} err := json.Unmarshal([]byte(jsonInput), &objMap)

if err != nil {
    fmt.Println("JSON decode error!")
    return
}

fmt.Println("Price of the second product:", objMap\[1\]["price"])

}

```

上面的程式碼在沒有Go結構的情況下列印了第二個產品專案的價格。

從檔案系統中讀取JSON檔案

我們在前面的例子中使用了硬編碼的JSON字串進行演示。但是,在實踐中,我們從不同的來源載入JSON字串:從檔案系統,通過網際網路,通過本地網路位置,等等。大多數程式設計師通常使用JSON格式來儲存檔案系統上的配置細節。

讓我們寫一些Go程式碼,從檔案中讀取和解碼JSON資料,並將其轉換成Go物件。首先,建立一個名為config.json 的檔案並輸入以下內容。

``` { "timeout": 50.30, "pluginsPath": "~/plugins/", "window": { "width": 500, "height": 200, "x": 500, "y": 500 } }

```

現在,執行以下程式碼,將上述JSON檔案解碼為合適的結構。

``` package main import ( "fmt" "io/ioutil" "encoding/json" ) type Config struct { Timeout float32 PluginsPath string Window struct { Width int Height int X int Y int } } func main() {

bytes, err := ioutil.ReadFile("config.json")

if err != nil {
    fmt.Println("Unable to load config file!")
    return
}

var config Config
err = json.Unmarshal(bytes, &config)

if err != nil {
    fmt.Println("JSON decode error!")
    return
}

fmt.Println(config) // {50.3 ~/plugins/ {500 200 500 500}}

}

```

上面的程式碼用ioutil.ReadFile 函式將JSON檔案內容讀成位元組,並將資料記錄解碼到Config 結構中。

將JSON檔案寫到檔案系統中

在前面的例子中,我們通過Println 函式將編碼後的JSON內容列印到控制檯。現在我們可以通過ioutil.WriteFile 函式將這些JSON字串儲存為檔案,如下圖所示。

`` package main import ( "io/ioutil" "encoding/json" ) type Window struct { Width intjson:"width"Height intjson:"height"X intjson:"x"Y intjson:"y"} type Config struct { Timeout float32json:"timeout"PluginsPath stringjson:"pluginsPath"Window Windowjson:"window"` } func main() { config := Config { Timeout: 40.420, PluginsPath: "~/plugins/etc", Window: Window {500, 200, 20, 20}, } bytes, _ := json.MarshalIndent(config, "", " ") ioutil.WriteFile("config.json", bytes, 0644) }

```

上面的程式碼通過將config 物件編碼為JSON物件來寫入config.json 。這裡我們使用了兩個空格來縮排,並通過使用結構標籤將結構欄位轉換為駱駝大寫的JSON鍵。

自定義抓取和解除抓取

Go json包非常靈活,它提供了覆蓋編碼和解碼過程的功能。當您在編碼/解碼過程中需要將JSON資料記錄從一種格式轉換為另一種格式時,這些功能很有幫助。

自定義編排

假設你正在用Go編寫一個聯絡人管理應用程式,你提供一個功能給所有使用者下載JSON格式的聯絡人列表。假設由於安全策略的原因,你不能讓非管理員使用者看到所有的電子郵件ID。在這種情況下,你可以通過Go json包中的自定義marshaling功能來定製JSON編碼過程,如下所示。

`` package main import ( "fmt" "encoding/json" "strings" ) type Person struct { Name stringjson:"name"Age intjson:"age"Email stringjson:"-"} func main() { persons := []Person { Person {"James Henrick", 25, "[email protected]"}, Person {"David Rick", 30, "[email protected]"}, } bytes, _ := json.MarshalIndent(persons, "", " ") fmt.Println(string(bytes)) } func (p *Person) MarshalJSON() ([]byte, error) { type PersonAlias Person return json.Marshal(&struct{ *PersonAlias Email stringjson:"email"` }{ PersonAlias: (PersonAlias)(p), Email: strings.Repeat("", 4) + "@mail.com", // alter email }) }

```

上面的程式碼輸出了所有的聯絡方式,但由於安全策略的原因,它改變了原始的電子郵件地址。請注意,這裡我們需要從Person 型別中建立另一個型別(alias),因為如果我們試圖為原來的Person 型別呼叫Marshal 函式,由於編碼過程的遞迴實現,程式將進入無限迴圈。一旦你執行上述程式碼片斷,你會看到如下的輸出。

The output for our custom marshaling example

自定義解密

Go json包也可以讓你自定義JSON解碼過程。假設你需要處理一個JSON配置檔案,並需要在解碼過程中轉換一些值。假設一個配置欄位說的是開爾文的溫度,但你需要用攝氏度來儲存具體數值。

請看下面的程式碼,它實現了自定義解密。

`` package main import ( "fmt" "encoding/json" ) type Config struct { FunctionName string Temperature float32 } func main() { jsonInput :={ "functionName": "triggerModule", "temperature": 4560.32 }` var config Config err := json.Unmarshal([]byte(jsonInput), &config)

if err != nil {
    fmt.Println("JSON decode error!")
    return
}

fmt.Println(config) // {triggerModule 4287.17}

} func (c Config) UnmarshalJSON(data []byte) error { type ConfigAlias Config tmp := struct { Temperature float32 ConfigAlias }{ ConfigAlias: (*ConfigAlias)(c), } if err := json.Unmarshal(data, &tmp); err != nil { return err } c.Temperature = tmp.Temperature - 273.15 return nil }

```

上述程式碼通過將temperature 欄位的值從開爾文轉換為攝氏度來解讀JSON。這裡我們還需要建立另一個型別(alias),以避免無限迴圈,這與自定義的marshaling類似。

總結

在本教程中,我們通過實際例子討論了Go中的JSON編碼(marshaling)和解碼(unmarshaling)。JSON是一種廣泛使用的、獨立於語言的編碼格式。因此,幾乎所有基於Go的網路框架都在內部處理JSON編碼和解碼。例如,GinHTTP框架允許你使用json包直接向API函式傳送一個結構體,而不需要手動進行marshaling。

然而,你可以在你的Go程式中使用Go json包,而不需要消耗第三方庫,因為json包是標準庫的一部分。另外,Go json包還有一些更快的替代品(根據這個基準)。但是,json包是標準庫的一部分,由Go開發團隊維護。因此,Go開發團隊會在即將釋出的版本中提高編碼/json包的效能。

The postUsing JSON in Go:有例子的指南出現在LogRocket部落格上。