go与JSON

JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议。在类似的协议中,JSON并不是唯一的一个标准协议。 XML(§7.14)、ASN.1和Google的Protocol Buffers都是类似的协议,并且有各自的特色,但是由于简洁性、可读性和流行程度等原因,JSON是应用最广泛的一个。

Go语言对于这些标准格式的编码和解码都有良好的支持,由标准库中的encoding/jsonencoding/xmlencoding/asn1等包提供支持

并且这类包都有着相似的API接口。本节,我们将对重要的encoding/json包的用法做个概述。

JSON是对JavaScript中各种类型的值——字符串、数字、布尔值和对象——Unicode本文编码。它可以用有效可读的方式表示基础数据类型和数组、slice、结构体和map等聚合数据类型。

基本的JSON类型有数字(十进制或科学记数法)、布尔值(true或false)、字符串,其中字符串是以双引号包含的Unicode字符序列,支持和Go语言类似的反斜杠转义特性,不过JSON使用的是\Uhhhh转义数字来表示一个UTF-16编码(译注:UTF-16和UTF-8一样是一种变长的编码,有些Unicode码点较大的字符需要用4个字节表示;而且UTF-16还有大端和小端的问题),而不是Go语言的rune类型。

这些基础类型可以通过JSON的数组和对象类型进行递归组合。

一个JSON数组是一个有序的值序列,写在一个方括号中并以逗号分隔;一个JSON数组可以用于编码Go语言的数组和slice。

一个JSON对象是一个字符串到值的映射,写成一系列的name:value对形式,用花括号包含并以逗号分隔;JSON的对象类型可以用于编码Go语言的map类型(key类型是字符串)和结构体。

例如:

boolean         true
number          -273.15
string          "She said \"Hello, BF\""
array           ["gold", "silver", "bronze"]
object          {"year": 1980,
                 "event": "archery",
                 "medals": ["gold", "silver", "bronze"]}

考虑一个应用程序,该程序负责收集各种电影评论并提供反馈功能。它的Movie数据类型和一个典型的表示电影的值列表如下所示。(在结构体声明中,Year和Color成员后面的字符串面值是结构体成员Tag;我们稍后会解释它的作用。)

package main

import "fmt"
import "encoding/json"
import "log"

type Movie struct {
        Title  string
        Year   int  `json:"released"`
        Color  bool `json:"color,omitempty"`
        Actors []string
}

