31.Go JSON

Go中的标准库encoding/json提供JSON格式的序列化和反序列化功能.

序列化struct为JSON

type Person struct {
    fullName string
    Name     string
    Age      int    `json:"age"`
    City     string `json:"city"`
}

p := Person{
    Name: "John",
    Age:  37,
    City: "SF",
}
d, err := json.Marshal(&p)
if err != nil {
    log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in compact JSON: %s\n", string(d))

d, err = json.MarshalIndent(p, "", "  ")
if err != nil {
    log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in pretty-printed JSON:\n%s\n", string(d))

Person in compact JSON: {"Name":"John","age":37,"city":"SF"}
Person in pretty-printed JSON:
{
"Name": "John",
"age": 37,
"city": "SF"
}

json.Marshal和json.MarshalIndent都将interface {}作为第一个参数。我们可以传递任何Go值,并将其类型包装到interface {}中。

Marshaller将使用反射来检查传递的值并将其编码为JSON字符串。

在序列化struct时,仅对导出的字段(其名称以大写字母开头)进行序列化/反序列化。

在我们的示例中,未对fullName进行序列化。

struct被序列化为JSON字典。默认情况下,字典键与struct字段名称相同。

struct字段名称在字典键名称下序列化。

可以提供带有struct标签的自定义映射。

可以将任意的struct标签字符串附加到struct字段。

json:"age"指示JSON编码器/解码器使用名称age作为表示字段Age的字典关键字。

序列化struct时,将值和指针传递给它会产生相同的结果。

传递指针效率更高,因为按值传递会创建不必要的副本。

json.MarshallIndent格式化打印嵌套struct, 这样会占用更多空间但更易于阅读。

把JSON转为struct

type Person struct {
    Name       *string `json:"name"`
    Age        int     `json:"age"`
    City       string
    Occupation string
}

var jsonStr = `{
    "name": "Jane",
    "age": 24,
    "city": "ny"
}`

var p Person
err := json.Unmarshal([]byte(jsonStr), &p)
if err != nil {
    log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
fmt.Printf("Person struct parsed from JSON: %#v\n", p)
fmt.Printf("Name: %#v\n", *p.Name)

Person struct parsed from JSON: main.Person{Name:(*string)(0xc000010330), Age:24, City:"ny", Occupation:""}
Name: "Jane"

解析与序列化相反

与序列化不同,在解析为结构时,必须将指针传递给struct。否则json.Unmarshal将接收并修改该结构的副本,而不是结构本身。从json.Unmarshal返回后,该副本将被丢弃。

请注意,即使名称不匹配,JSON元素city也被解码为City struct字段,并且我们没有使用json struct标签提供显式映射。

发生这种情况是因为在将字典关键字名称与结构字段名称进行匹配时,JSON解码器具有一些技巧。最好不要依赖这种智能,而是明确定义映射。

所有struct字段都是可选的,并且当不以JSON文本形式出现时,其值将保持不变。当解码为新初始化的struct时,对于给定类型,其值为零。

字段名称显示JSON解码器还可以自动将其解码为指向值的指针。

当您需要知道JSON中是否存在值时,这很有用。如果我们使用字符串作为Name,我们将不知道空字符串的值是否意味着JSON具有以空字符串作为值的名称键,或者是因为该值根本不存在。

通过使用指向字符串的指针,我们知道nil表示没有值。

JSON和Go类型映射:

  • JSON类型 Go类型
  • boolean bool
  • number float64 or int
  • string string
  • array slice
  • dictionary map[struct]interface{} or struct
  • null nil

解析任意JSON

解析为一个结构非常方便,但有时我们不知道JSON的结构。

对于任意JSON,我们可以解码为map[string]interface{},它可以表示任意有效的JSON。

var jsonStr = `{
    "name": "Jane",
    "age": 24,
    "city": "ny"
}`

var doc map[string]interface{}
err := json.Unmarshal([]byte(jsonStr), &doc)
if err != nil {
    log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
fmt.Printf("doc: %#v\n", doc)
name, ok := doc["name"].(string)
if !ok {
    log.Fatalf("doc has no key 'name' or its value is not string\n")
}
fmt.Printf("name: %#v\n", name)

doc: map[string]interface {}{"age":24, "city":"ny", "name":"Jane"}
name: "Jane"

对于基本JSON类型,映射中的值为bool,int,float64或string。

对于JSON数组,该值为[] interface {}。

对于JSON字典,该值是(再次)map [string] interface {}。

这种方法很灵活,但是处理map [string] interface {}以访问值是很痛苦的。

反序列化为匿名struct

解析为结构时,我们可以使用匿名struct来避免声明结构类型。

var jsonBlob = []byte(`
{
  "_total": 1,
  "_links": {
    "self": "https://api.twitch.tv/kraken/channels/foo/subscriptions?direction=ASC&limit=25&offset=0",
    "next": "https://api.twitch.tv/kraken/channels/foo/subscriptions?direction=ASC&limit=25&offset=25"
  },
  "subscriptions": [
    {
      "created_at": "2011-11-23T02:53:17Z",
      "_id": "abcdef0000000000000000000000000000000000",
      "_links": {
        "self": "https://api.twitch.tv/kraken/channels/foo/subscriptions/bar"
      },
      "user": {
        "display_name": "bar",
        "_id": 123456,
        "name": "bar",
        "created_at": "2011-06-16T18:23:11Z",
        "updated_at": "2014-10-23T02:20:51Z",
        "_links": {
          "self": "https://api.twitch.tv/kraken/users/bar"
        }
      }
    }
  ]
}
`)

var js struct {
    Total int `json:"_total"`
    Links struct {
        Next string `json:"next"`
    } `json:"_links"`
    Subs []struct {
        Created string `json:"created_at"`
        User    struct {
            Name string `json:"name"`
            ID   int    `json:"_id"`
        } `json:"user"`
    } `json:"subscriptions"`
}

err := json.Unmarshal(jsonBlob, &js)
if err != nil {
    fmt.Println("error:", err)
}
fmt.Printf("%+v", js)

{Total:1 Links:{Next:https://api.twitch.tv/kraken/channels/foo/subscriptions?direction=ASC&limit=25&offset=25} Subs:[{Created:2011-11-23T02:53:17Z User:{Name:bar ID:123456}}]}

从文件反序列化JSON

我们可以从磁盘上的文件,或者任何io.Reader,比如网络连接来反序列化JSON.
下面的例子从文件读取JSON并反序列化:

type Student struct {
    Name     string
    Standard int `json:"Standard"`
}

func decodeFromReader(r io.Reader) ([]*Student, error) {
    var res []*Student

    dec := json.NewDecoder(r)
    err := dec.Decode(&res)
    if err != nil {
        return nil, err
    }
    return res, nil
}

func decodeFromString(s string) ([]*Student, error) {
    r := bytes.NewBufferString(s)
    return decodeFromReader(r)
}

func decodeFromFile(path string) ([]*Student, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    return decodeFromReader(f)
}

Student: John Doe, standard: 4
Student: Peter Parker, standard: 11
Student: Bilbo Baggins, standard: 150

通过编写帮助函数decodeFromReader,我们可以轻松编写可用于文件,字符串或网络连接的包装器。

配置JSON序列化

隐藏/跳过某些字段

要导出Revenue和sales但不对它们进行编码/解码,请使用json:“-”或重命名变量以小写字母开头。 请注意,这将防止变量在包外部可见。

type Company struct {
    Name     string `json:"name"`
    Location string `json:"location"`
    Revenue  int    `json:"-"`
    sales    int
}

忽略空字段

为了防止Location设置为零值时将其包含在JSON中,请将,omitempty添加到json标记中。

type Company struct {
    Name     string `json:"name"`
    Location string `json:"location,omitempty"`
}

自定义JSON序列化

编写自定义JSON序列化
有时,类型没有明显的JSON映射。

如何序列化time.Time? 有很多可能性。

Go为time.Time提供了默认的JSON映射。 我们可以为用户定义的类型(例如struct)实现自定义序列化。

对于现有类型,我们可以定义一个新的(但兼容)类型。

这是时间的自定义序列化。时间仅序列化年/月/日部分:

type Event struct {
    What string
    When time.Time
}
e := Event{
    What: "earthquake",
    When: time.Now(),
}
d, err := json.Marshal(&e)
if err != nil {
    log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Standard time JSON: %s\n", string(d))

type customTime time.Time

const customTimeFormat = `"2006-02-01"`

func (ct customTime) MarshalJSON() ([]byte, error) {
    t := time.Time(ct)
    s := t.Format(customTimeFormat)
    return []byte(s), nil
}

func (ct *customTime) UnmarshalJSON(d []byte) error {
    t, err := time.Parse(customTimeFormat, string(d))
    if err != nil {
        return err
    }
    *ct = customTime(t)
    return nil
}

type Event2 struct {
    What string
    When customTime
}

e := Event2{
    What: "earthquake",
    When: customTime(time.Now()),
}
d, err := json.Marshal(&e)
if err != nil {
    log.Fatalf("json.Marshal failed with '%s'\n", err)
}
fmt.Printf("\nCustom time JSON: %s\n", string(d))
var decoded Event2
err = json.Unmarshal(d, &decoded)
if err != nil {
    log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
t := time.Time(decoded.When)
fmt.Printf("Decoded custom time: %s\n", t.Format(customTimeFormat))

notCustom()
custom()

Standard time JSON: {"What":"earthquake","When":"2019-11-06T02:18:11.203193337Z"}
Custom time JSON: {"What":"earthquake","When":"2019-06-11"}
Decoded custom time: "2019-06-11"

请注意,UnmashalJSON的接收者类型是指向该类型的指针。

这对于将更改保留在函数本身之外是必要的。

带私有字段的封送结构
考虑具有已导出和未导出字段的结构:

type MyStruct struct {
    uuid string
    Name string
}

想象一下,您想将该结构Marshal()转换为有效的JSON,以便存储在etcd之类的文件中。

但是,由于未导入uuid,因此json.Marshal()跳过了它。

要封送私有字段而不将其公开,我们可以使用自定义封送程序:

type MyStruct struct {
    uuid string
    Name string
}

func (m MyStruct) MarshalJSON() ([]byte, error) {
    j, err := json.Marshal(struct {
        Uuid string
        Name string
    }{
        Uuid: m.uuid,
        Name: m.Name,
    })
    if err != nil {
        return nil, err
    }
    return j, nil
}

s := MyStruct{
    uuid: "uid-john",
    Name: "John",
}
d, err := json.Marshal(&s)
if err != nil {
    log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in compact JSON: %s\n", string(d))

Person in compact JSON: {"Uuid":"uid-john","Name":"John"}

幕后的自定义封送处理
自定义封送处理如何工作?

包JSON定义了2个接口:Marshaler和Unmarshaler。

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

通过实现这些功能,我们使我们的类型符合Marshaler或Unmarshaler接口。

JSON编码器/解码器检查被编码的值是否符合那些接口,并将调用这些函数而不执行默认逻辑。

Go和JSON类型映射

JSON type Go type
null nil
boolean bool
number float64 or int
string string
array slice
dictionary map[struct]interface{} or struct

使用示例:

func printSerialized(v interface{}, w io.Writer) {
    d, err := json.Marshal(v)
    if err != nil {
        log.Fatalf("json.Marshal failed with '%s'\n", err)
    }
    fmt.Fprintf(w, "%T\t%s\n", v, string(d))
}

w := new(tabwriter.Writer)
w.Init(os.Stdout, 5, 0, 1, ' ', 0)
fmt.Fprint(w, "Go type:\tJSON value:\n")
fmt.Fprint(w, "\t\n")
printSerialized(nil, w)
printSerialized(5, w)
printSerialized(8.23, w)
printSerialized("john", w)
ai := []int{5, 4, 18}
printSerialized(ai, w)
a := []interface{}{4, "string"}
printSerialized(a, w)
d := map[string]interface{}{
    "i": 5,
    "s": "foo",
}
printSerialized(d, w)
s := struct {
    Name string
    Age  int
}{
    Name: "John",
    Age:  37,
}
printSerialized(s, w)
w.Flush()

Go type: JSON value:

null
int 5
float64 8.23
string "john"
[]int [5,4,18]
[]interface {} [4,"string"]
map[string]interface {} {"i":5,"s":"foo"}
struct { Name string; Age int } {"Name":"John","Age":37}

轻松生成JSON结构定义

编写映射JSON文件结构的结构定义很繁琐。

如果您有示例JSON文件,则可以使用在线工具自动生成Go定义:

https://app.quicktype.io/
https://mholt.github.io/json-to-go/

你可能感兴趣的:(31.Go JSON)