在Go中使用JSON(附demo)
Golang(又稱Go)是一種靜態(tài)類型的編譯編程語言,具有類似C語言的語法。Go為通用編程提供了一個最小的語法,只有25個關鍵詞。
現(xiàn)在,程序員使用Go來構建開發(fā)者工具、云計算工具、CLI程序以及桌面和網(wǎng)絡應用。Go在構建高性能軟件系統(tǒng)方面非常受歡迎,在這些系統(tǒng)中,并發(fā)性起著關鍵作用。
Go開發(fā)人員經(jīng)常需要處理JSON內容。例如,我們經(jīng)常要讀取JSON文件來填充Go對象,并從現(xiàn)有的Go對象中寫入JSON文件。像其他現(xiàn)代編程語言一樣,Go提供了一個標準庫模塊來處理JSON結構。
在本教程中,我將通過實際例子解釋如何在Go中處理JSON。此外,我還將解釋一些高級概念,如自定義JSON編碼和解碼。
Go編碼/json包
Go提供了encoding/json包,通過標準庫的編碼命名空間處理JSON內容。encoding/json包提供了API函數(shù),用于從Go對象生成JSON文檔--以及從JSON文檔中填充Go對象。此外,它還允許你定制JSON到Go和Go到JSON的翻譯過程。
JSON規(guī)范同時支持格式化和內聯(lián)(minified)文檔。因此,Go encoding/json包可以讓開發(fā)者同時生成格式化和最小化的JSON文檔。
編碼。將 Go 對象轉換為 JSON
什么是Go中的marshaling?
將Go對象編碼為JSON格式被稱為marshaling。我們可以使用Marshal
函數(shù)來將 Go 對象轉換為 JSON。Marshal
函數(shù)的語法如下。
func Marshal(v interface{}) ([]byte, error)
它接受一個空接口。換句話說,你可以向該函數(shù)提供任何Go數(shù)據(jù)類型--整數(shù)、浮點、字符串、結構體、地圖等--因為所有Go數(shù)據(jù)類型定義都可以用空接口表示。它返回兩個值:一個編碼JSON的字節(jié)片和error
。
裝載簡單對象
如上所述,我們可以用原始的Go數(shù)據(jù)類型生成JSON。例如,你可以將Go字符串轉換為JSON字符串。
但由于轉換基元在現(xiàn)實世界的軟件開發(fā)項目中沒有幫助,讓我們從轉換一些簡單對象開始。下面的代碼片斷將從一個map數(shù)據(jù)結構中編碼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()
,將字節(jié)轉換為字符串。Go將map數(shù)據(jù)結構編碼為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包在編碼過程中不會包含這個特定的字段,也不會出現(xiàn)任何錯誤。
上述代碼將輸出以下JSON結構。
{"Title":"Hello Golang","Author":"John Mike","Year":2021}
對復雜對象進行編碼
在前面的例子中,我們從Go對象中編碼了JSON,比如簡單的map和structs。如果你試圖對整數(shù)數(shù)組、字符串數(shù)組和原始變量進行編碼,Go將為這些元素產(chǎn)生簡單的JSON結構。
但大多數(shù)時候,我們必須從Go程序中的復雜對象中生成JSON文件,如產(chǎn)品列表、產(chǎn)品詳情和各種嵌套數(shù)據(jù)記錄。
首先,讓我們從一個產(chǎn)品列表中對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)) }
上面的代碼初始化了一個有兩個項目的產(chǎn)品列表。Product
結構有一個Seller
結構作為嵌套對象--所有的產(chǎn)品都放在一個產(chǎn)品片中。接下來,我們將最終的產(chǎn)品列表發(fā)送到Marshal
函數(shù),將其編碼為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數(shù)據(jù)結構中編碼JSON。但是現(xiàn)在,當我們看上面的輸出時,我們有兩個問題。
- 輸出的JSON結構的鍵總是以大寫的英文字母開始--我們怎樣才能重命名JSON字段?
- 當我們對大型復雜的結構進行編碼時,輸出結果變得簡直無法閱讀--我們如何才能美化JSON的輸出?
Go encoding/json軟件包通過額外的庫功能回答了上述問題。
集合功能
Go提供了幾個功能,通過額外的API函數(shù)和結構標簽改善和定制JSON輸出。
重命名字段
你必須以大寫英文字母開始結構字段的聲明,以便讓JSON包訪問它們。因此,你將永遠得到大寫的英文字母作為JSON鍵。Go編碼/json包允許開發(fā)人員通過JSON結構標簽隨意重命名JSON字段。
下面的代碼片斷對產(chǎn)品對象的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編碼過程中重命名一個特定的結構字段。
一旦你執(zhí)行上述代碼,你將得到以下輸出。
{"id":50,"name":"Writing Book","seller":{"id":1,"name":"ABC Company","country_code":"US"},"price":100}
生成具有縮進功能的JSON(pretty-print)
Marshal
函數(shù)生成最小的內聯(lián)JSON內容,沒有任何格式化。你可以使用MarshalIndent
函數(shù)來編碼具有縮進功能的可讀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
)進行縮進。你可以根據(jù)你的要求使用四個空格、兩個空格、八個空格等進行格式化。
忽略JSON輸出中的特定字段
早些時候,我們使用結構標簽來重命名JSON鍵。我們也可以使用結構標簽來省略特定字段。如果我們使用json:”-”
作為標簽,相關的結構字段將不會被用于編碼。另外,如果我們在結構標簽名稱字符串中使用,omitempty
,如果相關字段的值為空,則不會被用于編碼。
下面的代碼省略了產(chǎn)品標識符的編碼。此外,它還從輸出中省略了空的國家代碼值。
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)) }
上述代碼產(chǎn)生以下輸出。注意,它不包含產(chǎn)品標識符和第二項的國家代碼鍵。
解除偽裝。將JSON轉換為Go對象
在Go環(huán)境中,JSON文檔的解碼過程被稱為unmarshaling。我們可以使用Unmarshal
函數(shù)來將JSON轉換為Go對象。Unmarshal
函數(shù)的語法如下。
func Unmarshal(data []byte, v interface{}) error
它接受兩個參數(shù):一個JSON內容的字節(jié)片和一個空的接口引用。如果在解碼過程中出現(xiàn)錯誤,該函數(shù)可能會返回一個錯誤。Unmarshal
函數(shù)不創(chuàng)建和返回Go對象,所以我們必須傳遞一個引用來存儲解碼后的內容。
解除對簡單JSON結構的封存
類似于JSON的marshaling,我們可以解封Go的原始數(shù)據(jù)類型,如整數(shù)、字符串、浮點數(shù)和布爾。但同樣的,由于原始數(shù)據(jù)解密在大多數(shù)軟件開發(fā)項目中沒有真正的使用案例,我們先將下面的鍵值結構解碼為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
函數(shù)之前,我們必須將其轉換為字節(jié)片狀。在這里,我們檢查了返回的錯誤對象的值以檢測解析錯誤。
在這種情況下,上述JSON標簽是可選的,因為Go編碼/json包通常將JSON字段映射為結構字段,并進行不區(qū)分大小寫的匹配。
同樣地,我們也可以將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] }
解除復雜數(shù)據(jù)結構的束縛
之前的解密例子向你展示了如何對簡單的JSON結構進行解密。我們在軟件開發(fā)項目中經(jīng)常要對復雜的嵌套JSON結構進行解碼。下面的例子演示了如何從一個JSON格式的產(chǎn)品列表中填充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這樣的在線工具,根據(jù)JSON輸入創(chuàng)建結構定義。
也有一種方法可以在Go中不創(chuàng)建結構而訪問解析后的值。你可以通過為JSON對象創(chuàng)建map[string]interface{}
類型對象來動態(tài)訪問任何值,但這種方法會導致非常復雜、質量較差的源代碼。
不過,你可以通過下面的示例代碼檢查動態(tài)JSON值的訪問,以達到實驗目的。但是,在沒有建立適當?shù)腉o結構的情況下,不要在生產(chǎn)軟件系統(tǒng)中使用這種方法,因為它會產(chǎn)生復雜和難以測試的代碼。
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結構的情況下打印了第二個產(chǎn)品項目的價格。
從文件系統(tǒng)中讀取JSON文件
我們在前面的例子中使用了硬編碼的JSON字符串進行演示。但是,在實踐中,我們從不同的來源加載JSON字符串:從文件系統(tǒng),通過互聯(lián)網(wǎng),通過本地網(wǎng)絡位置,等等。大多數(shù)程序員通常使用JSON格式來存儲文件系統(tǒng)上的配置細節(jié)。
讓我們寫一些Go代碼,從文件中讀取和解碼JSON數(shù)據(jù),并將其轉換成Go對象。首先,創(chuàng)建一個名為config.json
的文件并輸入以下內容。
{ "timeout": 50.30, "pluginsPath": "~/plugins/", "window": { "width": 500, "height": 200, "x": 500, "y": 500 } }
現(xiàn)在,運行以下代碼,將上述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
函數(shù)將JSON文件內容讀成字節(jié),并將數(shù)據(jù)記錄解碼到Config
結構中。
將JSON文件寫到文件系統(tǒng)中
在前面的例子中,我們通過Println
函數(shù)將編碼后的JSON內容打印到控制臺?,F(xiàn)在我們可以通過ioutil.WriteFile
函數(shù)將這些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數(shù)據(jù)記錄從一種格式轉換為另一種格式時,這些功能很有幫助。
自定義編排
假設你正在用Go編寫一個聯(lián)系人管理應用程序,你提供一個功能給所有用戶下載JSON格式的聯(lián)系人列表。假設由于安全策略的原因,你不能讓非管理員用戶看到所有的電子郵件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, "james.h@gmail.com"}, Person {"David Rick", 30, "rick.dvd@yahoo.com"}, } 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 }) }
上面的代碼輸出了所有的聯(lián)系方式,但由于安全策略的原因,它改變了原始的電子郵件地址。請注意,這里我們需要從Person
類型中創(chuàng)建另一個類型(alias
),因為如果我們試圖為原來的Person
類型調用Marshal
函數(shù),由于編碼過程的遞歸實現(xiàn),程序將進入無限循環(huán)。一旦你運行上述代碼片斷,你會看到如下的輸出。
自定義解密
Go json包也可以讓你自定義JSON解碼過程。假設你需要處理一個JSON配置文件,并需要在解碼過程中轉換一些值。假設一個配置字段說的是開爾文的溫度,但你需要用攝氏度來存儲具體數(shù)值。
請看下面的代碼,它實現(xiàn)了自定義解密。
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。這里我們還需要創(chuàng)建另一個類型(alias
),以避免無限循環(huán),這與自定義的marshaling類似。
總結
在本教程中,我們通過實際例子討論了Go中的JSON編碼(marshaling)和解碼(unmarshaling)。JSON是一種廣泛使用的、獨立于語言的編碼格式。因此,幾乎所有基于Go的網(wǎng)絡框架都在內部處理JSON編碼和解碼。例如,GinHTTP框架允許你使用json包直接向API函數(shù)發(fā)送一個結構體,而不需要手動進行marshaling。
然而,你可以在你的Go程序中使用Go json包,而不需要消耗第三方庫,因為json包是標準庫的一部分。另外,Go json包還有一些更快的替代品(根據(jù)這個基準)。但是,json包是標準庫的一部分,由Go開發(fā)團隊維護。因此,Go開發(fā)團隊會在即將發(fā)布的版本中提高編碼/json包的性能。
到此這篇關于在Go中使用JSON(附demo)的文章就介紹到這了,更多相關Go使用JSON內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
GO語言不固定參數(shù)函數(shù)與匿名函數(shù)的使用
本文主要介紹了GO語言不固定參數(shù)函數(shù)與匿名函數(shù)的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-03-03go+redis實現(xiàn)消息隊列發(fā)布與訂閱的詳細過程
這篇文章主要介紹了go+redis實現(xiàn)消息隊列發(fā)布與訂閱,redis做消息隊列的缺點:沒有持久化,一旦消息沒有人消費,積累到一定程度后就會丟失,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09