Go处理JSON方法

JSON 数据格式介绍

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition – December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言1

JSON建构于两种结构:

  • 键值对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
  • 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

由于不同语言具有很多相似特性,于是可以定义一种数据规范,使得不同语言之间可以交换数据。最早采用的XML,因为XML是一种纯文本格式,所以它适合在网络上交换数据。XML本身不算复杂,但是,加上DTD、XSD、XPath、XSLT等一大堆复杂的规范以后,任何正常的软件开发人员碰到XML都会感觉头大了,最后大家发现,即使你努力钻研几个月,也未必搞得清楚XML的规范。

终于,在2002年的一天,道格拉斯·克罗克福特(Douglas Crockford)同学为了拯救深陷水深火热同时又被某几个巨型软件企业长期愚弄的软件工程师,发明了JSON这种超轻量级的数据交换格式2

JSON序列化与反序列化

由于JSON是用于在不同语言之间交互数据,而不同语言之间的“对象”是不同的,我们无法把一个Java的Map发给Python直接使用,不同语言内部定义“对象”的规范不同,因此我们需要把Map先序列化成JSON,在Python中再反序列化成字典对象。

比如Java发送一个JSON格式的数据:

{
    "name": "Vincent", 
    "age": 18, "email":
    "vinctchanx@gmail.com"
 }

Python接收到数据,使用json包下面的loads 方法加载以后得到字典对象。我们看看json.loads的文档

Deserialize s (a str, bytes or bytearray instance containing a JSON document) to a Python object.

将字符串、字节数组实例反序列化为 Python 对象

总结:JSON最重要的两个方法是序列化和反序列化。

Go语言处理JSON

序列化

Go语言中可以把结构体或者Map序列化JSON,通常在序列化结构体时,由于结构体初始化时,编译器会分配默认值,导致序列化JSON以后出现null.

例如定义了下面的结构体:

type User struct {
    Age     int      `json:"age"`
    Name    string   `json:"name"`
    Email   string   `json:"email"`
    Hobbies []string `json:"hobbies"`
}

将这个结构体序列化为JSON以后:

{
 "age": 0,
 "name": "",
 "email": "",
 "hobbies": null
}

go语言会对基本类型初始化,比如字符串初始化为空字符串,整型为0,而切片类型为nil空指针,序列化以后就是null。正常情况应该是空数组。解决办法就是手动初始化。

user := User{Hobbies: []string{}}
buf, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(buf))

返回结果就是我们想要的

{
 "age": 0,
 "name": "",
 "email": "",
 "hobbies": []
}

上面的处理其实有点问题,除了Hobbies字段,其余是编译器给的默认值,数据中没有赋值,如果需要不显示没有赋值字段,如何处理?因为我们无法得知这个字段的值是真实的数据还是编译器的初始值,解决的办法在后面的标签中加入omitempty

type User struct {
    Age     int      `json:"age,omitempty"`
    Name    string   `json:"name"`
    Email   string   `json:"email,omitempty"`
    Hobbies []string `json:"hobbies"`
}
user := User{Hobbies: []string{}, Age: 18}
buf, _ := json.Marshal(user)
fmt.Println(string(buf))
// {"age":18,"name":"","hobbies":[]}

反序列化

将JSON文本反序列化为结构体(如果JSON的键值类型相同,可以反序列化为Map)依然存在JSON数据字段与默认值重合的问题。我们反序列化不能增加或者较少字段。

现在JSON数据是这样:

{"age":0,"name":"","hobbies":[]}

如果定义接收的结构体如下:

type User struct {
    Age     int      `json:"age"`
    Name    string   `json:"name"`
    Email   string   `json:"email"`
    Hobbies []string `json:"hobbies"`
}

data := `{"age":0,"name":"","hobbies":[]}`
var user = User{}
json.Unmarshal([]byte(data), &user) //反序列化
fmt.Println(user)
// {0   []}
buf, _ := json.Marshal(user) //序列化
fmt.Println(string(buf))
// {"age":0,"name":"","email":"","hobbies":[]}

通过反序列化以后发现多了email字段,从最后的序列化结果可以看到。这是由于结构体初始化email字段时分配了空字符串。

如果在tag里面增加omitempty标签,在反序列化时会将JSON中等于初始值的字段丢弃,以为是编译器分配的结果。对于上面那条JSON数据,反序列化的结果应该是 {}

type User struct {
    Age     int      `json:"age,omitempty"`
    Name    string   `json:"name,omitempty"`
    Email   string   `json:"email,omitempty"`
    Hobbies []string `json:"hobbies,omitempty"`
}
// 代码省略
// {}

要解决上面的问题,只能把结构体字段定义为指针类型,编译器会给指针类型初始化为空指针(nil)。在反序列化操作时,字段的值不会与结构的初始化值相同了,反序列化结果当然就没有问题了。

type User struct {
    Age     *int      `json:"age,omitempty"`
    Name    *string   `json:"name,omitempty"`
    Email   *string   `json:"email,omitempty"`
    Hobbies *[]string `json:"hobbies,omitempty"`
}

data := `{"age":0,"name":"","hobbies":[]}`
var user = User{}
json.Unmarshal([]byte(data), &user) //反序列化
fmt.Println(user)
//{0xc0000aa738 0xc0000a1d70 <nil> 0xc0000b4798}
buf, _ := json.Marshal(user) //序列化
fmt.Println(string(buf))
// {"age":0,"name":"","hobbies":[]}

反序列化以后结果结构体输出结果为地址,没有Email字段,对应空指针,再将这个结构序列化,发现结果与原来的data数据一致。

使用interface解析JSON

上面使用结构体接收JSON,这是我经常用的方式,这是因为解析完以后操作数据非常方便,编辑器可以点出结构里的属性值。除此之外可以使用interface接口来转换,还是来看上面的例子,JSON数据如下:

{
    "age":0,
    "name":"Vincent",
    "hobbies":[
        "running",
        "swimming"
    ]
}

使用interface结构每次都需要根据字段临时转换,比如age是数值型,interface会默认转为float64。而切片类型需要转为接口切片,然后在转换具体类型。

data := []byte(`{"age":0,"name":"Vincent","hobbies":["running","swimming"]}`)
user := make(map[string]interface{}) //map值为接口类型

err := json.Unmarshal(data, &user)
if err != nil {
    fmt.Println(err)
}

age := user["age"].(float64) //数值型必须用float64
fmt.Println("age:", age)

name := user["name"].(string) //字符串
fmt.Println("name:", name)

hobbies := user["hobbies"] //接口类型
fmt.Println(hobbies)

for _, h := range hobbies.([]interface{}) {
    fmt.Println(h.(string)) //接口转字符串
}

结果输出:

age: 0
name: Vincent
[running swimming]
running
swimming

  1. https://www.json.org/json-zh.html ↩︎
  2. https://www.liaoxuefeng.com/wiki/1022910821149312/1023021554858080 ↩︎
标签:

《Go处理JSON方法》有1个想法

发表评论

邮箱地址不会被公开。