Go操作JSON

简介

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包是通过反射机制来实现编解码的,因此结构体必须导出所转换的字段,不导出的字段不会被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包是通过反射机制来实现解码的,因此结构体必须导出所转换的字段,不导出的字段不会被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}
```~~~~~~~~