go中json的使用
文章目录
go 原生提供了json的加码解码支持,import "encoding/json"
就可以了。
1. encode 编码
json的 encode 加码方法是:
func Marshal(v interface{}) ([]byte, error)
正是因为参数是接口 interfae{},所以,理论上可以传入任何数据。
1.1 struct 编码
我们一般都用用结构体作为数据源,给他json加码操作,因为它的类型可以加入任意的数据类型,符合json字符串的特性。
我们先定义2个结构体,作为数据类型。
//1. 先定义1个结构体
type Animal struct {
Name string
Order string
}
// 定义结构体嵌套
type List struct {
Ret int
Info []Animal
}
要想对结构体实现json加码操作,得实例化1个变量出来。
//实例化1个变结构体量
var list0 = Animal{Name: "wo", Order: "HELLO"}
//encode
list9, err := json.Marshal(list0)
if err != nil {
_ = fmt.Errorf("%v", err)
}
//输出的list9是一个字符的切片 []byte, 得用string() 方法转换成字符串
fmt.Printf("%v\n", string(list9))
输出的json字符串如下:
{"Name":"wo","Order":"HELLO"}
我们来1个复杂的结构体,实现更复杂的json
// 结构体重复嵌套
//由于需要嵌套,所以得定义1个切片结构体。
var animals []Animal
//通过append给切片添加数据。
animals = append(animals, Animal{Name: "Platypus", Order: "Monotremata"})
animals = append(animals, Animal{Name: "Quoll", Order: "Dasyuromorphia"})
// encode
list2, err := json.Marshal(animals)
if err != nil {
_ = fmt.Errorf("%v", err)
}
// 转换成字符串
fmt.Printf("%v\n", string(list2)) //
输出json如下:
[{"Name":"Platypus","Order":"Monotremata"},{"Name":"Quoll","Order":"Dasyuromorphia"}]
我们再来看另外一种嵌套方式,子元素嵌套类型。
// 3. 子元素嵌套类型的结构体
var animals []Animal
//通过append给切片添加数据。
animals = append(animals, Animal{Name: "Platypus", Order: "Monotremata"})
animals = append(animals, Animal{Name: "Quoll", Order: "Dasyuromorphia"})
//初始化
var list1 = List{Ret: 0, Info: animals}
list3, err := json.Marshal(list1)
if err != nil {
_ = fmt.Errorf("%v", err)
}
fmt.Printf("%v\n", string(list3))
输出json如下:
{"Ret":0,"Info":[{"Name":"Platypus","Order":"Monotremata"},{"Name":"Quoll","Order":"Dasyuromorphia"}]}
我们可以用一个pretty的类库,使得输出结构更加人类可见。
import "github.com/tidwall/pretty"
aa := pretty.Pretty(list3)
fmt.Println(string(aa))
输出如下:
{
"Ret": 0,
"Info": [
{
"Name": "Platypus",
"Order": "Monotremata"
},
{
"Name": "Quoll",
"Order": "Dasyuromorphia"
}
]
}
1.2 map 编码
由于map的格式是固定的,没法实现更加丰富的结构,所以一般用map的不多,但是,如果我们对外输出的内容确实是简单固定的,那就可以用map
//定义map类型
personSalary := make(map[string]int)
//开始生成内容
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 123
//加码
b, _ := jsoniter.Marshal(personSalary)
fmt.Println(string(pretty.Pretty(b)))
输出如下:
{
"jamie": 15000,
"mike": 123,
"steve": 12000
}
1.3 [] 数组/切片编码
同样,如果我们数据类似都是数组格式的json,那就可以用数组/切片
//切片
a := []int{1,2,3,4}
//数组
//a := [4]string{"hello","world","ni","hao"}
b1, _ := jsoniter.Marshal(a)
fmt.Println(string(pretty.Pretty(b1)))
[1, 2, 3, 4]
["hello", "world", "ni", "hao"]
2. deocode 解码
自带的json类库的decode加码方法是:Unmarshal
func Unmarshal(data []byte, v interface{}) error {}
go里面的解码是比较麻烦的,要想解码json,你得根据这个json字符串里的字段内容,先定义解码出来的结构体,再实例化这个结构体,再调用解码函数。初次使用非常不习惯。
2.1 解码成struct
和加码struct操作步骤完全反过来,去做一遍。
比如,有这样一个json字符串
{"Name":"wo","Order":"HELLO"}
他的数据类型,其实很简单,类型都是一样,其实我们可以解析成map也是OK的,我们先解析成struct吧。看一下,这个json,有2个字段,1个Name,1个Order,所以我们也新建有这2个字段的结构体。
//第一步,我们得定义接受的结构体
type ListOne struct {
Name string
Order string
}
//第二步,实例化这个结构体
var ListInfo1 ListInfo
//第三步,转换成[]byte, 再引用调用,&, 传入指针变量,因为需要修改这个值。
err2 := json.Unmarshal([]byte(jsonStr), &ListInfo1)
if err2 != nil {
log.Fatal(err)
}
fmt.Printf("%T,%+v\n", ListInfoOne3, ListInfoOne3)
//打印 Name字段
fmt.Printf("%v\n", ListInfoOne3.Name)
最后得到了这个结构体:
main.ListInfo3,{Name:wo Order:HELLO}
Wo
再继续,复杂一点的json,我们看下如何解析成struct
[
{
"Name": "Platypus",
"Order": "Monotremata"
},
{
"Name": "Quoll",
"Order": "Dasyuromorphia"
}
]
分析一下,这个json 是一个数组,里面有2个子元素,每1个子元素里面就是1个struct。所以,我们得用一个[]struct
这样的数据来接受。
所以,这个结构体长这样:
[]struct
//先定义结构体
type ListInfo2 struct {
Name string
Order string
}
//构造1个[]struct 数组/切片
var ListInfoOne2 []ListInfo2
//开始解码
err3 := json.Unmarshal([]byte(jsonStr), &ListInfoOne2)
if err3 != nil {
log.Fatal(err)
}
fmt.Printf("%T,%+v\n", ListInfoOne2, ListInfoOne2)
//打印第0个元素的Name字段的值
fmt.Printf("%v\n", ListInfoOne2[0].Name)
打印出来的数据如下:
[]main.ListInfo2, [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
Platypus
再继续,更加复杂的json,我们看下如何解析成struct。
{
"Ret": 0,
"Info": [
{
"Name": "Platypus",
"Order": "Monotremata"
},
{
"Name": "Quoll",
"Order": "Dasyuromorphia"
}
]
}
我们先分析,如何定义结构体,来接受这个json,首先,外面是一个大的struct,其中Info字段是个数组,数组的子元素又是一个struct。OK,有了这个定义之后,我们就可以开始弄了。
所以,这个结构体,大致是长这样的,看样子,得定义2个结构体,搞嵌套
struct {
Ret:
Info : [] struct
}
开始写:
//先定义结构体
type ListOne struct {
Name string
Order string
}
type ListInfo struct {
Ret int
Info []ListOne
}
var ListInfo1 ListInfo
//解码
err2 := json.Unmarshal([]byte(jsonStr), &ListInfo1)
if err2 != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", ListInfo1)
fmt.Printf("%+v\n", ListInfo1.Info[1].Name)
输出如下:
{Ret:0 Info:[{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]}
Quoll
2.2 解码成map
解码成map,首先要保证这个json字符串很简单,k和v的数据类型都是重复一样的,就可以,用map来接受,和stuct一样,我们得定义这个map,再去解码
{
"steve": 12000,
"jamie": 15000,
"mike": 123
}
比如,这个json,分析一下,它的k和v都是相同的结构,可以用map
定义的map大致如下
map[string]int
var a1 map[string]int
_ = json.Unmarshal([]byte(jsonStr), &a1)
fmt.Println(a1)
fmt.Println(a1["mike"])
打印如下:
map[jamie:15000 mike:123 steve:12000]
123
再看一下,这个比较复杂的json,
[
{
"Name": "Platypus",
"Order": "Monotremata"
},
{
"Name": "Quoll",
"Order": "Dasyuromorphia"
}
]
这个json,我们分析一下,可以得到是如下一个map数组
[]map[string]string
代码如下:
var bb1 []map[string]string
_ = json.Unmarshal([]byte(jsonStr5), &bb1)
fmt.Printf("%+v\n", bb1)
fmt.Printf("%+v\n", bb1[0]["Name"])
[map[Name:Platypus Order:Monotremata] map[Name:Quoll Order:Dasyuromorphia]]
Platypus
2.3 解码成数组切片
对于非常简单的数组类型的json,比如:
[1,2,3,4]
那就可以解码成go数组了。
var bb1 []int
var jsonStr5 = `[1,2,3]`
_ = json.Unmarshal([]byte(jsonStr5), &bb1)
fmt.Printf("%+v\n", bb1)
fmt.Printf("%+v\n", bb1[0])
输出为:
[1 2 3]
1
3. struct的tag标签
strut 转成json的时候,支持加一个tag的标签。来支持更加丰富的json支持。
你想象一个场景,json的字符串,k的首字母有些都是小写的,可以是在GO里面,小写字母表示私有的,外部不可见的,所以,json 转成 struct后,怎么解决这个问题呢?再比如,json里面有些字段类型是string类型的数字,但是struct里面字段是int型的,怎么匹配呢?
//Golang中可以为结构体的字段添加tag, json:
/**
-:不要解析这个字段,表示该字段不会输出到 JSON
,omitempty 当字段为空(默认值)时,不要解析这个字段。比如 false、0、nil、长度为 0 的 array,map,slice,string,就不会输出到JSON 串中
FieldName,当解析 json 的时候,使用这个名字
,string当字段类型是 bool, string, int, int64 等,而 tag 中带有该选项时,那么该字段在输出到 JSON 时,会把该字段对应的值转换成 JSON 字符串.
*/
我们可以用json关键字,就可以实现刚说的这些问题。
type bbc struct {
Name string `json:"-"` //解析的时候忽略该字段。默认情况下会解析这个字段,因为它是大写字母开头的
Age int `json:"age"` //别名,解析(encode/decode) 的时候,使用 `age`,而不是 `Age`
Sex int `json:",omitempty"` // 表示如果为空,就不解析了
Height int `json:"h,string"` // 先用h替换,再类型转换,转成string
}
var bb2 = bbc {
Name: "yang", // 输出json, 就去掉,忽略了
Age: 12, // 变成:age
Sex: 0, // 因为是0,所以就忽略了。
Height: 180, //输出json 就变成了 h, 字符串类型
}
cd, _ := json.Marshal(bb2)
fmt.Printf("%+v\n", string(cd)) // {"age":12,"Height":"180"}
我们看下输出的是什么呢?达到了我们的目的。
{"age":12,"h":"180"}
同理,如果先得到了1个json,如何按照规则转换成json呢?
比如,有这样一个json:
{
"age": 12,
"height": "180",
"name": "bigbang",
"sex": ""
}
我们想转换成这样的go struct:
type bbc4 struct {
Name string
Age int
Sex int
Height int
}
怎么写这个json:""
关键字呢?首先是:别名、string转int、空变量就不解析,OK,开始写:
type bbc4 struct {
Name string `json:"name"`
Age int `json:"age"`
Sex string `json:"sex,omitempty"`
Height int `json:"height,string"`
}
var bb1 bbc4
var jsonStr5 = `{"age":12,"height":"180","name":"bigbang","sex":""}`
_ = json.Unmarshal([]byte(jsonStr5), &bb1)
fmt.Printf("%+v\n", bb1)
看下输出:
{Name:bigbang Age:12 Sex: Height:180}
4. 性能更好的json第三方库
github.com/json-iterator/go 是一个性能更高的第三方json库,它的调用法式和系统自带的json库是一模一样。所以,用起来还是非常方便。
import jsoniter "github.com/json-iterator/go"
//加码
err4 := jsoniter.Unmarshal([]byte(jsonStr3), &ListInfoOne3)
if err4 != nil {
fmt.Println(err4)
}
//解码
rtt, _ := jsoniter.Marshal(ListInfoOne3)
我们可以用benchmark跑一个测试看下,性能
package main
import (
"encoding/json"
"github.com/json-iterator/go"
"github.com/tidwall/gjson"
"testing"
)
type Animal struct {
Name string
Order string
}
var testString = `{"Name": "Platypus", "Order": "Monotremata"}`
func BenchmarkJsoniter(b *testing.B) {
var animal Animal
for i := 0; i < b.N; i++ {
var err error
var jsonBlob = []byte(testString)
err = jsoniter.Unmarshal(jsonBlob, &animal)
if err != nil {
//b.Log("error:%v\n", err)
}
}
}
func BenchmarkJson(b *testing.B) {
var animal Animal
for i := 0; i < b.N; i++ {
var err error
var jsonBlob = []byte(testString)
err = json.Unmarshal(jsonBlob, &animal)
if err != nil {
//b.Log("error:%v\n", err)
}
}
}
跑个测试看下,发现,居然同样的解码,比自带的性能搞了4倍多。
$ go test -bench=. -run=none json_test.go
goos: darwin
goarch: amd64
BenchmarkJsoniter-12 4199409 289 ns/op
BenchmarkJson-12 1447922 809 ns/op
PASS
ok command-line-arguments 4.466s
5. 性能更好,使用更方便的json第三方解码库
前面,我就吐槽过,解码一个json的时候,必须得参照这个json的结构,先定义一个struct或者map来接受这个转换后的数据,非常的麻烦和蛋疼。想象一下,如果一个json里面有几十上百个字段,那就得先定义,非常之蛋疼。更何况,有时候,我们其实只想获取这个json里面的某一个的值。
终于有这样一个第三方类库,可以拯救我们:
package main
import "github.com/tidwall/gjson"
const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
func main() {
value := gjson.Get(json, "name.last")
println(value.String()) // Prichard
}
是不是很爽啊。直接获取,不需要预先定义struct数据。
看下这3个的跑分:
package main
import (
"encoding/json"
"github.com/json-iterator/go"
"github.com/tidwall/gjson"
"testing"
)
type Animal struct {
Name string
Order string
}
var testString = `{"Name": "Platypus", "Order": "Monotremata"}`
func BenchmarkGJson(b *testing.B) {
for i := 0; i < b.N; i++ {
//_, ok := gjson.Parse(testString).Value().(map[string]interface{})
var _ = gjson.Get(testString, "Name")
//if !ok {
//}
}
}
func BenchmarkJsoniter(b *testing.B) {
var animal Animal
for i := 0; i < b.N; i++ {
var err error
var jsonBlob = []byte(testString)
err = jsoniter.Unmarshal(jsonBlob, &animal)
if err != nil {
//b.Log("error:%v\n", err)
}
}
}
func BenchmarkJson(b *testing.B) {
var animal Animal
for i := 0; i < b.N; i++ {
var err error
var jsonBlob = []byte(testString)
err = json.Unmarshal(jsonBlob, &animal)
if err != nil {
//b.Log("error:%v\n", err)
}
}
}
/**
goos: darwin
goarch: amd64
BenchmarkGJson-12 28741465 38.2 ns/op
BenchmarkJsoniter-12 4339891 280 ns/op
BenchmarkJson-12 1538788 761 ns/op
PASS
ok command-line-arguments 5.191s
*/
性能更强,也更简单,种草了!