JavaScript Object Notation(JSON)是一个数据交换标准,因其简单、可读性强广泛使用。Go的标准包encoding/json对JSON
的编解码提供了完整的支持。
编码即将Go数据类型转换为JSON。用到的函数:
func Marshal(v interface{}) ([]byte, error)
该函数递归遍历v的结构,生成对应的JSON例如:
package main
import (
"encoding/json"
"fmt"
)
func main() {
m := map[string][]string{
"level": {"debug"},
"message": {"File not found", "Stack overflow"},
}
if data, err := json.Marshal(m); err == nil {
fmt.Printf("%s\n", data)
}
}
输出:
{"level":["debug"],"message":["File not found","Stack overflow"]}
在编码过程中,json包会将Go的类型转换为JSON类型,转换规则如下:
bool -> JSON boolean
浮点数, 整数, Number -> JSON number
string -> JSON string
数组、切片 -> JSON数组
[]byte -> base64 string
struct、map -> JSON object
经常会使用结构体来转换成JSON。json包是通过反射机制来实现编解码的,因此结构体必须导出所转换的字段,不导出的字段不会被json包解析:
package main
import (
"encoding/json"
"fmt"
)
type DebugInfo struct {
Level string
Msg string
author string // 未导出字段不会被json解析
}
func main() {
dbgInfs := []DebugInfo{
DebugInfo{"debug", `File: "test.txt" Not Found`, "Cynhard"},
DebugInfo{"", "Logic error", "Gopher"},
}
if data, err := json.Marshal(dbgInfs); err == nil {
fmt.Printf("%s\n", data)
}
}
输出结果如下:
[{"Level":"debug","Msg":"File: \"test.txt\" Not Found"},{"Level":"","Msg":"Logic error"}]
json包在解析结构体时,如果遇到key为json
的字段标签,则会按照一定规则解析该标签:第一个出现的是字段在JSON串中使用的名字,之后为其他选项,例如omitempty
指定空值字段不出现在JSON中。如果整个value为"-"
,则不解析该字段。例如将上例中的结构体改为如下:
type DebugInfo struct {
Level string `json:"level,omitempty"` // Level解析为level,忽略空值
Msg string `json:"message"` // Msg解析为message
Author string `json:"-"` // 忽略Author
}
则输出为:
[{"level":"debug","message":"File: \"test.txt\" Not Found"},{"message":"Logic error"}]
json包在解析匿名字段时,会将匿名字段的字段当成该结构体的字段处理:
package main
import (
"encoding/json"
"fmt"
)
type Point struct{ X, Y int }
type Circle struct {
Point
Radius int
}
func main() {
if data, err := json.Marshal(Circle{Point{50, 50}, 25}); err == nil {
fmt.Printf("%s\n", data)
}
}
输出结果:
{"X":50,"Y":50,"Radius":25}
在调用Marshal(v interface{})
时,该函数会判断v是否满足json.Marshaler
或者 encoding.Marshaler
接口,如果满足,则会调用这两个接口来进行转换(如果两个都满足,优先调用json.Marshaler
)。这两个接口定义如下:
// json.Marshaler
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
// encoding.TextMarshaler
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}
下例声明了MarshalJSON()
函数,以使Point满足json.Marshaler
接口:
package main
import (
"encoding/json"
"fmt"
)
type Point struct{ X, Y int }
func (pt Point)MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`{"X":%d,"Y":%d}`, pt.X, pt.Y)), nil
}
func main() {
if data, err := json.Marshal(Point{50, 50}); err == nil {
fmt.Printf("%s\n", data)
}
}
结果如下:
{"X":50,"Y":50}
json包调用encoding.TextMarshaler
接口处理转换时与调用json.Marshaler
接口略有不同,例如将上例的MarshalJSON
改为MarshalText
,使Point满足text.TextMarshaler
:
func (pt Point)MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("{\"X\":%d,\"Y\":%d}", pt.X, pt.Y)), nil
}
则输出结果如下,可见json包在调用MarshalText
时,给字符串加上了双引号:
"{\"X\":50,\"Y\":50}"
将JSON转换为Go数据类型。用到的函数:
func Unmarshal(data []byte, v interface{}) error
此函数将data表示的JSON转换为v:
package main
import (
"encoding/json"
"fmt"
)
func main() {
data := `[{"Level":"debug","Msg":"File: \"test.txt\" Not Found"},` +
`{"Level":"","Msg":"Logic error"}]`
var dbgInfos []map[string]string
json.Unmarshal([]byte(data), &dbgInfos)
fmt.Println(dbgInfos)
}
输出为:
[map[Level:debug Msg:File: "test.txt" Not Found] map[Level: Msg:Logic error]]
在解码过程中,json包会将JSON类型转换为Go类型,转换规则如下:
JSON boolean -> bool
JSON number -> float64
JSON string -> string
JSON数组 -> []interface{}
JSON object -> map
null -> nil
JSON可以转换成结构体。同编码一样,json包是通过反射机制来实现解码的,因此结构体必须导出所转换的字段,不导出的字段不会被json包解析,另外解析时不区分大小写:
package main
import (
"encoding/json"
"fmt"
)
type DebugInfo struct {
Level string
Msg string
author string // 未导出字段不会被json解析
}
func (dbgInfo DebugInfo) String() string {
return fmt.Sprintf("{Level: %s, Msg: %s}", dbgInfo.Level, dbgInfo.Msg)
}
func main() {
data := `[{"level":"debug","msg":"File Not Found","author":"Cynhard"},` +
`{"level":"","msg":"Logic error","author":"Gopher"}]`
var dbgInfos []DebugInfo
json.Unmarshal([]byte(data), &dbgInfos)
fmt.Println(dbgInfos)
}
输出结果如下:
[{Level: debug, Msg: File Not Found} {Level: , Msg: Logic error}]
解码时依然支持结构体字段标签,规则和编码时一样:
package main
import (
"encoding/json"
"fmt"
)
type DebugInfo struct {
Level string `json:"level"` // level 解码为 Level
Msg string `json:"message"` // message 解码为 Msg
Author string `json:"-"` // 忽略Author
}
func (dbgInfo DebugInfo) String() string {
return fmt.Sprintf("{Level: %s, Msg: %s}", dbgInfo.Level, dbgInfo.Msg)
}
func main() {
data := `[{"level":"debug","message":"File Not Found","author":"Cynhard"},` +
`{"level":"","message":"Logic error","author":"Gopher"}]`
var dbgInfos []DebugInfo
json.Unmarshal([]byte(data), &dbgInfos)
fmt.Println(dbgInfos)
}
则结果为:
[{Level: debug, Msg: File Not Found} {Level: , Msg: Logic error}]
编码时,和解码类似,在解码JSON时,如果找不到字段,则查找字段的字段:
package main
import (
"encoding/json"
"fmt"
)
type Point struct{ X, Y int }
type Circle struct {
Point
Radius int
}
func main() {
data := `{"X":80,"Y":80,"Radius":40}`
var c Circle
json.Unmarshal([]byte(data), &c)
fmt.Println(c)
}
输出结果:
{{80 80} 40}
和编码类似,解码时根据参数是否满足json.Unmarshaler
和encoding.TextUnmarshaler
来调用相应函数(若两个函数都存在,则优先调用UnmarshalJSON
)。这两个接口定义如下:
// json.Unmarshaler
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
// encoding.TextUnmarshaler
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}
下例是一个使用json.Unmarshaler
接口的例子(这里不实现解码算法,仅把参数打印出来):
package main
import (
"encoding/json"
"fmt"
)
type Point struct{ X, Y int }
func (Point) UnmarshalJSON(data []byte) error {
fmt.Println(string(data))
return nil
}
func main() {
data := `{"X":80,"Y":80}`
var pt Point
json.Unmarshal([]byte(data), &pt)
}
输出如下:
{"X":80,"Y":80}