在Go中使用JSON(附demo)
目录
- Go编码/json包
- 编码。将Go对象转换为JSON
- 什么是Go中的marshaling?
- 装载简单对象
- 对复杂对象进行编码
- 集合功能
- 重命名字段
- 生成具有缩进功能的JSON(pretty-print)
- 忽略JSON输出中的特定字段
- 解除伪装。将JSON转换为Go对象
- 解除对简单JSON结构的封存
- 解除复杂数据结构的束缚
- 从文件系统中读取JSON文件
- 将JSON文件写到文件系统中
- 自定义抓取和解除抓取
- 自定义编排
- 自定义解密
- 总结
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, "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 }) }
上面的代码输出了所有的联系方式,但由于安全策略的原因,它改变了原始的电子邮件地址。请注意,这里我们需要从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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!