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))
}

用法很简单,不过有两个问题需要说明一下:

  1. 结构体Foo里的字段必须是大写字母开头,否则将不能从Json中解析出对应的字段。原因是Go语言约定一个包中只有首字母大写的符号才被导出给其他包使用。json.Unmarshal()函数是在另外一个包里的,所以如果字段名小写,就无法进行赋值。

  2. 这种写法默认把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。

你可能感兴趣的:(Go语言里的Json)