教程:在Go中处理JSON
JSON(JavaScript Object Notation,JavaScript对象表示法)是一种简单的数据交换格式。它的语法类似JavaScript的对象与列表,常用于与网络API服务通信,也广泛用于其他场景。json.org给出了清楚而简洁的标准定义。
借助encoding/json/v2包,Go程序可以轻松读写JSON。与旧的encoding/json包相比,这个包提供了更清晰的API和更好的默认行为。
编码
使用Marshal函数编码JSON:
func Marshal(in any, opts ...Options) (out []byte, err error)
给定Go数据结构Message:
type Message struct {
Name string
Body string
Time time.Time
}
以及一个实例:
m := Message{"Alice", "Hello", time.Date(2011, 1, 25, 0, 0, 0, 0, time.UTC)}
可用json.Marshal将m编码成JSON:
b, err := json.Marshal(m)
如果一切正常,err为nil,b是包含以下JSON数据的[]byte:
b == []byte(`{"Name":"Alice","Body":"Hello","Time":"2011-01-25T00:00:00Z"}`)
只有能表示为有效JSON的数据结构才能编码:
- 指针编码为其指向的值;若指针为
nil,则编码为null。 - JSON对象的键只能是字符串。因此,Go映射的键必须能编码成JSON字符串,例如
map[string]T,其中T是json包支持的任意Go类型。 - 通道、复数和函数类型不能编码。
- 不支持循环数据结构。
- 完整的编码语义见Marshal文档。
json包只访问结构体类型的导出字段,即名称以大写字母开头的字段。因此,JSON输出中只会出现结构体的导出字段。
解码
使用Unmarshal解码JSON:
func Unmarshal(in []byte, out any, opts ...Options) (err error)
先创建一个用于保存解码结果的变量:
var m Message
再将JSON数据的[]byte和指向m的指针传给json.Unmarshal:
err := json.Unmarshal(b, &m)
如果b包含适合存入m的有效JSON,调用后err为nil,数据已存入结构体m,效果相当于:
m = Message{
Name: "Alice",
Body: "Hello",
Time: time.Date(2011, 1, 25, 0, 0, 0, 0, time.UTC),
}
Unmarshal怎样决定将数据存入哪个字段?对于JSON键"Foo",它按以下优先顺序查找目标结构体的字段:
- 带有
json:"Foo"标签的导出字段;结构体标签详见Go语言规范。 - 名为
Foo的导出字段。
JSON数据结构与Go类型不完全一致时会怎样?
b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)
Unmarshal只解码能在目标类型中找到的字段。本例只会填充m.Name,Food被忽略。需要从一大段JSON中提取少数指定字段时,这种行为很有用。目标结构体中的未导出字段也不会受Unmarshal影响。
如果事先不知道JSON的数据结构呢?
用any处理通用JSON
any可以表示任意Go类型。它定义为interface{},即没有任何方法的空接口。所有Go类型都至少实现零个方法,因此都满足空接口。
any可用作通用容器类型:
var a any
a = "a string"
a = 2011
a = 2.777
类型断言可访问其底层具体类型:
r := a.(float64)
fmt.Println("the circle's area", math.Pi*r*r)
若底层类型未知,可以用类型选择语句确定类型:
switch v := a.(type) {
case int:
fmt.Println("twice a is", v*2)
case float64:
fmt.Println("the reciprocal of a is", 1/v)
case string:
h := len(v) / 2
fmt.Println("a swapped by halves is", v[h:]+v[:h])
default:
// a isn't one of the types above
}
json包使用map[string]any和[]any保存任意JSON对象与数组,也可以把任何有效JSON解码到普通的any值中。默认使用的具体Go类型如下:
- JSON布尔值对应
bool。 - JSON数值对应
float64。 - JSON字符串对应
string。 - JSON的
null对应nil。
解码任意数据
考虑保存在变量b中的JSON:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
无需事先知道结构,也能用Unmarshal将其解码为any:
var f any
err := json.Unmarshal(b, &f)
此时f中的Go值是一个映射,键为字符串,各个值也以any形式存储:
f = map[string]any{
"Name": "Wednesday",
"Age": 6,
"Parents": []any{
"Gomez",
"Morticia",
},
}
可以通过类型断言访问f底层的map[string]any:
m := f.(map[string]any)
再用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 []any:
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语句分配了一个结构体,并把其指针传给Unmarshal;当时Parents仍是nil切片。为填充该字段,Unmarshal在内部新分配了切片。这是它处理所支持的引用类型——指针、切片和映射——的典型方式。
再考虑将JSON解码到以下结构:
type Foo struct {
Bar *Bar
}
若JSON对象中有Bar字段,Unmarshal会分配并填充一个新的Bar;否则该字段仍为nil指针。
由此可以得到一种实用模式:如果应用接收几种不同消息,可定义这样的接收结构:
type IncomingMessage struct {
Cmd *Command
Msg *Message
}
发送方根据消息类型填充顶层JSON对象的Cmd和/或Msg字段。解码为IncomingMessage时,Unmarshal只为JSON中实际存在的数据分配结构。程序只需检查Cmd或Msg是否非nil,就能确定需要处理哪类消息。
流式编码与解码
Go中的io.Reader和io.Writer接口无处不在,用于以流的方式访问HTTP连接、WebSocket或文件等资源。MarshalWrite与UnmarshalRead可以直接向这些流编码或从中解码,无需先用一个中间[]byte保存整条消息。
func MarshalWrite(out io.Writer, in any, opts ...Options) (err error)
func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error)
例如,直接将编码结果写入标准输出:
err := json.MarshalWrite(os.Stdout, m)
自定义编码与解码
有时,默认编码行为并不适合某个类型。例如,假设有一个表示软件版本的类型:
type Version struct {
Major, Minor, Patch int64
}
v := Version{1, 2, 3}
b, err := json.Marshal(s)
按照结构体定义,它会编码为{"Major":1,"Minor":2,"Patch":3};不过,对于JSON的使用方,"1.2.3"这样的版本字符串可能更合适。
实现MarshalerTo接口,可提供自定义JSON表示;UnmarshalerFrom则负责从该表示解码:
type MarshalerTo interface {
MarshalJSONTo(*jsontext.Encoder) error
}
type UnmarshalerFrom interface {
UnmarshalJSONFrom(*jsontext.Decoder) error
}
实现这两个方法,即可在版本值和字符串表示之间转换:
func (v Version) MarshalJSONTo(enc *jsontext.Encoder) error {
return json.MarshalEncode(enc, fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch))
}
自定义编码后,Version{1, 2, 3}会编码为"1.2.3"。
func (v *Version) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
if k := dec.PeekKind(); k != jsontext.KindString {
// Value must be a string.
return &json.SemanticError{JSONKind: k}
}
var s string
if err := json.UnmarshalDecode(dec, &s); err != nil {
return err
}
_, err := fmt.Sscanf(s, "%d.%d.%d", &v.Major, &v.Minor, &v.Patch)
return err
}
自定义解码后,"1.2.3"会解码为Version{1, 2, 3}。这个简单示例要求严格采用"major.minor.patch"格式;更复杂的UnmarshalJSONFrom可以增加灵活性,例如允许省略次版本号和补丁号。
参考资料
更多信息见encoding/json/v2和encoding/json/jsontext包文档。
原始代码许可证全文
Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.











暂无评论内容