编码(生成 json)

开发过程中很多时候需要生成 json 格式的数据格式,golang 使用 JSON 包里面通过 Marshal 函数来编码,函数定义如下:

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

例如 OpenSDS 中的 base 数据模型:

package main

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

type BaseModel struct {
    // The uuid of the object, it's unique in the context and generated by system
    // on successful creation of the object. It's not allowed to be modified by
    // the user.
    // +readOnly
    Id string `json:"id"`

    // CreateAt representing the server time when the object was created successfully.
    // Now, it's represented as a time string in RFC8601 format.
    // +readOnly
    CreatedAt string `json:"createdAt"`

    // UpdatedAt representing the server time when the object was updated successfully.
    // Now, it's represented as a time string in RFC8601 format.
    // +readOnly
    UpdatedAt string `json:"updatedAt"`
}

func main() {
    base := BaseModel{"test", "2018-01-01", "2018-01-01"}
    data, err := json.Marshal(base)
    if err != nil {
        log.Fatal("JSON marshaling failed: %s", err)
    }
    fmt.Printf("%s\n", data)
}

输出是:{"id":"test","createdAt":"2018-01-01","updatedAt":"2018-01-01"}

为了生成便于阅读的格式,另一个 json.MarshalIndent 函数将产生整齐缩进的输出。该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进:

func main() {
    base := BaseModel{"test", "2018-01-01", "2018-01-01"}
    data, err := json.MarshalIndent(base, "", " ")
    if err != nil {
        log.Fatal("JSON marshaling failed: %s", err)
    }
    fmt.Printf("%s\n", data)
}

输出:

{
    "id": "test",
    "createdAt": "2018-01-01",
    "updatedAt": "2018-01-01"
}

几点需要注意:

1、只有导出的结构体成员才会被编码,所以结构体成员都要用大写。
2、如果要导出跟结构体成员命名不同的字段,可以用 struct tag,格式如:

UpdatedAt string `json:"updatedAt"`

如果字段的 tag 是 "-",那么这个字段不会输出到 JSON:

DontOutput string `json:"-"`

tag 中如果带有 "omitempty" 选项,那么如果该字段值为空,就不会输出到 JSON:

RecoveryTimeObjective int64 `json:"recoveryTimeObjective,omitempty"`

如果字段类型是 bool,float,int,int64 等数值类型,而 tag 中带有 "string" 选项,那么这个字段在输出到 JSON 的时候会把该字段对应的值转换成JSON字符串。

 Money    float64 `json:"money,string"`

解码 JSON

解析 JSON 通过函数 json.Unmarshal 完成:

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

例如:

var base BaseModel
data := []byte(`{"id":"test","createdAt":"2018-01-01","updatedAt":"2018-01-01"}`)
if err := json.Unmarshal(data, &base); err != nil {
    log.Fatal("JSON marshaling failed: %s", err)
}

在解码前,先要定义一个与 json 数据对应的结构体。解析式,Golang 会按照以下顺序查找匹配:

  1. 首先查找tag含有Foo的可导出的struct字段(首字母大写)
  2. 其次查找字段名是Foo的导出字段
  3. 最后查找类似FOO或者FoO这样的除了首字母之外其他大小写不敏感的导出字段

例如:
"Foo" 会先匹配

Foo string

找不到就会找:

Test string `json:"Foo"`

再找不到就匹配:

FoO string

能够被赋值的字段必须是可导出字段,即必须大写。这样我们就可以把自己不想导出或者不想被赋值的结构体成员命名为小写。