JSON 和 Go

未匹配的标注

本文为官方 Go Blog 的中文翻译,详见 翻译说明

Andrew Gerrand
2011 年 1 月 25 日

介绍

JSON (JavaScript Object Notation) 是一种简洁的数据交换格式. 从语法上讲, 其类似于 JavaScript 的对象和列表. 最常用于 Web 后端和浏览器中运行的 JavaScript 程序之间的通信, 但也用于许多其他地方. 它的主页, json.org 提供了一个清晰且简洁的标准定义.

使用 json 程序包, 可以轻松地从 Go 程序中读取和写入 JSON 数据.

编码

要编码 JSON 数据, 我们可以使用 Marshal 函数.

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

给定 Go 数据结构 Message,

type Message struct {
    Name string
    Body string
    Time int64
}

并实例化 Message

m := Message{"Alice", "Hello", 1294706395881547000}

我们可以使用 json.Marshal 封装 m 的 JSON 编码版本:

b, err := json.Marshal(m)

如果一切顺利, err 将为 nil 并且 b 将为包含以下 JSON 数据的一个 []byte:

b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)

只有可以表示为有效 JSON 的数据结构才会被编码:

  • JSON 对象仅支持将字符串作为键; 要编码 Go 集合类型, 它必须采用 map[string]T 的形式(其中 T 是 json 程序包支持的任何 Go 类型).

  • 无法对通道, 复杂类型和函数类型进行编码.

  • 不支持循环数据结构; 它们会导致 Marshal 函数陷入无限循环.

  • 指针将被编码为其指向的值(如果指针为 nil 则为 'null').

程序包 json 仅访问结构类型 (以大写字母开头的结构类型) 的导出字段. 因此, 仅结构体的的可导出字段会出现在 JSON 输出中.

解码

为了解码 JSON 数据, 我们使用 Unmarshal 函数.

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

我们必须先创建一个存储解码数据的地方

var m Message

并调用 json.Unmarshal 函数, 向其传递 JSON 数据的 []byte 和指向 m 的指针

err := json.Unmarshal(b, &m)

如果 b 包含适用于 m 的有效 JSON, 则在调用 err 后将为 nil, 并且来自 b 将存储在结构 m 中, 类似如下分配:

m = Message{
    Name: "Alice",
    Body: "Hello",
    Time: 1294706395881547000,
}

Unmarshal 函数如何识别存储解码数据的字段? 对于给定的 JSON 键 "Foo"Unmarshal 将遍历目标结构的字段以查找 (按优先顺序):

  • 标记为 "Foo" 的导出字段 (有关结构体标签的更多信息, 请参见 Go 规定),
  • 名为 "Foo" 的导出字段, 或者
  • 名为 "FOO""FoO" 的导出字段, 或 "Foo" 的其他一些不区分大小写的匹配项.

当 JSON 数据的结构与 Go 类型不完全匹配时会发生什么?

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

Unmarshal 函数将仅解码在目标类型中可以找到的字段. 在这种情况下, 将仅填充 m 的 Name 字段, 而 Food 字段将被忽略. 当您希望从大型 JSON Blob 中仅选择几个特定字段时, 此行为特别有用. 这也意味着目标结构体中任何未导出的字段都不会受到 Unmarshal 的影响.

但是, 如果您事先不知道 JSON 数据的结构时怎么办?

带 interface{} 的通用 JSON

interface{} (空接口) 类型描述了一个零方法的接口. 每个 Go 类型都至少实现零个方法, 因此满足空接口.

空接口用作常规容器类型:

var i interface{}
i = "a string"
i = 2011
i = 2.777

类型断言访问基础的具体类型:

r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)

或者, 如果基础类型未知, 则由类型开关确定类型:

switch v := i.(type) {
case int:
    fmt.Println("twice i is", v*2)
case float64:
    fmt.Println("the reciprocal of i is", 1/v)
case string:
    h := len(v) / 2
    fmt.Println("i swapped by halves is", v[h:]+v[:h])
default:
    // i isn't one of the types above
}

程序包 json 使用 map[string]interface{}[]interface{} 值存储任意 JSON 对象和数组; 它将很乐意将任何有效的 JSON Blob 解组为一个简单的 interface{} 值. 默认的具体 Go 类型是:

  • bool 对照为 JSON 布尔值,
  • float64 对照为 JSON 数值,
  • string 对照为 JSON 字符串, 以及
  • nil 对照为 JSON 空.

解码任意数据

考虑一下存储在变量 b 中的 JSON 数据:

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

在不知道此数据结构的情况下, 我们可以使用 Unmarshal 将其解码为 interface{} 值:

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

此时 f 中的 Go 值将是一个集合, 其键为字符串, 其值本身存储为空接口值:

f = map[string]interface{}{
    "Name": "Wednesday",
    "Age":  6,
    "Parents": []interface{}{
        "Gomez",
        "Morticia",
    },
}

要访问此数据我们可以使用类型断言来访问 fmap[string]interface{}:

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

然后我们可以使用 range 语句遍历集合, 并使用类型开关将其值作为其具体类型来访问:

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")
    }
}

这样您就可以使用未知的 JSON 数据, 同时仍然享有类型安全的好处.

引用类型

让我们定义一个 Go 类型以包含上一个示例中的数据:

type FamilyMember struct {
    Name    string
    Age     int
    Parents []string
}

    var m FamilyMember
    err := json.Unmarshal(b, &m)

将数据解封为 FamilyMember 值可以按预期工作, 但是如果仔细观察, 我们可以看到发生了一件了不起的事情. 通过 var 语句, 我们分配了一个 FamilyMember 结构, 然后将指向该值的指针提供给 Unmarshal 函数, 但是那时 Parents 字段是一个 nil 切片值. 为了填充 Parents 字段, Unmarshal 函数在幕后分配了一个新切片. 这就是 Unmarshal 函数如何与支持的引用类型 (指针, 切片和集合) 一起使用的典型方法.

考虑解封到此数据结构中:

type Foo struct {
    Bar *Bar
}

如果 JSON 对象中有一个 Bar 字段, 则 Unmarshal 将分配一个新的 Bar 并填充它. 如果不是, Bar 将保留为 nil 指针.

由此产生一种有用的模式: 如果您的应用程序接收一些不同的消息类型, 则可以定义 "接收器" 结构, 例如

type IncomingMessage struct {
    Cmd *Command
    Msg *Message
}

发送方可以根据他们想要传达的消息类型来填充 Cmd 字段和/或顶级 JSON 对象的 Msg 字段. 当将 JSON 解码为 IncomingMessage 结构时, Unmarshal 仅将分配存在于 JSON 数据中的数据结构. 要知道要处理哪些消息, 程序员只需测试 CmdMsg 不是 nil.

流失编码器和解码器

程序包 json 提供 DecoderEncoder 类型来支持读取和写入 JSON 数据流的通用操作. NewDecoderNewEncoder 函数包装了 io.Readerio.Writer 接口类型.

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

这是一个示例程序, 该程序从标准输入读取一系列 JSON 对象, 从每个对象中删除了除 Name 字段之外的所有字段, 然后将这些对象写入标准输出:

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)
        }
    }
}

由于读取器和写入器无处不在, 因此这里的 EncoderDecoder 类型可以在广泛的场景中使用, 例如对 HTTP 连接, WebSocket 或文件的读写.

引用

有关更多信息可参阅 json 程序包文档. 有关 json 程序包的用法可参阅 jsonrpc 程序包.

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/go-blog/json-an...

译文地址:https://learnku.com/docs/go-blog/json-an...

上一篇 下一篇
Summer
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~