Golang - Json and Go

json在Go中简介

1. 编码与解码

  • func Marshal(v interface{}) ([]byte, error)
  • func Unmarshal(data []byte, v interface{}) error

1.1. 示例

package main

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

type Goods struct {
    Id        int
    Name      string
    Producer  []string
    RealPrice float64 `json:"price"`
    SalePrice float64
    Price     float64
}

func main() {
    // goods json
    gb, err := GoodsJson()
    if err != nil {
        log.Panic(err)
    }

    // goods object
    g, err := GoodsObj(gb)
    if err != nil {
        log.Panic(err)
    }

    // main.Goods{Id:100, Name:"HEAD PHONE", Producer:[]string{"Huawei", "Xiaomi", "Meizhu"}, RealPrice:4300, SalePrice:0}
    // 
    fmt.Printf("%#v", g)
}

// GoodsJson get goods json encode
func GoodsJson() (jsbyte []byte, err error) {
    goods := map[string]interface{}{
        "id":       100,
        "name":     "HEAD PHONE",
        "producer": []string{"Huawei", "Xiaomi", "Meizhu"},
        "price":    4300.00,
    }
    return json.Marshal(goods)
}

// GoodsObj get goods object
func GoodsObj(jsbyte []byte) (g Goods, err error) {
    return g, json.Unmarshal(jsbyte, &g)
}

1.2. 编码

只有可以表示为有效JSON的数据结构才会被编码:

  • 仅支持字符串作为key,编码go的map类型,必须形式为map[string]T,且T类型也是可以被json编码的
  • json包只访问struct类型的导出字段(以大写字母开头的字段),因此,只有结构的导出字段才会出现在JSON输出中(注意编码和解码都是如是)
  • Channel, complex, function类型不能被编码
  • 不支持循环数据结构,会导致json编码过程的无限循环
  • 指针将被编码为它们指向的值(如果指针为零,则为“null”)

1.3. 解码

Unmarshal如何识别存储解码数据的字段?

对于给定的JSON键“Foo”,Unmarshal将查看目标结构的字段以查找(按优先顺序):

  • 导出字段,且有Json标记的(参见Exp的json:"price");
  • 导出字段,字段为“Foo”;
  • 导出字段,“FOO”或“FoO”的导出字段或“Foo”的其他一些不区分大小写的匹配项。

Unmarshal将仅解码它可以在目标类型中找到的字段,当从大型JSON lob中仅选择一些特定字段时,此行为特别有用。

它还意味着目标结构中的任何未导出的字段都不受Unmarshal的影响。

2. 其他问题

2.1. JSON与interface

json包使用map[string]interface{}[]interface{}值来存储任意JSON对象数组;它会愉快地将任何有效的JSON blob解组为普通的**interface{}**值。默认的具体Go类型是:

  • 用于JSON布尔值的bool
  • 用于JSON数字的float64
  • 用于JSON字符串的字符串
  • 用于JSONnull的nil

2.2. 解析任意JSON数据到interface中

func ParseJsonStr()  {
    b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
    var f interface{}
    err := json.Unmarshal(b, &f) 
    if err != nil {
        log.Panic(err)
    }

    m := f.(map[string]interface{})
    for k, v := range m {
        switch vv := v.(type) {
        case string:
            fmt.Println(k, "is string", vv)
        case float64:
            fmt.Println(k, "is float64", 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")
        }
    }
}

2.3. JSON解析的到结构体中字段引用问题

考虑以下内容信息:

// 切片引用
type FamilyMember struct {
    Name    string
    Age     int
    Parents []string
}

var m FamilyMember
err := json.Unmarshal(b, &m)

// 对象引用
type Foo struct {
    Bar *Bar
}
  1. 切片应用中,var定义m的时候,m.Parents为nil类型,但json.Unmarshal通过解析过程中创建新的切片来完成了解析
  2. 对象引用中,BarJSON对象中有一个字段,Unmarshal则会分配一个新字段Bar并填充它,如果没有,Bar将nil留作指针

由此可以引出一个有用的接收器设计模式:

type IncomingMessage struct {
    Cmd *Command
    Msg *Message
}

如果您有一个接收几种不同消息类型的应用程序,发送方可以填充顶级JSON对象的Cmd字段和/或Msg字段,具体取决于想要通信的消息类型。 在将JSON解码为IncomingMessage结构时,Unmarshal将仅分配JSON数据中存在的数据结构。 要知道要处理哪些消息,程序员只需测试Cmd或Msg不是nil。

2.4. JSON流式编码和解码

json包提供解码器和编码器类型,以支持读写JSON数据流的常见操作。NewDecoderNewEncoder函数包装了io.Readerio.Writer接口类型。

  • func NewDecoder(r io.Reader) *Decoder
  • func NewEncoder(w io.Writer) *Encoder

示例程序,它从标准输入读取一系列JSON对象,从每个对象中删除除Name字段之外的所有对象,然后将对象写入标准输出:

func StreamJsonHandle() {
    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
        }
        fmt.Println(v)

        for k := range v {
            if k != "Name" {
                delete(v, k)
            }
        }
        if err := enc.Encode(&v); err != nil {
            log.Println(err)
        }
    }
}

// input
// {"id":70,"Name":"Cpu","price":2000.88,"producer":["Intel","AMD"]}
// output
// map[Name:Cpu price:2000.88 producer:[Intel AMD] id:70]
// {"Name":"Cpu"}

由于读/写器无处不在,这些编码器和解码器类型可用于各种场景,例如读取和写入HTTP连接,WebSockets或文件。