JSON

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

JSON是对JavaScript中各种类型的值——字符串、数字、布尔值和对象——Unicode本文编码。

基本的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类型是字符串)和结构体。

  • 解析JSON
    1. 数据结构 --> 指定格式 = 序列化 编码(传输之前)
    2. 指定格式 --> 数据格式 = 反序列化 解码(传输之后)
    序列化是在内存中把数据转换成指定格式(data -> string),反之亦然(string -> data structure)。 编码也是一样的,只是输出一个数据流(实现了 io.Writer 接口);解码是从一个数据流(实现了 io.Reader)输出到一个数据结构。

json字符串:

  1. {"servers":[{"serverName":"Shanghai","serverIP":"127.0.0.1"},{"serverName":"Beijing","serverIP":"127.0.0.2"}]}
  • json解析到结构体

在Go中我们经常需要做数据结构的转换,Go中提供的处理json的标准包是 encoding/json,主要使用的是以下两个方法:

  1. // 序列化 结构体=> json
  2. func Marshal(v interface{}) ([]byte, error)
  3. // 反序列化 json=>结构体
  4. func Unmarshal(data []byte, v interface{}) error

序列化前后的数据结构有以下的对应关系:

  1. bool for JSON booleans
  2. float64 for JSON numbers
  3. string for JSON strings
  4. []interface{} for JSON arrays
  5. map[string]interface{} for JSON objects
  6. nil for JSON null

如果我们有一段json要转换成结构体就需要用到(Unmarshal)样的函数:

  1. func Unmarshal(data []byte, v interface{}) error

通过这个函数我们就可以实现解析:

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. type Server struct {
  7. ServerName string
  8. ServerIP string
  9. }
  10. type Serverslice struct {
  11. Servers []Server
  12. }
  13. func main() {
  14. var s Serverslice
  15. str := `{"servers":[{"serverName":"Shanghai","serverIP":"127.0.0.1"}, {"serverName":"Beijing","serverIP":"127.0.0.2"}]}`
  16. json.Unmarshal([]byte(str), &s)
  17. fmt.Println(s)
  18. }

我们首先定义了与json数据对应的结构体,数组对应slice,字段名对应JSON里面的KEY,在解析的时候,如何将json数据与struct字段相匹配呢?例如JSON的key是Foo,那么怎么找对应的字段呢?这就用到了我们上面结构体说的tag。

同时能够被赋值的字段必须是可导出字段(即首字母大写)。同时JSON解析的时候只会解析能找得到的字段,找不到的字段会被忽略,这样的一个好处是:当你接收到一个很大的JSON数据结构而你却只想获取其中的部分数据的时候,你只需将你想要的数据对应的字段名大写,即可轻松解决这个问题。

  • 结构体转json

结构体转json就需要用到JSON包里面通过Marshal函数来处理:

  1. func Marshal(v interface{}) ([]byte, error)

假设我们还是需要生成上面的服务器列表信息,那么如何来处理呢?

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. type Server struct {
  7. ServerName string
  8. ServerIP string
  9. }
  10. type Serverslice struct {
  11. Servers []Server
  12. }
  13. func main() {
  14. var s Serverslice
  15. s.Servers = append(s.Servers, Server{ServerName: "Shanghai", ServerIP: "127.0.0.1"})
  16. s.Servers = append(s.Servers, Server{ServerName: "Beijing", ServerIP: "127.0.0.2"})
  17. b, err := json.Marshal(s)
  18. if err != nil {
  19. fmt.Println("json err:", err)
  20. }
  21. fmt.Println(string(b))
  22. }

输出结果:

  1. {"Servers":[{"ServerName":"Shanghai","ServerIP":"127.0.0.1"},{"ServerName":"Beijing","ServerIP":"127.0.0.2"}]}

我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现:

  1. type Server struct {
  2. ServerName string `json:"serverName"`
  3. ServerIP string `json:"serverIP"`
  4. }
  5. type Serverslice struct {
  6. Servers []Server `json:"servers"`
  7. }

针对JSON的输出,我们在定义struct tag的时候需要注意的几点是:

  • 字段的tag是”-“,那么这个字段不会输出到JSON
  • tag中带有自定义名称,那么这个自定义名称会出现在JSON的字段名中,例如上面例子中serverName
  • tag中如果带有”omitempty”选项,那么如果该字段值为空,就不会输出到JSON串中
  • 如果字段类型是bool, string, int, int64等,而tag中带有”,string”选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串
  1. type Server struct {
  2. // ID 不会导出到JSON中
  3. ID int `json:"-"`
  4. // ServerName2 的值会进行二次JSON编码
  5. ServerName string `json:"serverName"`
  6. ServerName2 string `json:"serverName2,string"`
  7. // 如果 ServerIP 为空,则不输出到JSON串中
  8. ServerIP string `json:"serverIP,omitempty"`
  9. }
  10. s := Server {
  11. ID: 1,
  12. ServerName: `Go "1.0" `,
  13. ServerName2: `Go "1.10" `,
  14. ServerIP: ``,
  15. }
  16. b, _ := json.Marshal(s)
  17. os.Stdout.Write(b)