var movies = []Movie{
        {Title: "战狼2", Year: 2017, Color: true,
                Actors: []string{"吴京", "吴刚"}},
        {Title: "老炮儿", Year: 2015, Color: true,
                Actors: []string{"冯小刚", "吴亦凡", "许晴"}},
        {Title: "Bullitt", Year: 1968, Color: false,
                Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
        // ...
}

func main() {
        json_str, err := json.Marshal(movies)
        if err != nil {
                log.Fatal(err)
                return
        }

        fmt.Printf("%s\n", json_str)
}

执行结果

[{"Title":"战狼2","released":2017,"color":true,"Actors":["吴京","吴刚"]},{"Title":"老炮儿","released":2015,"color":true,"Actors":["冯小刚","吴亦凡","许晴"]},{"Title":"Bullitt","released":1968,"Actors":["Steve McQueen","Jacqueline Bisset"]}]

这种紧凑的表示形式虽然包含了全部的信息,但是很难阅读。为了生成便于阅读的格式,另一个json.MarshalIndent函数将产生整齐缩进的输出。该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进:

func main() {
        json_str, err := json.MarshalIndent(movies, "", "    ")
        if err != nil {
                log.Fatal(err)
                return
        }

        fmt.Printf("%s\n", json_str)
}

上面的代码将产生这样的输出(注:在最后一个成员或元素后面并没有逗号分隔符):

[
    {
        "Title": "战狼2",
        "released": 2017,
        "color": true,
        "Actors": [
            "吴京",
            "吴刚"
        ]
    },
    {
        "Title": "老炮儿",
        "released": 2015,
        "color": true,
        "Actors": [
            "冯小刚",
            "吴亦凡",
            "许晴"
        ]
    },
    {
        "Title": "Bullitt",
        "released": 1968,
        "Actors": [
            "Steve McQueen",
            "Jacqueline Bisset"
        ]
    }
]

在编码时,默认使用Go语言结构体的成员名字作为JSON的对象。只有导出的结构体成员才会被编码,这也就是我们为什么选择用大写字母开头的成员名称。

其中Year名字的成员在编码后变成了released,还有Color成员编码后变成了小写字母开头的color。这是因为结构体成员Tag所导致的。一个结构体成员Tag是和在编译阶段关联到该成员的元信息字符串:

Year  int  `json:"released"`
Color bool `json:"color,omitempty"`

结构体的成员Tag可以是任意的字符串面值,但是通常是一系列用空格分隔的key:"value"键值对序列;因为值中含有双引号字符,因此成员Tag一般用原生字符串面值的形式书写。json开头键名对应的值用于控制encoding/json包的编码和解码的行为,并且encoding/...下面其它的包也遵循这个约定。成员Tag中json对应值的第一部分用于指定JSON对象的名字,比如将Go语言中的TotalCount成员对应到JSON中的total_count对象。Color成员的Tag还带了一个额外的omitempty选项,表示当Go语言结构体成员为空或零值时不生成该JSON对象(这里false为零值)。果然,Bullitt是一个黑白电影,并没有输出Color成员。

编码的逆操作是解码,对应将JSON数据解码为Go语言的数据结构,Go语言中一般叫unmarshaling,通过json.Unmarshal函数完成。下面的代码将JSON格式的电影数据解码为一个结构体slice,结构体中只有Title成员。通过定义合适的Go语言数据结构,我们可以选择性地解码JSON中感兴趣的成员。当Unmarshal函数调用返回,slice将被只含有Title信息的值填充,其它JSON成员将被忽略。

package main

import "fmt"
import "encoding/json"
import "log"

type Movie struct {
        Title  string
        Year   int  `json:"released"`
        Color  bool `json:"color,omitempty"`
        Actors []string
}

var movies = []Movie{
        {Title: "战狼2", Year: 2017, Color: true,
                Actors: []string{"吴京", "吴刚"}},
        {Title: "老炮儿", Year: 2015, Color: true,
                Actors: []string{"冯小刚", "吴亦凡", "许晴"}},
        {Title: "Bullitt", Year: 1968, Color: false,
                Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
        // ...
}

func main() {
        /*
                注意 json 编解码 只能处理结构体成员变量是大写的 字段,小写的直接忽略
                如果在编码时 想让json字符串的字段小写,那么需要再结构体中加Tag
                如果再解码时, json有小写的字段名,那么在定义结构体接收的时候,成员名要大写,然后也要加Tag
        */

        //编码,,将go结构体 转换成 json字符串

        //json_str, err := json.Marshal(movies)
        json_data, err := json.MarshalIndent(movies, "", "    ")
        if err != nil {
                log.Fatal(err)
                return
        }

        fmt.Printf("%s\n", json_data)

        fmt.Println("-----------------")

        //解码,将json字符串 转换成 go数据类型
        var titles []struct {
                Title string
        }
        if err := json.Unmarshal(json_data, &titles); err != nil {
                log.Fatal(err)
                return
        }
        fmt.Printf("%+v\n", titles)

        var years []struct {
                Year int `json:"released"`
        }
        if err := json.Unmarshal(json_data, &years); err != nil {
                log.Fatal(err)
                return
        }
        fmt.Printf("%+v\n", years)

        var actors []struct {
                Actors []string
        }

        if err := json.Unmarshal(json_data, &actors); err != nil {
                log.Fatal(err)
                return
        }

        fmt.Printf("%+v\n", actors)

        fmt.Println("-----------------")

        var my_movies []struct {
                Title  string
                Year   int `json:"released"`
                Actors []string
        }

        if err := json.Unmarshal(json_data, &my_movies); err != nil {
                log.Fatal(err)
                return
        }

        fmt.Printf("%+v\n", movies)

}

结果:

[
    {
        "Title": "战狼2",
        "released": 2017,
        "color": true,
        "Actors": [
            "吴京",
            "吴刚"
        ]
    },
    {
        "Title": "老炮儿",
        "released": 2015,
        "color": true,
        "Actors": [
            "冯小刚",
            "吴亦凡",
            "许晴"
        ]
    },
    {
        "Title": "Bullitt",
        "released": 1968,
        "Actors": [
            "Steve McQueen",
            "Jacqueline Bisset"
        ]
    }
]
-----------------
[{Title:战狼2} {Title:老炮儿} {Title:Bullitt}]
[{Year:2017} {Year:2015} {Year:1968}]
[{Actors:[吴京 吴刚]} {Actors:[冯小刚 吴亦凡 许晴]} {Actors:[Steve McQueen Jacqueline Bisset]}]
-----------------
[{Title:战狼2 Year:2017 Color:true Actors:[吴京 吴刚]} {Title:老炮儿 Year:2015 Color:true Actors:[冯小刚 吴亦凡 许晴]} {Title:Bullitt Year:1968 Color:false Actors:[Steve McQueen Jacqueline Bisset]}]

results matching ""

    No results matching ""