JSON 和 Go
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",
},
}
要访问此数据我们可以使用类型断言来访问 f
的 map[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 数据中的数据结构. 要知道要处理哪些消息, 程序员只需测试 Cmd
或 Msg
不是 nil
.
流失编码器和解码器
程序包 json 提供 Decoder
和 Encoder
类型来支持读取和写入 JSON 数据流的通用操作. NewDecoder
和 NewEncoder
函数包装了 io.Reader
和 io.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)
}
}
}
由于读取器和写入器无处不在, 因此这里的 Encoder
和 Decoder
类型可以在广泛的场景中使用, 例如对 HTTP 连接, WebSocket 或文件的读写.
引用
有关更多信息可参阅 json 程序包文档. 有关 json 程序包的用法可参阅 jsonrpc 程序包.
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: