本文对常见的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\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。遇到解析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

Golang的json包的更多相关文章

  1. golang中json包序列化与反序列化

    package main import ( "encoding/json" "fmt" "reflect" ) type Info stru ...

  2. golang json 包简单分析

    首先上代码: func main() { b := true a1, _ := json.Marshal(b) a2, _ := Marshal(b) fmt.Println(string(a1)) ...

  3. Golang解析json的几种方法

    Golang解析json的几种方法 概要 使用Golang调用其它平台API接口时总会被多层的json串给恶心到,我记录一下自己解析json的几种方法. 一.自带的json包 func JsonUnm ...

  4. 从0写一个Golang日志处理包

    WHY 日志概述 日志几乎是每个实际的软件项目从开发到最后实际运行过程中都必不可少的东西.它对于查看代码运行流程,记录发生的事情等方面都是很重要的. 一个好的日志系统应当能准确地记录需要记录的信息,同 ...

  5. 关于golang的time包总结

    目录 前言 time包详解 总结 前言 各种编程语言都少不了与时间有关的操作,因为很多判断都是基于时间,因此正确和方便的使用时间库就很重要额. golang提供了import "time&q ...

  6. WP8如何添加Newtonsoft.Json包

    WP8开发的时候如何使用Newtonsoft.Json包呢?我在网上包括官网下的DLL文件,添加引用时都给出了这样的提示: 而后在网上找到的解决办法是:使用NuGet程序包来添加. 首先点击工具--& ...

  7. Golang爬虫示例包系列教程(一):pedaily.com投资界爬虫

    Golang爬虫示例包 文件结构 自己用Golang原生包封装了一个爬虫库,源码见go get -u -v github.com/hunterhug/go_tool/spider ---- data ...

  8. 两层嵌套的JSON包的解法

    由于后台的变态,有时候会出现两层甚至多层嵌套的JSON包. 一层的很好解,而且我看过一些比较大的网站新闻接口返回的JSON包也仅仅是一层的. 比如下图所示一层的包 代码也很简单直观 dict = [d ...

  9. Java中net.sf.json包关于JSON与对象互转的问题

    在Web开发过程中离不开数据的交互,这就需要规定交互数据的相关格式,以便数据在客户端与服务器之间进行传递.数据的格式通常有2种:1.xml:2.JSON.通常来说都是使用JSON来传递数据.本文正是介 ...

随机推荐

  1. scrapy架构流程

    1.爬虫spiders将请求通过引擎传递给调度器scheduler 2.scheduler有个请求队列,在请求队列中拿出请求给下载器,downloader 3.downloader从Internet的 ...

  2. Ubuntu server 安装及jdk+mysql安装教程

    Ubuntu server 安装教程 1.查找及下载Ubuntu镜像文件 可以在以下页面下载想要的版本,我这里选择19.04 server版的iso镜像文件: http://mirrors.163.c ...

  3. Node.js入门教程 第六篇 (连接使用MySql)

    连接使用MySql 安装MySql模块: npm install mysql 创建连接: const mysql = require('mysql') // 连接 mysql 服务器 const co ...

  4. 实体对象属性和值转为键值对Dictionary

    实体对象转为Dictionary键值对 /// <summary> /// 实体转键值对 /// </summary> /// <typeparam name=" ...

  5. django创建表单以及表单数据类型和属性

    08.15自我总结 关于django的表单不同关系之间的创建 一.不同关系之间的创建 1.一对一 举例 母表:userinfo id name age 1 张三 12 2 李四 58 字表:priva ...

  6. 教你用Vue写一个开心消消乐

    之前做过一个算法题,算法要求就是写一个开心消消乐的逻辑算法,当时也是考虑了一段时间才做出来.后来想了想,既然核心算法都有了,能不能实现一个开心消消乐的小游戏呢,于是花了两天时间做了一个小游戏出来. 效 ...

  7. PHP array_unique

    1.函数的作用:移除数组中重复的值 2.函数的参数: @params array $array @params int $sort_flag SORT_REGULAR : 通常方法比较(不改变类型) ...

  8. [JZOJ5866]【NOIP2018模拟9.13】指引

    Description

  9. [JOYOI1463] 智商问题

    题目限制 时间限制 内存限制 评测方式 题目来源 1500ms 131072KiB 标准比较器 Local 题目背景 各种数据结构帝~ 各种小姊妹帝~ 各种一遍AC帝~ 来吧! 题目描述 某个同学又有 ...

  10. TensorFlow2.0(8):误差计算——损失函数总结

    .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...