在Go中使用JSON(附demo)

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鍵值對象。一旦你運行上述代碼,你將得到如下所示的輸出。

你也可以從一個結構中對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 int `json:"id"`
    Name string `json:"name"`
    CountryCode string `json:"country_code"`
}
type Product struct {
    Id int `json:"id"`
    Name string `json:"name"`
    Seller Seller `json:"seller"`
    Price int `json:"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 int `json:"id"`
    Name string `json:"name"`
    CountryCode string `json:"country_code"`
}
type Product struct {
    Id int `json:"id"`
    Name string `json:"name"`
    Seller Seller `json:"seller"`
    Price int `json:"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結構,如下圖所示。

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

忽略JSON輸出中的特定字段

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

下面的代碼省略瞭產品標識符的編碼。此外,它還從輸出中省略瞭空的國傢代碼值。

package main
import (
    "fmt"
    "encoding/json"
)
type Seller struct {
    Id int `json:"id"`
    Name string `json:"name"`
    CountryCode string `json:"country_code,omitempty"`
}
type Product struct {
    Id int `json:"-"`
    Name string `json:"name"`
    Seller Seller `json:"seller"`
    Price int `json:"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))
}

上述代碼產生以下輸出。註意,它不包含產品標識符和第二項的國傢代碼鍵。

解除偽裝。將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 int `json:"width"`
    Height int `json:"height"`
    Title string `json:"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 int `json:"id"`
    Name string `json:"name"`
    Seller struct {
        Id int `json:"id"`
        Name string `json:"name"`
        CountryCode string `json:"country_code"`
    } `json:"seller"`
    Price int `json:"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 int `json:"width"`
    Height int `json:"height"`
    X int `json:"x"`
    Y int `json:"y"`
}
type Config struct {
    Timeout float32 `json:"timeout"`
    PluginsPath string `json:"pluginsPath"`
    Window Window `json:"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 string `json:"name"`
    Age int `json:"age"`
    Email string `json:"-"`
}
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 string `json:"email"`
    }{
        PersonAlias: (*PersonAlias)(p),
        Email: strings.Repeat("*", 4) + "@mail.com", // alter email
    })
}

上面的代碼輸出瞭所有的聯系方式,但由於安全策略的原因,它改變瞭原始的電子郵件地址。請註意,這裡我們需要從Person 類型中創建另一個類型(alias),因為如果我們試圖為原來的Person 類型調用Marshal 函數,由於編碼過程的遞歸實現,程序將進入無限循環。一旦你運行上述代碼片斷,你會看到如下的輸出。

自定義解密

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包的性能。

到此這篇關於在Go中使用JSON(附demo)的文章就介紹到這瞭,更多相關Go使用JSON內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: