本篇文章翻译自Elliot Chance的"Working With JSON in Go",原文地址“http://elliot.land/working-with-json-in-go” 。欢迎大家访问[我的博客](http://zuozuohao.github.io/),代码可以在[@Zuozuohao](https://github.com/Zuozuohao/GolangGOFPatterns)下载。
在强类型语言中使用JSON是需要一些技巧的。技巧的意思是数据从JSON数据结构到语言支持的数据结构的转换可以灵活的进行,反之亦然。从而避免在操作数组和字典的时候产生类型检查和运行时类型转换的冗余代码。Go在这方面做了很多杰出的工作,正如Go在其他方面做出的努力一样。
Go提供了原生的JSON库,并且与语言本身有效的集成在了一起。encoding/jsobn包包含了很多重要的使用说明。
我将在这篇文章里这里面大概90%的知识点。
所有的示例使用以下的包引用语句,所以我们不用在代码中重复这些语句。
```
import (
"encoding/json"
"fmt"
)
```
**Table of Contents**
1. Decoding JSON
2. Encoding JSON
3. Mapping Keys
4. Default Values
5. Handling Errors
6. Dynamic JSON
7. Validating JSON Schemas
**Decoding JSON**
在理想的情况下,对象的数据结构应该是清晰明确的。这是一个比较简单的应用场景,也是一个最理想的示例方式。
json.Unmarshal需要一个byte数组(我们经常把一个string转换为[]byte)和一个容纳解码后数据的对象引用。
```
type Person struct {
FirstName string
LastName string
}
func main() {
theJson := `{"FirstName": "Bob", "LastName": "Smith"}`
var person Person
json.Unmarshal([]byte(theJson), &person)
fmt.Printf("%+v\n", person)
}
```
输出:
**{FirstName:Bob LastName:Smith}**
Go把名称的第一个字母大小写作为该类型是否暴露给外界的标识。然而,解码的时候并不需要付出过多的工作,以为解码机制对于大小写不敏感。我们可以把{FirstName:Bob LastName:Smith} 解码到
```
type Person struct {
FirstName string
LastName string
}
```
是不是还能正确的解码。但是当你把这些数据编码到JSON数据格式的时候需要转换为大写,否则会导致程序崩溃。稍后会为大家介绍解决这个问题的简单方式--标签。
**Encoding JSON**
JSON编码十分简单。我们直接调用json.Marshal函数即可。
```
type Person struct {
FirstName string
LastName string
}
func main() {
person := Person{"James", "Bond"}
theJson, _ := json.Marshal(person)
fmt.Printf("%+v\n", string(theJson))
}
```
输出:
*{"FirstName":"James","LastName":"Bond"}*
**Mapping Keys**
如果你想要不同的JSON键值映射到结构体中就可以使用标签方式。标签方式是基于变量的文本注释来实现的。
```
type Person struct {
FirstName string `json:"fn"`
LastName string `json:"ln"`
}
func main() {
theJson := `{"fn": "Bob", "ln": "Smith"}`
var person Person
json.Unmarshal([]byte(theJson), &person)
fmt.Printf("%+v\n", person)
}
```
标签还可以用于编码对象,这样键值就可以映射回对象具有变量名称。
**Default Values**
json.Unmarshal的参数是对象实例而不是类型名。JSON数据中查找到属于对象的键值都会替换掉对象中的原有值,否则就会使用对象的默认值。
```
type Person struct {
FirstName string
LastName string
}
func newPerson() Person {
return Person{"<No First>", "<No Last>"}
}
func main() {
theJson := `{"FirstName": "Bob"}`
person := newPerson()
json.Unmarshal([]byte(theJson), &person)
fmt.Printf("%+v\n", person)
}
```
输出:
*{FirstName:Bob LastName:<No Last>}*
**Handling Errors**
如果JSON不能被成功解析(例如语法错误),json.Unmarshal就会返回错误类型。
```
func main() {
theJson := `invalid json`
err := json.Unmarshal([]byte(theJson), nil)
fmt.Printf("%+v\n", err)
}
```
输出:
*invalid character 'i' looking for beginning of value*
更为通常的情况是JSON数据包含Go中不同类型数据实例,下面的例子将尝试将字符串解码为integer类型。
```
func main() {
theJson := `"123"`
var value int
err := json.Unmarshal([]byte(theJson), &value)
fmt.Printf("%+v\n", err)
}
```
输出:
*json: cannot unmarshal string into Go value of type int*
如何处理这些错误取决于你的设计,但是要保证代码的简洁和可读性。
**Dynamic JSON**
动态JSON数据处理是一个难点,困难之处在于你在使用之前不知道JSON数据的确切数据结构。
这里有两种方式处理动态的JSON数据:
**1. Create a flexible skeleton to unserialise into and test the types inside that**
```
type PersonFlexible struct {
Name interface{}
}
type Person struct {
Name string
}
func main() {
theJson := `{"Name": 123}`
var personFlexible PersonFlexible
json.Unmarshal([]byte(theJson), &personFlexible)
if _, ok := personFlexible.Name.(string); !ok {
panic("Name must be a string.")
}
// When validation passes we can use the real object and types.
// This code will never be reached because the above will panic()...
// But if you make the Name above a string it will run the following:
var person Person
json.Unmarshal([]byte(theJson), &person)
fmt.Printf("%+v\n", person)
}
```
**2. Go totally rogue (pun intended) and investigate the entire value one step at a time**
这个方法有点类似于JavaScript的处理方式,即大量使用typeof函数进行类型检查。你可以使用Go的switch语句优雅的实现这一方式。
```
func main() {
theJson := `123`
var anything interface{}
json.Unmarshal([]byte(theJson), &anything)
switch v := anything.(type) {
case float64:
// v is an float64
fmt.Printf("NUMBER: %f\n", v)
case string:
// v is a string
fmt.Printf("STRING: %s\n", v)
default:
panic("I don't know how to handle this!")
}
}
```
**PS**Go使用固定的数据类型来编解码JSON键值。类似于123这样的数据将被解码为float64类型而不是int类型。这种实现方式简化了switch的结构,但是需要你实现对数据的二次加工。
**Validating JSON Schemas**
如果你有一个结构复杂的JSON数据,更为简单的方式是使用"JSON Schema"。
我不会深入的去讲解 JSON Schema 的用法,因为这样占用很大的篇幅。这里,我们使用xeipuuv/gojsonschema包提供的处理方式:
```
import (
"fmt"
"github.com/xeipuuv/gojsonschema"
)
func main() {
schemaLoader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")
documentLoader := gojsonschema.NewReferenceLoader("file:///home/me/document.json")
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
panic(err.Error())
}
if result.Valid() {
fmt.Printf("The document is valid\n")
} else {
fmt.Printf("The document is not valid. see errors :\n")
for _, desc := range result.Errors() {
fmt.Printf("- %s\n", desc)
}
}
}
```
非常感谢您读完这篇文章,欢迎您提出宝贵的意见或者留下您的评论。
享受编码的乐趣!
**其他链接**
[C的面向对象编程](http://zuozuohao.github.io/Series/CSeries/)
有疑问加站长微信联系(非本文作者)