输出内容:

  1. {"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.10\\\" \""}

Marshal函数只有在转换成功的时候才会返回数据,但是我们应该注意下:

  • JSON对象只支持string作为key,所以要编码一个map,那么必须是map[string]T这种类型(T是Go语言中任意的类型)
  • Channel, complex和function是不能被编码成JSON的
  • 嵌套的数据是不能编码的,不然会让JSON编码进入死循环
  • 指针在编码的时候会输出指针指向的内容,而空指针会输出null

  • 解析到interface

在Go中Interface{}可以用来存储任意数据类型的对象,这种数据结构正好用于存储解析的未知结构的json数据的结果。JSON包中采用map[string]interface{}和[]interface{}结构来存储任意的JSON对象和数组。Go类型和JSON类型的对应关系如下:

  • bool 代表 JSON booleans,
  • loat64 代表 JSON numbers,
  • string 代表 JSON strings,
  • nil 代表 JSON null.

通常情况下我们会拿到一段json数据:

  1. b := []byte(`{"Name":"Ande","Age":10,"Hobby":"Football"

现在开始解析到接口中:

  1. var f interface{}
  2. err := json.Unmarshal(b, &f)

在这个接口f里面存储了一个map类型,他们的key是string,值存储在空的interface{}里

  1. f = map[string]interface{}{
  2. "Name": "Ande",
  3. "Age": ,
  4. "Hobby":"Football"
  5. }

通过断言的方式我们把结构体强制转换数据类型:

  1. m := f.(map[string]interface{})

通过断言之后,我们就可以通过来访问里面的数据:

  1. for k, v := range m {
  2. switch vv := v.(type) {
  3. case string:
  4. fmt.Println(k, "is string", vv)
  5. case int:
  6. fmt.Println(k, "is int", vv)
  7. case float64:
  8. fmt.Println(k,"is float64",vv)
  9. case []interface{}:
  10. fmt.Println(k, "is an array:")
  11. for i, u := range vv {
  12. fmt.Println(i, u)
  13. }
  14. default:
  15. fmt.Println(k, "is of a type I don't know how to handle")
  16. }
  17. }

通过interface{}与type assert的配合,我们就可以解析未知结构的JSON数了。

其实很多时候我们通过类型断言,操作起来不是很方便,我们可以使用一个叫做simplejson的包,在处理未知结构体的JSON数据:

  1. data,err := NewJson([]byte(`{
  2. "test": {
  3. "array": [1, "2", 3, 4, 5]
  4. "int": 10,
  5. "float": 5.150,
  6. "bignum": 9223372036854775807,
  7. "string": "simplejson",
  8. "bool": true
  9. }
  10. }`))
  11. arr, _ := js.Get("test").Get("array").Array()
  12. i, _ := js.Get("test").Get("int").Int()
  13. ms := js.Get("test").Get("string").MustString()

这个库用起来还是很方便也很简单。

  • json中的Encoder和Decoder

通常情况下,我们可能会从 Request 之类的输入流中直接读取 json 进行解析或将编码(encode)的 json 直接输出,为了方便,标准库为我们提供了 Decoder 和 Encoder 类型。它们分别通过一个 io.Reader 和 io.Writer 实例化,并从中读取数据或写数据。

  1. #Decoder 从 r io.Reader 中读取数据,`Decode(v interface{})` 方法把数据转换成对应的数据结构
  2. func NewDecoder(r io.Reader) *Decoder
  3. #Encoder 的 `Encode(v interface{})` 把数据结构转换成对应的 JSON 数据,然后写入到 w io.Writer 中
  4. func NewEncoder(w io.Writer) *Encoder

查看源码可以发现,Encoder.Encode/Decoder.DecodeMarshal/Unmarshal 实现大体是一样。 但是也有一些不同点:Decoder 有一个方法 UseNumber,它的作用是,在默认情况下,json 的 number 会映射为 Go 中的 float64,有时候,这会有些问题,比如:

  1. b := []byte(`{"Name":"keke","Age":25,"Money":200.3}`)
  2. var person = make(map[string]interface{})
  3. err := json.Unmarshal(b, &person)
  4. if err != nil {
  5. log.Fatalln("json unmarshal error:", err)
  6. }
  7. age := person["Age"]
  8. log.Println(age.(int))

运行:

  1. > interface conversion: interface is float64, not int. #age是int,结果 panic 了

然后我们改用Decoder.Decode(用上 UseNumber):

  1. b := []byte(`{"Name":"keke","Age":25,"Money":200.3}`)
  2. var person = make(map[string]interface{})
  3. decoder := json.NewDecoder(bytes.NewReader(b))
  4. decoder.UseNumber()
  5. err := decoder.Decode(&person)
  6. if err != nil {
  7. log.Fatalln("json unmarshal error:", err)
  8. }
  9. age := person["Age"]
  10. log.Println(age.(json.Number).Int64())
  • json.RawMessage能够延迟对 json进行解码

此外我们在解析的时候,还可以把某部分先保留为 JSON 数据不要解析,等到后面得到更多信息的时候再去解析。