• 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技巧
  • 相关阅读:
    CQOI2009中位数图
    CQOI2011分金币&HAOI2008糖果传递
    SCOI2010游戏
    JSOI2007建筑抢修
    HNOI2008明明的烦恼
    SCOI2009生日快乐
    (22/24) webpack实战技巧:静态资源集中输出
    (22/24) webpack实战技巧:静态资源集中输出
    [mysql]linux mysql 读写分离
    [mysql]linux mysql 读写分离
  • 原文地址:https://www.cnblogs.com/zongmin/p/16306741.html
Copyright © 2020-2023  润新知