Go 语言标准库之 encoding/json 包

encoding/json包实现了json对象的编码和解码,本文对常用的数据结构与json格式之间相互转换进行介绍。

基本使用

encoding/json包中最常用的是Marshal()Unmarshal()函数:

// 返回 v 的 json 编码,会递归对 v 进行处理。
func Marshal(v interface{}) ([]byte, error)

// 解析 json 编码的数据并将结果存入 v 指向的值
func Unmarshal(data []byte, v interface{}) error

一般来说,Marshal()函数会使用以下的基于类型的默认编码格式:

  • 布尔类型编码为json布尔类型;
  • 浮点数、整数和json.Number类型编码为json数字类型;
  • 字符串类型编码为json字符串;
  • 数组和切片类型编码为json数组,但[]byte编码为base64编码字符串,nil切片编码为null
  • 结构体类型编码为json对象,每一个可导出字段(首字母大写)会变成该对象的一个成员。

☕️ 示例代码

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name   string
    Age    int64
    Height float64
}

func main() {
    p1 := Person{
        Name:   "Alice",
        Age:    18,
        Height: 160.7,
    }

    // struct -> json string
    b, err := json.Marshal(p1)
    if err != nil {
        fmt.Printf("json.Marshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("%s\n", b) // {"Name":"Alice","Age":18,"Height":160.7}

    // json string -> struct
    var p2 Person
    err = json.Unmarshal(b, &p2)
    if err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("%#v\n", p2) // main.Person{Name:"Alice", Age:18, Height:160.7}
}

常用序列化/反序列化操作

指定字段名

// tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来
// 在 tag 中添加字段名,json 序列化/反序列化时会使用该字段名
type Person struct {
    Name   string `json:"name"` // 指定 json 序列化/反序列化时使用小写 name
    Age    int64  `json:"age"`  // 指定 json 序列化/反序列化时使用小写 age
    Height float64
}

p := Person{
    Name:   "Alice",
    Age:    18,
    Height: 160.7,
}
{"name":"Alice","age":18,"Height":160.7}

忽略某个字段

// 在 tag 中添加 "-",json 序列化/反序列化时会忽略该字段
type Person struct {
    Name   string  `json:"name"` // 指定 json 序列化/反序列化时使用小写 name
    Age    int64   `json:"age"`  // 指定 json 序列化/反序列化时使用小写 age
    Height float64 `json:"-"`    // 指定 json 序列化/反序列化时忽略此字段
}

p1 := Person{
    Name:   "Alice",
    Age:    18,
    Height: 160.7,
}
// Height 字段被忽略
{"name":"Alice","age":18}

忽略空值字段

⭐️ 未忽略空值字段

type User struct {
    Name  string   `json:"name"`
    Email string   `json:"email"`
    Hobby []string `json:"hobby"`
}

u1 := User{
    Name: "Alice",
}
// 空值字段不会被忽略
{"name":"Alice","email":"","hobby":null}

✏️ 忽略空值字段

// 在 tag 中添加 omitempty 会忽略空值
// 空值指的是 false、0、""、nil 指针、nil 接口、长度为 0 的数组、切片、映射
type User struct {
    Name  string   `json:"name"`
    Email string   `json:"email,omitempty"`
    Hobby []string `json:"hobby,omitempty"`
}

u1 := User{
    Name: "Alice",
}
// 添加 omitempty 后,空值字段会被忽略
{"name":"Alice"}

忽略嵌套结构体空值字段

📚 嵌套结构体

type User struct {
    Name  string   `json:"name"`
    Email string   `json:"email,omitempty"`
    Hobby []string `json:"hobby,omitempty"`
    Profile
}

type Profile struct {
    Website string `json:"site"`
    Slogan  string `json:"slogan"`
}

u1 := User{
    Name:  "Alice",
    Hobby: []string{"足球", "篮球"},
}
// 匿名嵌套 Profile 时,序列化后的 json 串为单层的
{"name":"Alice","hobby":["足球","篮球"],"site":"","slogan":""}

✌ 如果想要变成嵌套的json串,需要改为具名嵌套或定义字段tag

type User struct {
    Name    string   `json:"name"`
    Email   string   `json:"email,omitempty"`
    Hobby   []string `json:"hobby,omitempty"`
    Profile `json:"profile"`
}

type Profile struct {
    Website string `json:"site"`
    Slogan  string `json:"slogan"`
}

u1 := User{
    Name:  "Alice",
    Hobby: []string{"足球", "篮球"},
}
// 定义字段 tag 后,序列化后的 json 串为双层的
{"name":"Alice","hobby":["足球","篮球"],"profile":{"site":"","slogan":""}}

✍ 如果想要忽略嵌套结构体空值字段,仅添加omitempty是不够的,需要使用嵌套的结构体指针

type User struct {
    Name     string   `json:"name"`
    Email    string   `json:"email,omitempty"`
    Hobby    []string `json:"hobby,omitempty"`
    *Profile `json:"profile,omitempty"`
}

type Profile struct {
    Website string `json:"site"`
    Slogan  string `json:"slogan"`
}

u1 := User{
    Name:  "七米",
    Hobby: []string{"足球", "篮球"},
}
{"name":"七米","hobby":["足球","篮球"]}

不修改原结构体忽略空值字段

// 如果不需要将 User 结构体的 Password 字段序列化,但是又不想修改 User 结构体,
// 可以创建另外一个结构体匿名嵌套原 User,同时指定 Password 字段为匿名结构体指针类型,并添加 omitempty 标签
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name     string `json:"name"`
    Password string `json:"password"`
}

type NewUser struct {
    *User              // 匿名嵌套
    Password *struct{} `json:"password,omitempty"`
}

func main() {
    u := User{
        Name:     "Alice",
        Password: "123456",
    }
    b, err := json.Marshal(NewUser{User: &u})
    if err != nil {
        fmt.Printf("json.Marshal u1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("%s\n", b) // {"name":"Alice"}
}

使用匿名结构体添加字段

// 如果想扩展结构体字段,但有时候又没有必要单独定义新的结构体,可以使用匿名结构体简化操作
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name     string `json:"name"`
    Password string `json:"password"`
}

func main() {
    u := User{
        Name:     "Alice",
        Password: "123456",
    }
    // 使用匿名结构体内嵌 User 并添加额外字段Token
    b, err := json.Marshal(struct {
        *User        
        Token string `json:"token"`
    }{
        User:  &u,
        Token: "91je3a4s72d1da96h",
    })
    if err != nil {
        fmt.Printf("json.Marshal u1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("%s\n", b) // {"name":"Alice","password":"123456","token":"91je3a4s72d1da96h"}
}

使用匿名结构体组合多个结构体

// 同理,可以使用匿名结构体来组合多个结构体来序列化与反序列化数据
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name     string `json:"name"`
    Password string `json:"password"`
}

type Post struct {
    ID    int    `json:"id"`
    Title string `json:"title"`
}

func main() {
    u := User{
        Name:     "Alice",
        Password: "123456",
    }

    p := Post{
        ID:    123456,
        Title: "hello world",
    }

    b, err := json.Marshal(struct {
        *User
        *Post
    }{
        User: &u,
        Post: &p,
    })
    if err != nil {
        fmt.Printf("json.Marshal u1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("%s\n", b) // {"name":"Alice","password":"123456","id":123456,"title":"hello world"}

    jsonStr := `{"name":"Alice","password":"123456","id":123456,"title":"Hello World"}`
    var (
        u2 User
        p2 Post
    )
    if err := json.Unmarshal([]byte(jsonStr), &struct {
        *User
        *Post
    }{&u2, &p2}); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("%#v\n", u2) // main.User{Name:"Alice", Password:"123456"}
    fmt.Printf("%#v\n", p2) // main.Post{ID:123456, Title:"Hello World"}
}

处理字符串格式数字

// 如果 json 串使用的是字符串格式的数字,可以在 tag 中添加 string 来告诉 json 包反序列化时从字符串解析相应字段
package main

import (
    "encoding/json"
    "fmt"
)

type Card struct {
    ID    int64   `json:"id,string"`    // 添加 string tag
    Score float64 `json:"score,string"` // 添加 string tag
}

func main() {
    // json 串中使用的是字符串格式的数字,不添加 string tag 反序列会报错
    jsonStr := `{"id": "1234567","score": "88.50"}`
    var c1 Card
    if err := json.Unmarshal([]byte(jsonStr), &c1); err != nil {
        fmt.Printf("json.Unmarsha jsonStr1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("%#v\n", c1) // main.Card{ID:1234567, Score:88.5}
}

整数变为浮点数

// JSON 协议中没有整型和浮点型的区别,它们统称为 number
// 如果将 JSON 格式的数据反序列化为 map[string]interface{} 时,数字都变成科学计数法表示的浮点数
package main

import (
    "encoding/json"
    "fmt"
)

type student struct {
    ID   int64  `json:"id"`
    Name string `json:"q1mi"`
}

func main() {
    s := student{
        ID:   123456789,
        Name: "Alice",
    }
    b, _ := json.Marshal(s)

    var m map[string]interface{}
    _ = json.Unmarshal(b, &m)
    fmt.Printf("%#v\n", m["id"]) // 1.23456789e+08
    fmt.Printf("%T\n", m["id"])  // float64
}

如果想更合理的处理数字,需要使用decoder去反序列化,使用json.Number类型,示例代码如下:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
)

type student struct {
    ID   int64  `json:"id"`
    Name string `json:"q1mi"`
}

func main() {
    s := student{
        ID:   123456789,
        Name: "Alice",
    }
    b, _ := json.Marshal(s)

    // 使用功能 decoder 方式进行反序列,指定使用 number 类型
    var m map[string]interface{}
    decoder := json.NewDecoder(bytes.NewReader(b))
    decoder.UseNumber()
    _ = decoder.Decode(&m)

    // 反序列后,类型为 json.Number 类型
    fmt.Printf("%#v\n", m["id"]) // "123456789"
    fmt.Printf("%T\n", m["id"])  // json.Number

    // 根据字段的实际类型调用 Float64() 或 Int64() 函数获取对应类型
    id, _ := m["id"].(json.Number).Int64()
    fmt.Printf("%#v\n", id) // 123456789
    fmt.Printf("%T\n", id)  // int64
}

json.Number类型的源码定义如下:

// A Number represents a JSON number literal.
type Number string

// String returns the literal text of the number.
func (n Number) String() string { return string(n) }

// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
    return strconv.ParseFloat(string(n), 64)
}

// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
    return strconv.ParseInt(string(n), 10, 64)
}

处理不确定层级的 json

// 如果 json 串没有固定格式导致不好定义与其相对应的结构体时,可以使用 json.RawMessage 将原始字节数据保存下来
package main

import (
    "encoding/json"
    "fmt"
)

type sendMsg struct {
    User string `json:"user"`
    Msg  string `json:"msg"`
}

func main() {
    jsonStr := `{"sendMsg":{"user":"Alice","msg":"永远不要高估自己"},"say":"Hello"}`
    var data map[string]json.RawMessage
    if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
        fmt.Printf("json.Unmarshal jsonStr failed, err:%v\n", err)
        return
    }

    var msg sendMsg
    if err := json.Unmarshal(data["sendMsg"], &msg); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("%#v\n", msg) // main.sendMsg{User:"Alice", Msg:"永远不要高估自己"}
}

自定义序列化/反序列化

// 可以通过实现 json.Marshaler/json.Unmarshaler 接口来实现自定义的事件格式解析
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

☕️ 示例代码

// 内置的 json 包不识别常用的字符串时间格式,如 2022-05-24 14:50:00
package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Post struct {
    ID         int       `json:"id"`
    Title      string    `json:"title"`
    CreateTime time.Time `json:"create_time"`
}

func main() {
    // 序列化
    p := Post{
        ID:         123456,
        Title:      "hello world",
        CreateTime: time.Now(),
    }
    b, err := json.Marshal(p)
    if err != nil {
        fmt.Printf("json.Marshal p1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("%s\n", b) // {"id":123456,"title":"hello world","create_time":"2022-05-24T14:58:45.3702937+08:00"}

    // 反序列化
    jsonStr := `{"id":123456,"title":"hello world","create_time":"2022-05-24 14:50:00"}`
    var p2 Post
    if err := json.Unmarshal([]byte(jsonStr), &p2); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("%#v\n", p2) // json.Unmarshal failed, err:parsing time "\"2022-05-24 14:50:00\"" as "\"2006-01-02T15:04:05Z07:00\"": cannot parse " 14:50:00\"" as "T"
}
// 通过实现 json.Marshaler/json.Unmarshaler 接口来自定义时间字段的事件格式解析
package main

import (
    "encoding/json"
    "fmt"
    "time"
)

const layout = "2006-01-02 15:04:05"

type Post struct {
    ID         int       `json:"id"`
    Title      string    `json:"title"`
    CreateTime time.Time `json:"create_time"`
}

// MarshalJSON 为 Post 类型自定义序列化方法
func (p Post) MarshalJSON() ([]byte, error) {
    type TempPost Post // 定义与 Post 字段一致的新类型
    return json.Marshal(struct {
        *TempPost         // 直接嵌套 Post 会进入死循环,需要使用新类型 TempPost
        CreateTime string `json:"create_time"`
    }{
        TempPost:   (*TempPost)(&p),
        CreateTime: p.CreateTime.Format(layout),
    })
}

// UnmarshalJSON 为 Post 类型自定义反序列化方法
func (p *Post) UnmarshalJSON(data []byte) error {
    type TempPost Post // 定义与 Post 字段一致的新类型
    tp := struct {
        *TempPost        // 直接嵌套 Post 会进入死循环,需要使用新类型 TempPost
        CreateTime string `json:"create_time"`
    }{
        TempPost: (*TempPost)(p),
    }

    if err := json.Unmarshal(data, &tp); err != nil {
        return err
    }

    var err error
    p.CreateTime, err = time.Parse(layout, tp.CreateTime)
    if err != nil {
        return err
    }
    return nil
}

func main() {
    // 序列化
    p := Post{
        ID:         123456,
        Title:      "hello world",
        CreateTime: time.Now(),
    }
    b, err := json.Marshal(p)
    if err != nil {
        fmt.Printf("json.Marshal p1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("%s\n", b) // {"id":123456,"title":"hello world","create_time":"2022-05-24 18:30:03"}

    // 反序列化
    jsonStr := `{"id":123456,"title":"hello world","create_time":"2022-05-24 14:50:00"}`
    var p2 Post
    if err := json.Unmarshal([]byte(jsonStr), &p2); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("%#v\n", p2) // main.Post{ID:123456, Title:"hello world", CreateTime:time.Date(2022, time.May, 24, 14, 50, 0, 0, time.UTC)}
}

参考

  1. 你需要知道的那些go语言json技巧
posted @ 2022-05-24 18:58  呵呵233  阅读(562)  评论(0编辑  收藏  举报