目录
忽略某个字段
在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/fastjson | 1.2 k |
tidwall/gjson | 8.3 k |
buger/jsonparser | 4 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 序列化和反序列化库,它具有更高的性能。
以下是替换过程的基本步骤:
-
安装
jsoniter
:go get github.com/json-iterator/go
-
在你的 Go 代码中导入
jsoniter
包:import "github.com/json-iterator/go"
-
在初始化阶段,将
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
。