• Golang的json包


    本文对常见的json包做一些介绍,方便快速入门。每一小节均有示例说明。大家在实际开发中可以选择适合自己的json包。

    encoding/json

    encoding/json是官方提供的标准json, 实现RFC 7159中定义的JSON编码和解码。使用的时候需要预定义struct,原理是通过reflectioninterface来完成工作, 性能低。

    常用的接口:

    • func Marshal(v interface{}) ([]byte, error) 生成JSON
    • func Unmarshal(data []byte, v interface{}) error 解析JSON到struct

    示例1 生成JSON:

    type ColorGroup struct {
        ID     int
        Name   string
        Colors []string
    }
    
    group := ColorGroup{
        ID:     1,
        Name:   "Reds",
        Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
    }
    
    b, err := json.Marshal(group)
    if err != nil {
        fmt.Println("error:", err)
    }
    
    os.Stdout.Write(b)
    

    Output:

    {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
    

    示例2 解析JSON:

    var jsonBlob = []byte(`[
    	{"Name": "Platypus", "Order": "Monotremata"},
    	{"Name": "Quoll",    "Order": "Dasyuromorphia"}
    ]`)
    
    type Animal struct {
        Name  string
        Order string
    }
    var animals []Animal
    err := json.Unmarshal(jsonBlob, &animals)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Printf("%+v", animals)
    

    Output:

    [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
    

    easyjson, ffjson

    标准库性能的瓶颈在反射。easyjson, ffjson 并没有使用反射方式实现,而是在Go中为结构体生成静态MarshalJSONUnmarshalJSON函数,类似于预编译。调用编码解码时直接使用生成的函数,从而减少了对反射的依赖,所以通常快2到3倍。但相比标准JSON包,使用起来略为繁琐。

    使用步骤:

    1、定义结构体,每个结构体注释里标注 //easyjson:json或者 //ffjson: skip
    2、使用 easyjson或者ffjson命令将指定目录的go结构体文件生成带有MarshalUnmarshal方法的新文件;
    3、代码里如果需要进行生成JSON或者解析JSON,调用生成文件的 MarshalUnmarshal方法即可。

    下面是使用示例。

    easyjson

    GitHub:https://github.com/mailru/easyjson

    1、先安装:

    go get -u github.com/mailru/easyjson/
    

    2、定义结构体:

    记得在需要使用easyjson的结构体上加上//easyjson:json。 如下:

    //easyjson:json
    type School struct {
    	Name string		`json:"name"`
    	Addr string		`json:"addr"`
    }
    
    //easyjson:json
    type Student struct {
    	Id       int       `json:"id"`
    	Name     string    `json:"s_name"`
    	School   School    `json:"s_chool"`
    	Birthday time.Time `json:"birthday"`
    }
    

    3、在结构体包下执行

    easyjson  -all student.go
    

    此时在该目录下出现一个新的文件:easyjson_student.go,该文件给结构体增加了MarshalJSONUnmarshalJSON等方法。

    4、使用

    package main
    
    import (
        "studygo/easyjson"
        "time"
        "fmt"
    )
    
    func main(){
        s:=easyjson.Student{
            Id: 11,
            Name:"qq",
            School:easyjson.School{
                Name:"CUMT",
                Addr:"xz",
            },
            Birthday:time.Now(),
        }
        bt,err:=s.MarshalJSON()
        fmt.Println(string(bt),err)
        
        json:=`{"id":11,"s_name":"qq","s_chool":{"name":"CUMT","addr":"xz"},"birthday":"2017-08-04T20:58:07.9894603+08:00"}`
        ss:=easyjson.Student{}
        ss.UnmarshalJSON([]byte(json))
        fmt.Println(ss)
    }
    

    运行结果:

    {"id":11,"s_name":"qq","s_chool":{"name":"CUMT","addr":"xz"},"birthday":"2017-08-04T20:58:07.9894603+08:00"} <nil>
    {121  {CwwwwwwwUMT xzwwwww} 2017-08-04 20:52:03.4066002 +0800 CST}
    

    ffjson

    GitHub:https://github.com/pquerna/ffjson

    本小节就不给示例了,大家直接看github上说明。用法与easyjson类似。

    需要注意的是,ffjson也提供了ffjson.Marshalffjson.Unmarshal方法,如果没有使用ffjson给对应结构体生成静态的方法,则会调用标准库encoding/json进行编码解码:

    func Marshal(v interface{}) ([]byte, error) {
      //调用结构体的静态方法
    	f, ok := v.(marshalerFaster)
    	if ok {
    		buf := fflib.Buffer{}
    		err := f.MarshalJSONBuf(&buf)
    		b := buf.Bytes()
    		if err != nil {
    			if len(b) > 0 {
    				Pool(b)
    			}
    			return nil, err
    		}
    		return b, nil
    	}
    
      //调用encoding/json
    	j, ok := v.(json.Marshaler)
    	if ok {
    		return j.MarshalJSON()
    	}
    	return json.Marshal(v)
    }
    

    json-iterator/go

    这是一个很神奇的库,滴滴开发的,不像 easyjson 和 ffjson 都使用了预编译,而且 100% 兼容encoding/json的高性能json库。根据作者的压测介绍,该库比easyjson性能还要好那么一点点(有点怀疑~)。

    json-iterator使用modern-go/reflect2来优化反射性能。然后就是通过大幅度减少反射操作,来提高速度。

    Github: https://github.com/json-iterator/go

    首先来看下用法:

    标准库写法:

    import "encoding/json"
    json.Marshal(&data)
    

    json-iterator写法:

    import "github.com/json-iterator/go"
    
    var json = jsoniter.ConfigCompatibleWithStandardLibrary
    json.Marshal(&data)
    

    此外,该库还提供了个功能,对于从PHP转过来的朋友很有帮助。

    PHP是弱类型,所以接口里经常把数字10写成字符串”10″返回,导致一个表达年龄的JSON变成了这样:

    {
    	"age": "10"
    }
    

    golang标准库的json并不能兼容这种情况下的解析,因此如果我们的struct企图使用int来反射这个字段,将会导致decode失败。此时json-iterator/go就派上用场了:

    package main
    
    import (
    	"fmt"
    	jsoniter "github.com/json-iterator/go"
    	"github.com/json-iterator/go/extra"
    )
    
    var json = jsoniter.ConfigCompatibleWithStandardLibrary
    
    func init() {
    	// RegisterFuzzyDecoders decode input from PHP with tolerance.
    	//  It will handle string/number auto conversation, and treat empty [] as empty struct.
    	extra.RegisterFuzzyDecoders()
    }
    
    type StdStruct struct {
    	Age int `json:"age"`
    }
    
    func main() {
    	s := `{"age": "10"}`
    
    	d := &StdStruct{}
    
    	if err := json.Unmarshal([]byte(s), d); err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println(d.Age)
    	}
    }
    

    输出:

    10
    

    们只需要在main文件里的init方法中开启1次PHP兼容模式即可,后续引入的模块不需要重复开启。

    go-simplejson, gabs, jason

    这几个包都是在encoding/json的基础上进行开发的,为了是更方便的操作JSON:它不需要创建struct,而是动态按字段取内容。它们大部分只是一个解析库,并没有序列化的接口。有时候我们仅仅想取JSON里的某个字段,用这个非常有用。

    下面是go-simplejson示例。

    go-simplejson

    Github: https://github.com/bitly/go-simplejson

    package main
    
    import (
    	"fmt"
    	"github.com/bitly/go-simplejson"
    )
    
    func main() {
    
    	data := []byte(`{
          "hits":{
              "total":2,
              "max_score":4.631368,
              "hits":[
                  {
                      "_source":{
                          "account_number":298,
                          "balance":34334,
                          "firstname":"Bullock",
                          "lastname":"Marsh"
                      }
                  }
              ]
          }
      }`)
    
    	js, _ := simplejson.NewJson(data)
    
    	//get total
    	total, _ := js.Get("hits").Get("total").Int64()
    	fmt.Println(total)
    
    	account_number, _ := js.Get("hits").Get("hits").GetIndex(0).Get("_source").Get("account_number").Int64()
    	fmt.Println(account_number)
    
    	//get _source list
    	hitsjson, _ := js.Get("hits").Get("hits").MarshalJSON()
    	fmt.Printf("%s", hitsjson)
    }
    

    输出:

    2
    298
    [{"_id":"298","_index":"bank","_score":4.631368,"_source":{"account_number":298,"balance":34334,"firstname":"Bullock","lastname":"Marsh"},"_type":"account"}]
    

    go-simplejson 没有提供类似Each方法,无法对数组类型的进行遍历。但是我们可以将数组取到后调用MarshalJSON生成JSON,使用标准的encoding/json进行解析。

    gabs

    Github: https://github.com/Jeffail/gabs

    package main
    
    import (
    	"fmt"
    	"github.com/Jeffail/gabs/v2"
    )
    
    func main() {
    
      data := []byte(`{}`) //注:为节省篇幅,data结构参考go-simplejson
    
    	js, _ := gabs.ParseJSON(data)
    
    	//get total
    	var total float64
    	//使用断言,否则类型错误会报错
    	if val, ok := js.Path("hits.total").Data().(float64); ok {
    		total = val
    	}
    	total2 := js.Search("hits", "total").Data().(float64)
    	total3 := js.S("hits", "total").Data().(float64) // S is shorthand for Search
    
    	gObj, _ := js.JSONPointer("/hits/total")
    	total4 := gObj.Data().(float64)
    	fmt.Println(total, total2, total3, total4)
    
    	exist := js.Exists("hits", "total")
    	fmt.Println(exist)
    
    	account_number := js.Path("hits.hits.0._source.account_number").Data().(float64)
    	fmt.Println(account_number)
    
    	//Iterating arrays
    	for _, v := range js.S("hits", "hits").Children() {
    		lastname := v.S("_source", "lastname").Data().(string)
    		fmt.Printf("%v
    ", lastname)
    	}
    }
    

    输出:

    2 2 2 2
    true
    298
    Marsh
    

    除此之外,gabs 还支持重新动态生成JSON、合并JSON等操作。但是解析需要使用断言这一点不是很方便。

    jason

    Github: https://github.com/antonholmquist/jason

    示例:

    package main
    
    import (
    	"fmt"
    	"github.com/antonholmquist/jason"
    )
    
    func main() {
    
    	data := []byte(`{}`) //注:为节省篇幅,data结构参考go-simplejson
    
    	js, _ := jason.NewObjectFromBytes(data)
    
    	//get total
    	total, _ := js.GetInt64("hits", "total")
    	fmt.Println(total)
    
    	//get _source list
    	hitsjson, _ := js.GetObjectArray("hits", "hits")
    	for _, v := range hitsjson {
    		lastname, _ := v.GetString("_source", "lastname")
    		fmt.Printf("%v
    ", lastname)
    	}
    }
    

    输出:

    2
    Marsh
    

    提供了遍历数组的方法,但是没有提供按索引取某个数组的方法。

    jsonparser

    jsonparser 功能与go-simplejson类似,只是一个解析库,并没有序列化的接口。但是由于底层不是基于encoding/json开发的,官方宣称它比encoding/json快10倍。

    GitHub: https://github.com/buger/jsonparser

    下面是个解析ES的示例:

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"github.com/buger/jsonparser"
    )
    
    type UserInfo struct {
    	AccountNumber int64  `json:"account_number"`
    	Balance       int64  `json:"balance"`
    	Firstname     string `json:"firstname"`
    	Lastname      string `json:"lastname"`
    }
    
    func main() {
    
    	data := []byte(`{}`) //注:为节省篇幅,data结构参考go-simplejson
    
    	//get total
    	total, _ := jsonparser.GetInt(data, "hits", "total")
    	fmt.Println(total)
    
    	//get _source list
    	var list []UserInfo
    	hitsjson, _, _, _ := jsonparser.Get(data, "hits", "hits")
    
    	type hitsMap struct {
    		Source UserInfo `json:"_source,omitempty"`
    	}
    
    	var hitsMaps []hitsMap
    	json.Unmarshal(hitsjson, &hitsMaps)
    
    	for _, info := range hitsMaps {
    		list = append(list, info.Source)
    	}
    	fmt.Printf("%+v
    ", list)
    
    	//get each _source
    	jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
    		_source, _, _, _ := jsonparser.Get(value, "_source")
    		fmt.Println(string(_source))
    	}, "hits", "hits")
    }
    

    输出:

    2
    
    [{AccountNumber:298 Balance:34334 Firstname:Bullock Lastname:Marsh}]
    
    {
      "account_number": 298,
      "balance": 34334,
      "firstname": "Bullock",
      "lastname": "Marsh"
    }
    
    

    大家可以看一下 elastic/go-elasticsearch 给出的示例里是怎么解析JSON的:

    // Print the response status, number of results, and request duration.
      log.Printf(
        "[%s] %d hits; took: %dms",
        res.Status(),
        int(r["hits"].(map[string]interface{})["total"].(map[string]interface{})["value"].(float64)),
        int(r["took"].(float64)),
      )
      // Print the ID and document source for each hit.
      for _, hit := range r["hits"].(map[string]interface{})["hits"].([]interface{}) {
        log.Printf(" * ID=%s, %s", hit.(map[string]interface{})["_id"], hit.(map[string]interface{})["_source"])
      }
    

    对,就是使用的断言,这个会让人很崩溃,万一值不存在或者类型不对,还会直接扔个ERROR...

    总结

    大部分情况下大家直接使用 encoding/json就行了。如果追求极致的性能,考虑 easyjson。遇到解析ES搜索返回的复杂的JSON或者仅需要解析个别字段, go-simplejson或者jsonparser就很方便了。

    参考

    1、json - GoDoc
    https://godoc.org/encoding/json#example-Unmarshal
    2、Golang的json包一览 - 知乎
    https://zhuanlan.zhihu.com/p/24451749
    3、bitly/go-simplejson: a Go package to interact with arbitrary JSON
    https://github.com/bitly/go-simplejson
    4、buger/jsonparser: Alternative JSON parser for Go that does not require schema (so far fastest)
    https://github.com/buger/jsonparser
    5、Golang高性能json包:easyjson - 梦朝思夕的个人空间 - OSCHINA
    https://my.oschina.net/qiangmzsx/blog/1503018
    6、pquerna/ffjson: faster JSON serialization for Go
    https://github.com/pquerna/ffjson
    7、Jeffail/gabs: For parsing, creating and editing unknown or dynamic JSON in Go
    https://github.com/Jeffail/gabs
    8、golang – 利用json-iterator库兼容解析PHP JSON | 鱼儿的博客
    https://yuerblog.cc/2019/11/08/golang-利用json-iterator库兼容解析php-json/
    9、json-iterator 使用要注意的大坑 – 萌叔
    http://vearne.cc/archives/433
    10、golang json 性能分析 - - SegmentFault 思否
    https://segmentfault.com/a/1190000013022780

  • 相关阅读:
    高德地图周边搜索与搜索提示(三)
    jupyter notebook 添加目录
    Ubuntu 下简单完全卸载MySQL 以及便捷安装
    Ubuntu中安装MySQL,更改默认用户密码
    python-pip版本更新
    概率论与数理统计--->陈希孺---书籍链接下载
    机器学习实战-书籍链接下载
    Markdown安装/破解/下载
    Kaggle比赛入门比赛--Titanic-测试玩--
    python机器学习算法简介
  • 原文地址:https://www.cnblogs.com/52fhy/p/11830755.html
Copyright © 2020-2023  润新知