go json技巧以及强大的三方json库(jsoniter)

忽略某个字段

在json序列化/反序列化的时候忽略掉结构体中的某个字段,可以按如下方式在tag中添加-

// 使用json tag指定json序列化与反序列化时的行为
type Person struct {
	Name   string `json:"name"` // 指定json序列化/反序列化时使用小写name
	Age    int64
	Weight float64 `json:"-"` // 指定json序列化/反序列化时忽略此字段
}

忽略空值字段

当 struct 中的字段没有值时, json.Marshal() 序列化的时候不会忽略这些字段,而是默认输出字段的类型零值(例如int和float类型零值是 0,string类型零值是"",对象类型零值是 nil)。如果想要在序列序列化时忽略这些没有值的字段时,可以在对应字段添加omitempty tag。

type User struct {
	Name  string   `json:"name"`
	Email string   `json:"email"`
	Hobby []string `json:"hobby"`
}

func omitemptyDemo() {
	u1 := User{
		Name: "哈哈",
	}
	// struct -> json string
	b, err := json.Marshal(u1)
	if err != nil {
		fmt.Printf("json.Marshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b)
	// str:{"name":"哈哈","email":"","hobby":null}
}
// 在tag中添加omitempty忽略空值
// 注意这里 hobby,omitempty 合起来是json tag值,中间用英文逗号分隔
type User struct {
	Name  string   `json:"name"`
	Email string   `json:"email,omitempty"`
	Hobby []string `json:"hobby,omitempty"`
}

str:{"name":"哈哈"} // 序列化结果中没有email和hobby字段

忽略嵌套结构体空值字段

想要在嵌套的结构体为空值时,忽略该字段,仅添加omitempty是不够的:

type User struct {
	Name  string   `json:"name"`
	Email string   `json:"email,omitempty"`
	Hobby []string `json:"hobby,omitempty"`
	Profile Profile `json:"profile"`
}

type Profile struct {
	Website string `json:"site,omitempty"`
	Slogan  string `json:"slogan,omitempty"`
}

func main() {
	u1 := User{
		Name:  "哈哈",
		Hobby: []string{"足球", "双色球"},
	}
	b, err := json.Marshal(u1)
	if err != nil {
		fmt.Printf("json.Marshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b)
	// str:{"name":"哈哈","hobby":["足球","双色球"],"profile":{}}
}

还需要使用嵌套的结构体指针:

type User struct {
	Name  string   `json:"name"`
	Email string   `json:"email,omitempty"`
	Hobby []string `json:"hobby,omitempty"`
	Profile *Profile `json:"profile,omitempty"`
}

type Profile struct {
	Website string `json:"site"`
	Slogan  string `json:"slogan"`
}

func main() {
	u1 := User{
		Name:  "哈哈",
		Hobby: []string{"足球", "双色球"},
	}
	b, err := json.Marshal(u1)
	if err != nil {
		fmt.Printf("json.Marshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b)
	// str:{"name":"哈哈","hobby":["足球","双色球"]}
}

优雅处理字符串格式的数字

前端在传递来的json数据中可能会使用字符串类型的数字,这个时候可以在结构体tag中添加string来告诉json包从字符串中解析相应字段的数据:

type Card struct {
	ID    int64   `json:"id,string"`    // 添加string tag
	Score float64 `json:"score,string"` // 添加string tag
}

func intAndStringDemo() {
	jsonStr1 := `{"id": "1234567","score": "88.50"}`
	var c1 Card
	if err := json.Unmarshal([]byte(jsonStr1), &c1); err != nil {
		fmt.Printf("json.Unmarsha jsonStr1 failed, err:%v\n", err)
		return
	}
	fmt.Printf("c1:%#v\n", c1) // c1:main.Card{ID:1234567, Score:88.5}
}

整数变浮点数

在 JSON 协议中是没有整型和浮点型之分的,它们统称为number。json字符串中的数字经过Go语言中的json包反序列化之后都会成为float64类型。
使用结构其指定类型接受就没啥问题,但将JSON格式的数据反序列化为map[string]interface{}时,数字都会变成科学计数法表示的浮点数。

func useNumberDemo(){
	type student struct {
		ID int64 `json:"id"`
		Name string `json:"q1mi"`
	}
	s := student{ID: 123456789,Name: "q1mi"}
	b, _ := json.Marshal(s)
	var m map[string]interface{}
	// decode
	json.Unmarshal(b, &m)
	fmt.Printf("id:%#v %v %T\n", m["id"],m["id"],m["id"])  // 1.23456789e+08

	// use Number decode
	decoder := json.NewDecoder(bytes.NewReader(b))
	decoder.UseNumber()
	decoder.Decode(&m)
	fmt.Printf("id:%#v %v %T\n", m["id"],m["id"],m["id"])  // 123456789
	
	// id:1.23456789e+08 1.23456789e+08 float64
	// id:"123456789" 123456789 json.Number
}

如果想更合理的处理数字就需要使用decoder去反序列化,示例代码如下:

func decoderDemo() {
	// map[string]interface{} -> json string
	var m = make(map[string]interface{}, 1)
	m["count"] = 1 // int
	b, err := json.Marshal(m)
	if err != nil {
		fmt.Printf("marshal failed, err:%v\n", err)
	}
	fmt.Printf("str:%#v\n", string(b))
	
	// json string -> map[string]interface{}
	var m2 map[string]interface{}
	// 使用decoder方式反序列化,指定使用number类型
	decoder := json.NewDecoder(bytes.NewReader(b))
	decoder.UseNumber()
	err = decoder.Decode(&m2)
	if err != nil {
		fmt.Printf("unmarshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("value:%v\n", m2["count"]) // 1
	fmt.Printf("type:%T\n", m2["count"])  // json.Number
	
	// 将m2["count"]转为json.Number之后调用Int64()方法获得int64类型的值	
	// “无法将类型“interface{}”的表达式转换为类型“string””这个报错很明显,这时候类型转换就需要用到类型断言了。
	// x为非接口类型,会编译不通过。也就是断言只能用于接口值变量,不能用于普通变量
	// 断言失败后,value为T类型,value的值为T类型的默认值
	count, err := m2["count"].(json.Number).Int64()
	if err != nil {
		fmt.Printf("parse to int64 failed, err:%v\n", err)
		return
	}
	fmt.Printf("type:%T\n", int(count)) // int
}

在处理number类型的json字段时需要先得到json.Number类型,然后根据该字段的实际类型调用Float64()或Int64()。

自定义解析时间字段

内置的json包不识别我们常用的字符串时间格式

func marTime(){
	jsonStr:=`{"create_time":"2022-05-28 12:01:42"}`
	p:=new(Post)
	err:=json.Unmarshal([]byte(jsonStr),p)
	if err!=nil{
		log.Fatalln(err)
	}
	fmt.Println(p)
}
// 2022/05/28 14:54:26 parsing time "\"2022-05-28 12:01:42\"" as "\"2006-01-02T15:04:05Z07:00\"": cannot parse " 12:01:42\"" as "T"
// exit status 1

自定义MarshalJSON和UnmarshalJSON方法

为某个类型实现了MarshalJSON()([]byte, error)和UnmarshalJSON(b []byte) error方法,那么这个类型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)时就会使用你定制的相应方法。

type Order struct {
	ID          int       `json:"id"`
	Title       string    `json:"title"`
	CreatedTime time.Time `json:"created_time"`
}

const layout = "2006-01-02 15:04:05"

// MarshalJSON 为Order类型实现自定义的MarshalJSON方法
func (o *Order) MarshalJSON() ([]byte, error) {
	type TempOrder Order // 定义与Order字段一致的新类型
	return json.Marshal(struct {
		CreatedTime string `json:"created_time"`
		*TempOrder         // 避免直接嵌套Order进入死循环
	}{
		CreatedTime: o.CreatedTime.Format(layout),
		TempOrder:   (*TempOrder)(o),
	})
}

// UnmarshalJSON 为Order类型实现自定义的UnmarshalJSON方法
func (o *Order) UnmarshalJSON(data []byte) error {
	type TempOrder Order // 定义与Order字段一致的新类型
	ot := struct {
		CreatedTime string `json:"created_time"`
		*TempOrder         // 避免直接嵌套Order进入死循环
	}{
		TempOrder: (*TempOrder)(o),
	}
	if err := json.Unmarshal(data, &ot); err != nil {
		return err
	}
	var err error
	o.CreatedTime, err = time.Parse(layout, ot.CreatedTime)
	if err != nil {
		return err
	}
	return nil
}

// 自定义序列化方法
func customMethodDemo() {
	o1 := Order{
		ID:          123456,
		Title:       "《七米的Go学习笔记》",
		CreatedTime: time.Now(),
	}
	// 通过自定义的MarshalJSON方法实现struct -> json string
	b, err := json.Marshal(&o1)
	if err != nil {
		fmt.Printf("json.Marshal o1 failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b)
	// 通过自定义的UnmarshalJSON方法实现json string -> struct
	jsonStr := `{"created_time":"2020-04-05 10:18:20","id":123456,"title":"《七米的Go学习笔记》"}`
	var o2 Order
	if err := json.Unmarshal([]byte(jsonStr), &o2); err != nil {
		fmt.Printf("json.Unmarshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("o2:%#v\n", o2)
}

使用第三方库

go get -u github.com/tidwall/gjson

库名Star
标准库 JSON Unmarshal
valyala/fastjson1.2 k
tidwall/gjson8.3 k
buger/jsonparser4 k

使用匿名结构体添加字段

使用内嵌结构体能够扩展结构体的字段,但有时候我们没有必要单独定义新的结构体,可以使用匿名结构体简化操作:

type UserInfo struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func anonymousStructDemo() {
	u1 := UserInfo{
		ID:   123456,
		Name: "七米",
	}
	// 使用匿名结构体内嵌User并添加额外字段Token
	b, err := json.Marshal(struct {
		*UserInfo
		Token string `json:"token"`
	}{
		&u1,
		"91je3a4s72d1da96h",
	})
	if err != nil {
		fmt.Printf("json.Marsha failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b)
	// str:{"id":123456,"name":"七米","token":"91je3a4s72d1da96h"}
}

bson,json的区别

BSON是由10gen开发的一个数据格式,目前主要用于MongoDB中,是MongoDB的数据存储格式。BSON基于JSON格式,选择JSON进行改造的原因主要是JSON的通用性及JSON的schemaless的特性。

BSON主要会实现以下三点目标:

1.更快的遍历速度
对JSON格式来说,太大的JSON结构会导致数据遍历非常慢。在JSON中,要跳过一个文档进行数据读取,需要对此文档进行扫描才行,需要进行麻烦的数据结构匹配,比如括号的匹配,而BSON对JSON的一大改进就是,它会将JSON的每一个元素的长度存在元素的头部,这样你只需要读取到元素长度就能直接seek到指定的点上进行读取了。

2.操作更简易
对JSON来说,数据存储是无类型的,比如你要修改基本一个值,从9到10,由于从一个字符变成了两个,所以可能其后面的所有内容都需要往后移一位才可以。而使用BSON,你可以指定这个列为数字列,那么无论数字从9长到10还是100,我们都只是在存储数字的那一位上进行修改,不会导致数据总长变大。当然,在MongoDB中,如果数字从整形增大到长整型,还是会导致数据总长变大的。

3.增加了额外的数据类型
JSON是一个很方便的数据交换格式,但是其类型比较有限。BSON在其基础上增加了“byte array”数据类型。这使得二进制的存储不再需要先base64转换后再存成JSON。大大减少了计算开销和数据大小。

当然,在有的时候,BSON相对JSON来说也并没有空间上的优势,比如对{“field”:7},在JSON的存储上7只使用了一个字节,而如果用BSON,那就是至少4个字节(32位)

目前在10gen的努力下,BSON已经有了针对多种语言的编码解码包。并且都是Apache 2 license下开源的。并且还在随着MongoDB进一步地发展。关于BSON,

json三方库

encoding/json

go默认的json模块是通过reflection和interface来完成工作, 性能低。

func main() {
	jsonStr:=`{"username":"Gin","password":"Gin"}`
	dst:=make(map[string]interface{})
	err:=json.Unmarshal([]byte(jsonStr), &dst)
	if err!=nil{
		log.Fatalln(err)
	}else{
		fmt.Println(dst)
	}
}

easyjson

easyjson则通过预先生成编码与解码方法,避免反射来提高效率。

根据官方文档,easyjson -all <file>.go生成一个go文件,然后调用里边儿的方法,start 4k,劝退。。。

json-iterator

引用官方介绍:A high-performance 100% compatible drop-in replacement of "encoding/json",star也达到了惊人的11k。

这是作者基准测试的数据:

在这里插入图片描述

安装

go get github.com/json-iterator/go

使用

func main() {
	jsonStr:=`{"username":"Gin","password":"Gin"}`
	dst:=make(map[string]interface{})
	err:=jsoniter.Unmarshal([]byte(jsonStr),&dst)
	if err!=nil{
		log.Fatalln(err)
	}else{
		fmt.Println(dst)
	}
}
编码
import "encoding/json"
json.Marshal(&data)

import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary
json.Marshal(&data)
解码
import "encoding/json"
json.Unmarshal(input, &data)

import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary
json.Unmarshal(input, &data)

gin使用jsoniter

在这里插入图片描述
Gin 默认采用 encoding/json 包:

// github.com/gin-gonic/gin@v1.6.3/internal/json/json.go
// +build !jsoniter

package json

import "encoding/json"

var (
	// Marshal is exported by gin/json package.
	Marshal = json.Marshal
	// Unmarshal is exported by gin/json package.
	Unmarshal = json.Unmarshal
	// MarshalIndent is exported by gin/json package.
	MarshalIndent = json.MarshalIndent
	// NewDecoder is exported by gin/json package.
	NewDecoder = json.NewDecoder
	// NewEncoder is exported by gin/json package.
	NewEncoder = json.NewEncoder
)

同时还支持了 jsoniter

// github.com/gin-gonic/gin@v1.6.3/internal/json/jsoniter.go
// +build jsoniter

package json

import "github.com/json-iterator/go"

var (
	json = jsoniter.ConfigCompatibleWithStandardLibrary
	// Marshal is exported by gin/json package.
	Marshal = json.Marshal
	// Unmarshal is exported by gin/json package.
	Unmarshal = json.Unmarshal
	// MarshalIndent is exported by gin/json package.
	MarshalIndent = json.MarshalIndent
	// NewDecoder is exported by gin/json package.
	NewDecoder = json.NewDecoder
	// NewEncoder is exported by gin/json package.
	NewEncoder = json.NewEncoder
)

怎么才能使用到 jsoniter 呢?源码中已经明确了编译tag,所以,我们只需在编译时带上这个 tag 就可以了,例如:

go build -tags=jsoniter main.go

// 或者
go run -tags=jsoniter main.go

注入使用jsoniter

要在 Gin 框架中将默认的 encoding/json 替换为 jsoniter,你需要进行一些设置。jsoniter 是一个兼容标准库 encoding/json 的 JSON 序列化和反序列化库,它具有更高的性能。

以下是替换过程的基本步骤:

  1. 安装 jsoniter

    go get github.com/json-iterator/go
    
  2. 在你的 Go 代码中导入 jsoniter 包:

    import "github.com/json-iterator/go"
    
  3. 在初始化阶段,将 jsoniter.Config 设置为 gin 的 JSON 处理器:

    package main
    
    import (
        "github.com/gin-gonic/gin"
        "github.com/json-iterator/go"
    )
    
    var json = jsoniter.ConfigCompatibleWithStandardLibrary
    
    func main() {
        router := gin.Default()
    
        // 使用 jsoniter 替换默认的 JSON 处理器
        router.Use(func(c *gin.Context) {
            c.Set("json", json)
            c.Next()
        })
    
        router.GET("/ping", func(c *gin.Context) {
            // 使用 c.MustGet("json").(*jsoniter.Config) 获取 jsoniter 实例
            c.JSON(200, gin.H{"message": "pong"})
        })
    
        router.Run(":8080")
    }
    

这个例子中的关键部分是使用 router.Use 中间件设置 jsoniter 实例,然后在路由处理程序中使用 c.MustGet("json").(*jsoniter.Config) 获取该实例,以便在 c.JSON 中使用。

这样,你就成功地将 Gin 的默认 JSON 处理器替换为 jsoniter

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值