原文地址:JSON and Go - The Go Programming Language
安德鲁·杰朗
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}
我们可以使用以下方法编组 m 的 JSON 编码版本json.Marshal
:
b, err := json.Marshal(m)
如果一切顺利,err
将是nil
并且b
将是[]byte
包含此 JSON 数据的:
b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
只有可以表示为有效 JSON 的数据结构才会被编码:
-
JSON 对象只支持字符串作为键;要对 Go 地图类型进行编码,它必须采用以下形式
map[string]T
(T
json 包支持的任何 Go 类型在哪里)。 -
通道、复杂和函数类型无法编码。
-
不支持循环数据结构;它们将导致
Marshal
进入无限循环。 -
指针将被编码为它们指向的值(如果指针为 ,则为“空”
nil
)。
json 包仅访问结构类型的导出字段(以大写字母开头的字段)。因此,只有结构的导出字段才会出现在 JSON 输出中。
解码
为了解码 JSON 数据,我们使用该Unmarshal函数。
func Unmarshal(data []byte, v interface{}) error
我们必须首先创建一个存储解码数据的地方
var m Message
并调用json.Unmarshal
,传递一个[]byte
JSON 数据和一个指向m
err := json.Unmarshal(b, &m)
如果b
包含适合的有效 JSON,m
则在调用之后,并且来自的数据err
将存储在 struct中,就好像通过如下赋值:nil
b
m
m = Message{
Name: "Alice",
Body: "Hello",
Time: 1294706395881547000,
}
如何Unmarshal
识别存储解码数据的字段?对于给定的 JSON key "Foo"
, Unmarshal
将查看目标结构的字段以查找(按优先顺序):
-
带有标签的导出字段( 有关结构标签的更多信息,
"Foo"
请参见Go 规范), -
一个名为 的导出字段
"Foo"
,或 -
"FOO"
一个名为or"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 数据的结构怎么办?
带接口的通用 JSON
( interface{}
empty 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
for JSON booleans, -
float64
for JSON numbers, -
string
for JSON strings, and -
nil
for JSON null.
解码任意数据
考虑存储在变量中的 JSON 数据b
:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
在不知道该数据结构的情况下,我们可以将其解码为一个interface{}
值Unmarshal
:
var f interface{}
err := json.Unmarshal(b, &f)
此时的 Go 值f
将是一个映射,其键是字符串,其值本身存储为空接口值:
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
要访问这些数据,我们可以使用类型断言来访问f
底层map[string]interface{}
:
m := f.(map[string]interface{})
然后我们可以使用 range 语句遍历 map 并使用类型开关来访问其值作为它们的具体类型:
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
}
并且发送方可以填充顶级 JSON 对象 的Cmd
字段和/或字段,具体取决于他们想要传达的消息类型。,在将 JSON 解码为结构时,只会分配 JSON 数据中存在的数据结构。要知道要处理哪些消息,程序员只需测试or 或is not 。Msg
Unmarshal
IncomingMessage
Cmd
Msg
nil
流式编码器和解码器
json 包提供Decoder
和Encoder
类型来支持读取和写入 JSON 数据流的常见操作。NewDecoder
andNewEncoder
函数包装了andio.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)
}
}
}
由于 Readers 和 Writers 无处不在,这些Encoder
和Decoder
类型可用于广泛的场景,例如读取和写入 HTTP 连接、WebSocket 或文件。