encoding/JSON
encoding/JSON 是官方提供的标准 JSON, 实现 RFC 7159 中 https://tools.ietf.org/html/rfc7159 定义的 JSON 编码和解码. 使用的时候需要预定义 struct, 原理是通过 reflection 和 interface 来完成工作, 性能低.
常用的接口:
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 中为结构体生成静态 MarshalJSON 和 UnmarshalJSON 函数. 生成的函数减少了对运行时反射的依赖, 所以通常快 2 到 3 倍. 但相比标准 JSON 包, 使用起来略为繁琐.
使用步骤:
1, 定义结构体, 每个结构体注释里标注 //easyjson:JSON 或者 //ffjson: skip;
2, 使用 easyjson 或者 ffjson 命令将指定目录的 go 结构体文件生成带有 Marshal,UnMarshal 方法的新文件;
3, 代码里如果需要进行生成 JSON 或者解析 JSON, 调用生成文件的 Marshal,UnMarshal 方法即可.
下面是使用示例.
- 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, 该文件给结构体增加了 MarshalJSON,UnmarshalJSON 等方法.
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.Marshal 和 ffjson.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)
- }
- 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\n", 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\n", 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\n", 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"
- }
大家可以看一下 https://github.com/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, ffjson. 遇到解析 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
来源: http://www.bubuko.com/infodetail-3281486.html