博客 记录生活,记录工作。

JSON and Go

2017-12-06
go
 

1. 引言

解析JSON字符串,弱类型语言例如PHP来说json_encode()json_decode()就能很好的完成功能,但是对于强类型语言Go来说,解析JSON字符串就需要考虑一些情况了,下面我们对Go对JSON的转换做一些介绍。

2. 编码

在Go中,使用Marshal函数进行JSON的编码:

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

如下示例所示,

//Go data structure, Message
type Message struct {
    Name string
    Body string
    Time int64
}

//an instance of Message
m := Message{"Alice", "Hello", 1294706395881547000}

//JSON encode
b, err := json.Marshal(m)

//If all is well, err will be nil and b will be a []byte containing this JSON data
b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)

只有能够表示为合法JSON的数据结构才能被编码:

  • JSON对象只支持key为string,即Go的map类型为map[string]T(其中T为json包支持Go的任意类型)
  • channel、complex和function type不能被编码
  • 循环数据结构不能被编码(将会导致Marshal进入无限循环)
  • 指针将会被编码为指针所指的值(指针为nil的编码为null)

json包只能访问结构体的可访问field(大写字母开头的field),因此结构体中只有可访问的field才能表示为JSON的输出。

3. 已知类型解码

解析已知类型的数据,我们可以使用Unmarshal函数:

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

如下示例所示,

//create a place where the decoded data will be stored
var m Message

//call json.Unmarshal, passing it a []byte of JSON data and a pointer to m
err := json.Unmarshal(b, &m)

//If b contains valid JSON that fits in m, 
//after the call err will be nil and the data
//from b will have been stored in the struct m, 
//as if by an assignment like
m = Message{
    Name: "Alice",
    Body: "Hello",
    Time: 1294706395881547000,
}

对于JSON字符串中一个已知keyFoo,Unmarshal将查找目标结构体的field:

  • Footag的可访问field
  • 名字为Foo的可访问field
  • 名字为FOOFoO或其他与Foo匹配不区分大小写的可访问field

对于JSON字符串不严格匹配定义的数据结构,Unmarshal只解析可以在目标数据结构中能找到的field。因此在下面的例子中,只有Name字段会被解析,而Food字段会被忽略。

b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)

当我们想在一个很大的JSON结构中,只解析少量我们期望的field,这种方式是非常有用的。这也意味着目标结构中任何不能访问的field不会受到Unmarshal的影响。

4. 任意类型解码

对于未知的数据类型,json包使用map[string]interface{}[]interface{}来存储未知类型的JSON对象和数组;也可以将任意合法的JSON字符串解析为interface{}。默认的Go类型为:

  • bool对应JSON的类型booleans
  • float64对应JSON的类型numbers
  • string对应JSON的类型strings
  • nil对应JSON的null

如下示例所示,

b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

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

//f would be a map,
//whose keys are strings 
//and whose values are themselves stored as empty interface values
/*
f = map[string]interface{}{
    "Name": "Wednesday",
    "Age":  6,
    "Parents": []interface{}{
        "Gomez",
        "Morticia",
    },
}
*/
for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case float64:
        fmt.Println(k, "is float64", vv)
    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don't know how to handle")
    }
}

/*
output:
Name is string Wednesday
Age is float64 6
Parents is an array:
0 Gomez
1 Morticia
*/

5. 引用类型

对于结构体中的 pointers、slices 和 maps ,Unmarshal将会分配存储结构并解析相应的引用类型。例如,如果JSON对象中存在Bar field,Unmarshal会new Bar结构并解析,否则Bar是nil指针。

type Foo struct {
    Bar *Bar
}

6. 流数据编码和解码

json包提供了Decoder和Encoder类型来支持通用的JSON数据的读写流,函数NewDecoder和NewEncoder分别处理 io.Reader 和 io.Writer 接口类型。

func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder

例如,从stdin中读入JSON对象,解析后移除除了Name之外的其他元素,然后输出到stdout中。

package main

import (
    "encoding/json"
    "log"
    "os"
)

func main() {
    dec := json.NewDecoder(os.Stdin)
    enc := json.NewEncoder(os.Stdout)
    for {
        var v map[string]interface{}
        if err := dec.Decode(&v); err != nil {
            log.Println(err)
            return
        }
        for k := range v {
            if k != "Name" {
                delete(v, k)
            }
        }
        if err := enc.Encode(&v); err != nil {
            log.Println(err)
        }
    }
}

在Go中,由于 Readers 和 Writers 无处不在,Encoder 和 Decoder 有很多的应用场景,例如HTTP链接的读写、WebSockets和file等。

7. 参考

  1. json and go
  2. Go json package
  3. JSON官网

相关博文

上一篇 Thrift介绍

评论