Golang的json包

  • 2019 年 11 月 10 日
  • 筆記

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)  }

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("%vn", 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("%vn", 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("%+vn", 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, 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