为什么不推荐用官方包
不好用
- encoding/json, 官方自带的, 文档最多, 易用性差, 性能差
- go-simplejson, gabs, jason等衍生包, 简单且易用, 易于阅读, 便于维护, 但性能最差
- easyjson, ffjson此类包, 适合固定结构的json, 易用性一般, 维护成本高, 性能特别好
- jsonparser 适合动态和固定结构的json, 简单且易用, 维护成本低, 性能极好
- 以性能的高低排序: jsonparser > easyjson > encoding/json > go-simplejson, gabs, jason
tidwall/gjson(推荐)
场景:解析json时就用这个包
- go get -u github.com/tidwall/gjson
- 怎么用:http://www.yishuifengxiao.com/2023/02/21/go%E8%AF%AD%E8%A8%80%E4%B9%8Bjson%E5%A4%84%E7%90%86%E5%B7%A5%E5%85%B7gjson/
package main
import (
"fmt"
"github.com/tidwall/gjson"
)
func main(){
const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
value := gjson.Get(json, "name.last")
fmt.Println(value.String())
}
结果:Prichard
bitly/go-simplejson
gopm get -g -v -u github.com/bitly/go-simplejson
package main
import (
"fmt"
"encoding/json"
"github.com/bitly/go-simplejson"
)
type MyData struct {
Name string `json:"item"`
Other float32 `json:"amount"`
}
func main(){
var detail MyData
detail.Name = "名字"
detail.Other = 1
// 编码成Json
body, err := json.Marshal(detail)
if err != nil{
panic(err)
}
//Json解码
js, err := simplejson.NewJson(body)
if err != nil{
panic(err)
}
fmt.Println(js)
}
json对应golang中的数据结构
json源于javascript的对象结构,golang中直接对应的数据结构,可是golang的map也是key-value结构,同时struct结构体也可以描述json。当然,对于json的数据类型,go也会有对象的结构所匹配。大致对应关系如下:
数据类型 | JSON | Golang |
---|---|---|
字串 | string | string |
整数 | number | int64 |
浮点数 | number | flaot64 |
数组 | arrary | slice |
对象 | object | struct |
布尔 | bool | bool |
空值 | null | nil |
官方API
golang提供了encoding/json的标准库用于编码json。
json.Marshal()
大致需要两步:
- 首先定义json结构体。
- 使用 Marshal方法序列化。
结构体转为 JSON 字符串
可以使用 json.Marshal() 函数将一个 Go 结构体类型的值转换为对应的 JSON 字符串。例如:
type Person struct {
Name string`json:"name"`
Age int`json:"age"`
}
p := Person{"Alice", 28}
bytes, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(bytes)) // {"name":"Alice","age":28}
map编码为json
func main() {
skill := make(map[string]interface{})
skill["id"] = 1
skill["name"] = "name"
skill["friends"] = []string{"ploat", "maroan"}
skill["hunhe"] = []interface{}{0, "a", []int{1, 2, 3}}
rs, err := json.Marshal(skill)
if err != nil{
fmt.Println(err)
}
fmt.Println(string(rs)) //{"friends":["ploat","maroan"],"hunhe":[0,"a",[1,2,3]],"id":1,"name":"name"}
skill1 := make(map[int]interface{})
skill1[1] = 1
skill1[2] = "name"
skill1[3] = []string{"ploat", "maroan"}
skill1[4] = []interface{}{0, "a", []int{1, 2, 3}}
rs, err = json.Marshal(skill1)
if err != nil{
fmt.Println(err)
}
fmt.Println(string(rs)) //{"1":1,"2":"name","3":["ploat","maroan"],"4":[0,"a",[1,2,3]]}
}
json.Unmarshal
func Unmarshal(data []byte, v interface{}) error
JSON 字符串转为结构体
- 接收结构体
type Person struct {
Name string`json:"name"`
Age int`json:"age"`
}
var p Person
err := json.Unmarshal([]byte(`{"name":"Alice","age":28}`), &p)
if err != nil {
log.Fatal(err)
}
fmt.Println(p.Name, p.Age) // Alice 28
- 接收结构体数组
package main
import (
"encoding/json"
"fmt"
)
func main() {
// json格式字符串
var jsonUsers = []byte(`[
{"id": "1", "name": "Anny"},
{"id": "2", "name": "Tom"}
]`)
// 用结构体User进行接收
type User struct {
Id int `json:"id,string"`
Name string `json:"name"`
}
// 因为json格式字符串是个数组格式,这里也要定义数组
var users []User
err := json.Unmarshal(jsonUsers, &users)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", users)
// output: [{Id:1 Name:Anny} {Id:2 Name:Tom}]
}
- 接收结构体数组: Unmarshal这个函数,可以看到存储解析内容的变量类型是interface,也就说除了结构体外也可以使用其它类型进行接收,这里用字典(map)写了一个示例。
func main() {
var jsonUsers = []byte(`[
{"id":"1", "name":"Anny"},
{"id":2, "name":"Tom"}
]`)
var users []map[string]interface{}
err := json.Unmarshal(jsonUsers, &users)
if err!=nil{
panic(err)
}
fmt.Printf("%v\n", users)
}
说明:反序列化时这种方式不是很常用,但是序列化的时候,非常方便,用的较多。
type LoginReq struct {
UserName string `json:"user_name"`
}
func onLogin(ctx *gin.Context) {
req := LoginReq{}
if err := ctx.BindJSON(&req); err != nil {
ctx.Error(err)
}
if req.UserName == "admin" {
ctx.JSON(http.StatusOK, gin.H{"code": 0, "msg": "success"})
} else {
ctx.JSON(http.StatusUnauthorized, gin.H{"code": -1, "msg": "账号错误!"})
}
}
https://www.jb51.net/article/262498.htm
结构体标签(StructTag 字段重名)
json:"xxx"
就是一段结构体标签,它本身并没有什么魔法,很像一段代码注释。但是和注释的区别在于,通过反射机制是可以获取到标签的,从而可以实现一些神奇的事情。
在调用 json.Unmarshal() 函数时,需要传入一个字节数组和一个指向目标结构体的指针。json.Unmarshal() 函数会将 JSON 数据解析后存储到指向目标结构体的指针中。
- 对于结构体进行编码:字段的首字母必须大写,否则无法编码
字段名 | 数据类型 | 编码 | 含义 |
---|---|---|---|
Name | string | json:"-" |
|
Subject | string | json:"Subject_name" |
|
Age | int | json:"age,string" |
|
Address | string | json:"address,omitempty" |
|
不指定标签时会有什么问题
定义结构体的时候,只有字段名是大写的,才会被编码到json当中。
type Account struct {
Email string
password string
Money float64
}
func main() {
account := Account{
Email: "rsj217@gmail.com",
password: "123456",
Money: 100.5,
}
rs, err := json.Marshal(account)
if err != nil{
log.Fatalln(err)
}
fmt.Println(rs)
fmt.Println(string(rs))
}
显示:
- [123 34 69 109 97 105 108 34 58 34 114 115 106 50 49 55 64 103 109 97 105 108 46 99 111 109 34 44 34 77 111 110 101 121 34 58 49 48 48 46 53 125]
- {“Email”:“rsj217@gmail.com”,“Money”:100.5}
可以看到,Marshal方法接受一个空接口的参数,返回一个[]byte结构。小写命名的password字段没有被编码到json当中,生成的json结构字段和Account结构一致。
分析:
- 对于大写字母,将会将变量名转为编码标签
- 对于小写字母,则不会编码
为什么需要结构体标签
- 在定义结构的时候,只有使用大写字母开头的字段才会被导出。
- 而通常json世界中,更盛行小写字母的方式。
看起来就成了一个矛盾。其实不然,golang提供了struct tag的方式可以重命名结构字段的输出形式。
package main
import (
"fmt"
"gopkg.in/gin-gonic/gin.v1/json"
"log"
)
type Account struct {
Email string `json:"email"`
Password string `json:"pass_word"`
Money float64 `json:"money"`
}
func main() {
account := Account{
Email: "rsj217@gmail.com",
Password: "123456",
Money: 100.5,
}
rs, err := json.Marshal(account)
if err != nil{
log.Fatalln(err)
}
fmt.Println(string(rs))
}
我们使用struct tag,重新给Aaccount结构的字段进行了重命名。其中email小写了,并且password字段还使用了下划线,输出的结果如下:
{"email":"rsj217@gmail.com","pass_word":"123456","money":100.5}
-选项
在解析 JSON 数据时,如果目标结构体中包含了未知的 JSON 字段,json.Unmarshal() 函数会返回一个错误。可以使用 json.RawMessage 类型来处理未知 JSON 字段。例如:
type Person struct {
Name string`json:"name"`
Age int`json:"age"`
ExtraData json.RawMessage `json:"-"`
}
var p Person
err := json.Unmarshal([]byte(`{"name":"Alice","age":28,"gender":"female"}`), &p)
if err != nil {
log.Fatal(err)
}
fmt.Println(p.Name, p.Age) // Alice 28
fmt.Println(string(p.ExtraData)) // {"gender":"female"}
在这个例子中,Person 结构体中多了一个 ExtraData 字段,它的类型为 json.RawMessage,并且指定了一个空的 json 标签。json.RawMessage 类型是一个字节数组,可以存储未知的 JSON 字段。
string选项
gplang是静态类型语言,对于类型定义的是不能动态修改。在json处理当中,struct tag的string可以起到部分动态类型的效果。有时候输出的json希望是数字的字符串,而定义的字段是数字类型,那么就可以使用string选项。
package main
import (
"fmt"
"gopkg.in/gin-gonic/gin.v1/json"
"log"
)
type Account struct {
Email string `json:"email"`
Password string `json:"password,omitempty"`
Money float64 `json:"money,string"`
}
func main() {
account := Account{
Email: "rsj217@gmail.com",
Password: "",
Money: 100.5, //{"email":"rsj217@gmail.com","money":"100.5"}
}
rs, err := json.Marshal(account)
if err != nil{
log.Fatalln(err)
}
fmt.Println(string(rs))
}
此时显示: {“email”:“rsj217@gmail.com”,“money”:“100.5”}
可以看到: int类型被编码成了string
omitempty可选字段
- omitempty:
- 一句话描述: 当其有值的时候就输出,而没有值(零值)的时候就不输出
- 说明:在将 Go 结构体类型的值转换为 JSON 字符串时,json.Marshal() 函数会将所有的零值字段都转换为 JSON 字符串中的 null 值。如果需要忽略空值字段,可以使用 omitempty 标记
遇到的问题
(1) string
type Account struct {
Email string `json:"email"`
}
account := Account{}
- {“email”:“”}
type Account struct {
Email string `json:"email"`
}
account := Account{} //{"email":""}
account = Account{
Email : "value",
}
- {“email”:“value”}
omitempty的表现:
(1) string
type Account struct {
Email string `json:"email,omitempty"`
}
account := Account{} //{}
- {}
type Account struct {
Email string `json:"email,omitempty"`
}
account := Account{} //{"email":""}
account = Account{
Email : "value",
}
- {“email”:“value”}
(2)数组
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 声明一个结构体:Json转结构体,或结构体转Json,结构体首字母必须大写
type ColorGroup struct {
ID int `json:"id,string"`
Name string `json:"name,omitempty"`
Colors []string `json:"colors"`
}
// 如果没有设置Name属性值,因为标记为了omitempty属性,则在编码成json的时候会忽略Name属性
group = ColorGroup{
ID: 1,
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
b, err = json.Marshal(group)
if err != nil {
panic(err)
}
fmt.Println(string(b))
// output: {"id":"1","colors":["Crimson","Red","Ruby","Maroon"]}
// 如果没有设置Colors值,因为没有omitempty属性,会输出nil
group = ColorGroup{
ID: 1,
Name: "Reds",
}
b, err = json.Marshal(group)
if err != nil {
panic(err)
}
fmt.Println(string(b))
// output: {"id":"1","name":"Reds","colors":null}
}
其他
package main
import (
"encoding/json"
"fmt"
)
type T1 struct {
FieldInt int `json:"field_int"`
FieldIgnore int `json:"-"` //忽略
FieldBooleab bool `json:"field_boolean,string"` //不同类型
FieldString1 string `json:"field_string1,omitempty"` //忽略空值,当时复合结构时为要为指针类型
FieldString2 string `json:"field_string2,omitempty"`
}
type T2 struct {
T1 `json:",inline"` //表示内嵌与T1输出一致
}
type T3 struct {
T1 `json:"t1"` //表示用t1包一层
}
func main() {
val1 := T1{
FieldInt: 11,
FieldIgnore: 11,
FieldBooleab: true,
FieldString2: "no empty",
}
bty1, _ := json.Marshal(val1)
fmt.Printf("%v\r\n", string(bty1))
val2 := T2{
val1,
}
bty2, _ := json.Marshal(val2)
fmt.Printf("%v\r\n", string(bty2))
val3 := T3{
val1,
}
bty3, _ := json.Marshal(val3)
fmt.Printf("%v\r\n", string(bty3))
}
//程序输出
{"field_int":11,"field_boolean":"true","field_string2":"no empty"}
{"field_int":11,"field_boolean":"true","field_string2":"no empty"}
{"t1":{"field_int":11,"field_boolean":"true","field_string2":"no empty"}}
无效的标签
type Account struct {
Email string `json:"email,required"`
}
account := Account{} //{"email":""}
json.Marshal(account)
type Account struct {
Email string `json:"email" validate:"nonzero"`
}
account := Account{} //{"email":""}
json.Marshal(account)
type Account struct {
Email string `json:"email" validate:"required"`
}
type Account struct {
Email string `json:"email" validate:"nonzero"`
}
account := Account{} //{"email":""}
json.Marshal(account)
其他
package main
import (
"fmt"
"reflect"
)
// Name of the struct tag used in examples
const tagName = "validate"
type User struct {
Id int `validate:"-"`
Name string `validate:"presence,min=2,max=32"`
Email string `validate:"email,required"`
}
func main() {
user := User{
Id: 1,
Name: "John Doe",
Email: "john@example",
}
// TypeOf returns the reflection Type that represents the dynamic type of variable.
// If variable is a nil interface value, TypeOf returns nil.
t := reflect.TypeOf(user)
// Get the type and kind of our user variable
fmt.Println("Type:", t.Name())
fmt.Println("Kind:", t.Kind())
// Iterate over all available fields and read the tag value
for i := 0; i < t.NumField(); i++ {
// Get the field, returns https://golang.org/pkg/reflect/#StructField
field := t.Field(i)
// Get the field tag value
tag := field.Tag.Get(tagName)
fmt.Printf("%d. %v (%v), tag: '%v'\n", i+1, field.Name, field.Type.Name(), tag)
}
}
其中首先导入了reflect这个包,然后通过reflect中的TypeOf,将user结构体变成动态变量。然后遍历结构体程序,通过.Tag.Get方法获取结构体的每一个成员的标签,然后去做我们想做的事情~
Type: User
Kind: struct
1. Id (int), tag: '-'
2. Name (string), tag: 'presence,min=2,max=32'
3. Email (string), tag: 'email,required'
解码器和编码器
https://blog.csdn.net/qq_34893654/article/details/129229605#t9
https://blog.csdn.net/weixin_52690231/article/details/123274409
参考: