介绍
JSON(JavaScript Object Notation)是一种简单的数据交换格式,语法上和JavaScript非常类似。它广泛应用于web后端与前端JavaScript程序的通信,当然在很多其他场合也非常常用。下面我们先简单介绍一下JSON语法。
JSON语法规则
JSON语法是JavaScript对象表示语法的子集。
- 数据在“名称(key)/值”对中。比如
"firstName" : "John"
. - 数据由逗号分隔。
- 花括号保存对象。
- 方括号保存数组。
JSON值
JSON的值可以是:
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true或false)
- 数组(在方括号中)
- 对象(在花括号中)
- null
JSON对象
JSON对象在花括号中书写,对象可以包含多个名称/值对:
{ "fistName":"John", "lastName":"Doe" }
JSON数组
JSON数组在方括号中书写,数组可包含多个对象:
{
"employee": [
{ "fistName":"John", "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName":"Jones" }
]
}
更多关于JSON信息可访问json.org。下面我们来看Go标准库提供的用于处理JSON格式的数据json包。
编码
我们使用Marshal
函数来编码JSON数据。
func Marshal(v interface{}) ([]byte, error)
比如有一个Go结构体Message和一个它的实例m:
type Message struct {
Name string `json:"name"`
Body string
Time int64
}
m := Message{"Alice", "Hello", 1294706395881547000}
我们可以使用json.Marshal
函数将m编码为JSON格式的数据:
b, err := json.Marshal(m)
如果没有任何错误的话,err的值为nil,而b将是一个[]byte结构,里面存放着JSON数据:
b == []byte(`Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
当然,只有合法的可以用JSON来表示的数据结构才可以被编码为JSON格式的数据,Go中有如下规约:
- JSON对象的key只能是string类型;比如我们要编码Go的Map类型的话,那这个map就必须是
map[string]T
(T是Go的json包里面支持的任意类型)形式的。NB:最新的Go文档中JSON的key可以为string类型或者整数类型或者任何实现encoding.TextMarshaler的类型。 - Channel、复数和函数类型不能被编码。
- 指针将被编码为它指向的值,如果是nil的话就编码为NULL。
- 对于结构体类型,标准库中的json包只能处理导出的字段(大写开头的字段)。一些第三方的json包没有这个限制。
解码
我们可以使用Unmarshal
函数来解码JSON数据:
func Unmarshal(data []byte, v interface{}) error
在调用这个函数之前,我们必须先创建一个位置用于存放解码的数据:
var m Message
然后调用json.Unmarshal,并将[]byte格式的JSON数据和指向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”的字段(比如Message中的name就是Name字段的标签)
- 导出的名称为“Foo”的字段
- 导出的名字为“FOO”、“FoO”等其他大小写不敏感的匹配“Foo”的字段
那如果像下面这样JSON数据中的字段与Go类型的不完全匹配会怎么样?
b := []byte(`{"Name":"Bob", "Food":"Pickle"`)
var m Message
err := json.Unmarshal(b, &m)
对于这种情况,Unmarshal函数只会解码能匹配上的字段,其他的不会受到任何影响。比如此例中,只会解码“Name”字段,“Food”字段将被忽略。当我们只想获取一个很大的JSON数据中的某一些字段时,该特性是非常有用的。但是如果我们事先不知道我们的JSON数据的结构怎么办呢?接着看下一节。
使用interface{}的通用JSON
之前在介绍Go的时候就提过,空接口interface{}可以接受任意类型的数据。所以我们可以使用Go的type assertion
和type switch
两个特性来获取未知数据背后的具体数据类型(如果对这两个特性或者空接口不清楚,请看我之前的博客《Go程序设计语言》要点总结——接口):
// 使用type assertion来访问具体类型
r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)
// 如果底层类型未知,可以使用type switch来确定其类型
switch v := i.(type) {
case int:
fmt.Println("integer")
case float64:
fmt.Println("float64")
case string:
fmt.Println("string")
default:
fmt.Println("Unkonwn type")
标准库的json包使用map[string]interface{}
和[]interface{}
值来存储任意的JSON对象和数组;所以Unmarshal函数可以将任意合法的JSON对象解码到interface{}中。具体Go类型与JSON类型对应关系如下:
- bool对应boolean
- float64对应JSON的数字
- string对应JSON的字符串
- nil对应JSON的null
下一节我们看个例子。
解码任意数据
考虑下面存储于b中的JSON数据:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
我们无需知道b的数据结构,可以直接使用Unmarshal函数将其解码到空接口interface{}里:
var f interface{}
err := json.Unmarshal(b, &f)
此时f的值将是一个map,这个map的key是string类型,value是interface{}类型:
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
我们可以使用之前说的方法,通过type assertion和type switch来访问和迭代获取f中的数据:
m := f.(map[string]interface{})
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case int:
fmt.Println(k, "is int", 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")
}
}
引用类型
我们定义一个Go类型包含前面例子中的数据:
type FamilyMember struct {
Name string
Age int
Parents []string
}
var m FamilyMember
err := json.Unmarshal(b, &m)
我们发现上面代码可以正常解码。但是我们仔细观察发现当使用var声明FamilyMember类型的变量m时,Parents字段值为nil,但是解析数据时却并没有panic(正常往值为nil的引用类型中插入数据会引发panic)。这是因为Unmarshal函数内部为了解码Parents字段,而为其分配了内存。Unmarshal函数这个特性适用于Go中所有的引用类型——指针、Slice、Map。当然,如果数据中没有引用类型的数据,那引用类型依旧为nil。比如对于上面结构体,如果b中没有Parents的数据,那Unmarshal之后,Parents字段为nil。
这个特性有个非常实用的应用场景:比如我们的程序可以接收几种不同类型的消息,比如在后端编程中,经常会接收到控制消息和数据消息,此时我们可以在接收端定义如下结构体:
type IncomingMessage struct {
Cmd *Command
Msg *Message
}
然后发送端程序可以根据自己想发送的消息类型在发送端的JSON格式数据的顶层实现任意一个类型的消息。而接收端我们就可以使用Unmarshal函数来解析消息,我们可以根据哪个字段不为nil来判断发送端发送的消息类型。
JSON流编码和解码
json包里面提供了两个通用的用于流式的操作JSON数据的类型:Decoder
和Encoder
。然后NewDecoder和NewEncoder两个函数封装了io.Reader
和io.Writer
接口类型。
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(r io.Writer) *Encoder
下面的例子程序实现从标准输入读入JSON对象,然后去掉除所有非"Name"字段,然后写到标准输出:
package main
import (
"encoding/json"
"os"
"log"
)
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类型是非常普遍的,所以这种用法可以用在很多场景,比如HTTP连接、WebSocket、文件等。
本文参考自:
膜拜楼主,受益匪浅 :smile:
go的资料比较少,以后会多多学习您写的技术文章
哈哈,欢迎常来,一起学习交流~