Go语言里的Json
Json是一种轻量级数据交换格式,具有灵活、易于阅读的特点,在互联网行业有广泛的应用。Go语言运行时里自带了encoding/json包,提供了Marshal()和Unmarshal()两个函数进行编码和解码,两个函数原型如下:
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
废话少说,直接上例子:
package main
import (
"fmt"
"encoding/json"
)
type Foo struct {
Name string
Age int
}
func main() {
data := []byte(`{"Name": "John Doe", "Age": 25}`)
var f Foo
json.Unmarshal(data, &f)
fmt.Printf("%s is %d years old.\n", f.Name, f.Age)
output, _ := json.Marshal(&f)
fmt.Println(string(output))
}
用法很简单,不过有两个问题需要说明一下:
-
结构体Foo里的字段必须是大写字母开头,否则将不能从Json中解析出对应的字段。原因是Go语言约定一个包中只有首字母大写的符号才被导出给其他包使用。json.Unmarshal()函数是在另外一个包里的,所以如果字段名小写,就无法进行赋值。
-
这种写法默认把Json里的字段赋值给结构体Foo里的同名字段,但是两者的字段名并不总是能保持一致,比如Json里的字段名可能是小写的name和age。这种情况下就需要手动指定字段的对应关系:
type Foo struct { Name string `json:"name"` Age int `json:"value"` }
结构体Foo里的字段不但可以是基本类型,也可以是其他的结构体,比如下面这个例子:
type Employment struct {
Company string `json:"company"`
Title string `json:"title"`
}
type Foo struct {
Name string `json:"name"`
Age int `json:"age"`
Job Employment `json:"job"`
}
func main() {
data := []byte(`{"name": "John Doe", "age": 25, "job": {"company": "ABC", "title": "Engineer"}}`)
var f Foo
json.Unmarshal(data, &f)
fmt.Printf("%s is %d years old.\n", f.Name, f.Age)
output, _ := json.Marshal(&f)
fmt.Println(string(output))
}
前面这两个例子里,Json的结构都是已经确定了的,因此才可以预先定义好结构体,然后用Marshal()和Unmarshal()在Go对象和Json串之间进行转换。有时候在解析之前我们可能并不知道Json对象里有哪些字段,这就需要一种更灵活的处理方式。在Python里,json.loads()直接将Json串转换成字典。类似的,在Go语言里,可以用把字段不确定的Json串转换成map。看下面这个例子:
type Foo struct {
Name string `json:"name"`
Age int `json:"age"`
Job Employment `json:"job"`
Extra map[string]interface{} `json:"extra"`
}
func main() {
data := []byte(`{
"name": "John Doe",
"age": 25,
"job": {
"company": "ABC",
"title": "Engineer"
},
"extra": {
"marital status": "married",
"childrens": 0
}}`)
var f Foo
json.Unmarshal(data, &f)
fmt.Printf("%s is %d years old and works at %s.\n", f.Name, f.Age, f.Job.Company)
fmt.Printf("He is %s and has %d childrens.\n", f.Extra["marital status"].(string), int(f.Extra["childrens"].(float64)))
output, _ := json.Marshal(&f)
fmt.Println(string(output))
}
Extra对应了Json对象里结构不确定的extra字段。Go语言的map在声明时必须指定key和value的类型(这点远不如Python灵活),但是extra里的字段可能是不同类型,因此这个例子里使用了interface作为value类型。interface也就是“接口”,Go语言里任何数据类型都是某种接口,因此interface类型可以指代任何类型。当然我们也可以自定义interface类型,这不在本文的讨论范围之内。按照我的理解,interface类型在这里的作用类似于C语言里的void *。
还有一点值得一提的是,例子里的childrens字段值虽然是整数,但是被解析成了float64类型。在Json的定义中,值的类型不分整型和浮点型,只有一个number类型,因此在我们没有指定字段和类型的情况下,Unmarshal()函数把所有number类型的值都当作float64。我也是在写这篇文章的时候才发现这一点的。
有了这些技巧,基本上已经能应付绝大部分的Json处理任务了。如果还是觉得不够好用,可以尝试一下bit.ly的simplejson,或者性能更好的easyjson。