Golang的json包

encoding/json

encoding/json是官方提供的標準json, 實現RFC 7159中定義的JSON編碼和解碼。使用的時候須要預約義struct,原理是經過reflectioninterface來完成工做, 性能低。html

經常使用的接口:git

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

示例1 生成JSON:github

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:json

{"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:bash

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

easyjson, ffjson

easyjson, ffjson 並無使用反射方式實現,而是在Go中爲結構體生成靜態MarshalJSONUnmarshalJSON函數。生成的函數減小了對運行時反射的依賴,因此一般快2到3倍。但相比標準JSON包,使用起來略爲繁瑣。app

使用步驟:elasticsearch

一、定義結構體,每一個結構體註釋裏標註 //easyjson:json或者 //ffjson: skip
二、使用 easyjson或者ffjson命令將指定目錄的go結構體文件生成帶有MarshalUnMarshal方法的新文件;
三、代碼裏若是須要進行生成JSON或者解析JSON,調用生成文件的 MarshalUnMarshal方法便可。函數

下面是使用示例。性能

easyjson

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

一、先安裝:

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

二、定義結構體:

記得在須要使用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"`
}

三、在結構體包下執行

easyjson  -all student.go

此時在該目錄下出現一個新的文件:easyjson_student.go,該文件給結構體增長了MarshalJSONUnmarshalJSON等方法。

四、使用

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("%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"
}

你們能夠看一下 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就很方便了。

參考

一、json - GoDoc
https://godoc.org/encoding/json#example-Unmarshal
二、Golang的json包一覽 - 知乎
https://zhuanlan.zhihu.com/p/24451749
三、bitly/go-simplejson: a Go package to interact with arbitrary JSON
https://github.com/bitly/go-simplejson
四、buger/jsonparser: Alternative JSON parser for Go that does not require schema (so far fastest)
https://github.com/buger/jsonparser
五、Golang高性能json包:easyjson - 夢朝思夕的我的空間 - OSCHINA
https://my.oschina.net/qiangmzsx/blog/1503018
六、pquerna/ffjson: faster JSON serialization for Go
https://github.com/pquerna/ffjson
七、Jeffail/gabs: For parsing, creating and editing unknown or dynamic JSON in Go
https://github.com/Jeffail/gabs

相關文章
相關標籤/搜索