golang:encoding/json库, 处理JSON编码

为什么不推荐用官方包

不好用

  • 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也会有对象的结构所匹配。大致对应关系如下:

数据类型JSONGolang
字串stringstring
整数numberint64
浮点数numberflaot64
数组arraryslice
对象objectstruct
布尔boolbool
空值nullnil

官方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 数据解析后存储到指向目标结构体的指针中。

  • 对于结构体进行编码:字段的首字母必须大写,否则无法编码
字段名数据类型编码含义
Namestringjson:"-"
  • 在使用json编码时,不编码这个字段 
  • - 标签的作用是不进行序列化,其效果和直接将结构体中的字段写成小写的效果一样
Subjectstringjson:"Subject_name"
  • 在json编码时,这个字段会编码成Subject_name
Ageintjson:"age,string"
  • 在json编码时,将age转换成string类型
  • struct tag的string可以起到部分动态类型的效果
Addressstringjson:"address,omitempty"
  • 在json编码时,如果这个字段是空的,那么忽略掉,不参与编码
  • omitempty 标签可以在序列化的时候忽略 0 值或者空值

不指定标签时会有什么问题

定义结构体的时候,只有字段名是大写的,才会被编码到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

参考:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值