Go 博客
JSON 和 Go
介绍
JSON(JavaScript 对象表示法)是一种简单的 数据交换格式。语法上它类似于 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 类型,它必须是
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 块中挑选几个特定字段时特别有用。这也意味着目标结构体中的任何非导出字段都不会受到 Unmarshal
的影响。
但是,如果您事先不知道 JSON 数据的结构怎么办?
使用接口的通用 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 块反序列化为一个简单的 interface{}
值。默认的具体 Go 类型是
-
JSON 布尔值使用
bool
, -
JSON 数字使用
float64
, -
JSON 字符串使用
string
,以及 -
JSON null 使用
nil
。
解码任意数据
考虑此 JSON 数据,它存储在变量 b
中
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{})
然后,我们可以使用范围语句遍历映射,并使用类型切换来访问其值作为它们的具体类型
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
字段和/或 Msg
字段。Unmarshal
在将 JSON 解码为 IncomingMessage
结构体时,只会分配 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)
}
}
}
由于 Reader 和 Writer 的普遍性,这些 Encoder
和 Decoder
类型可以在各种场景中使用,例如读取和写入 HTTP 连接、WebSockets 或文件。
参考
下一篇文章:Go 变得更加稳定
上一篇文章:Go 切片:用法和内部结构
博客索引