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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: