扩展包(下):json包的使用技巧
JSON数据与结构体转换的技巧
在Go语言开发中,处理JSON数据与结构体之间的转换确实是一个常见且重要的任务。Go语言的encoding/json包提供了一套强大的API来实现JSON数据的编码和解码。以下是encoding/json包的基本使用方法:
- 定义结构体:首先,需要定义一个或多个结构体来表示需要编码或解码的数据结构。
- 编码(Marshaling) :使用
json.Marshal函数将结构体实例转换为JSON格式的字节切片。 - 解码(Unmarshaling) :使用
json.Unmarshal函数将JSON格式的字节切片转换为结构体实例。 - 处理错误:编码和解码过程可能会返回错误,需要妥善处理这些错误。
- 自定义编码和解码行为:可以通过实现
encoding/json的Marshaler和Unmarshaler接口来自定义结构体的编码和解码行为。 - 处理 JSON 标签:可以通过在结构体字段中使用
json标签来控制JSON的键名和省略空值等。
以下是一个简单的示例代码,展示了如何使用encoding/json包:
go
```package main import ( "encoding/json" "fmt" "log" ) // User 结构体用于表示用户信息 type User struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email,omitempty"` IsAdmin bool `json:"isAdmin"` } func main() { // 创建一个User实例 user := User{ Name: "John Doe", Age: 30, Email: "john@example.com", IsAdmin: true, } // 将User实例编码为JSON jsonBytes, err := json.Marshal(user) if err != nil { log.Fatal(err) } // 输出JSON字符串 fmt.Println(string(jsonBytes)) // 解码JSON到另一个User实例 var newUser User err = json.Unmarshal(jsonBytes, &newUser) if err != nil { log.Fatal(err) } // 输出解码后的用户信息 fmt.Printf("%+v\n", newUser) }``
### 基本的序列化和反序列化
#### json.Marshal(序列化)与json.Unmarshal(反序列化)的基本用法
在Go语言中,`json.Marshal`和`json.Unmarshal`是`encoding/json`包中用于处理JSON数据的两个核心函数,以下是它们的基本用法。
1. **json.Marshal(序列化)**
1. 功能:将Go语言中的数据结构(通常是结构体)转换为JSON格式的字节切片。
2. 使用方法:调用`json.Marshal`函数,传入需要序列化的数据结构的变量。
2. **json.Unmarshal(反序列化)**
1. 功能:将JSON格式的字节切片转换为Go语言中的数据结构。
2. 使用方法:调用`json.Unmarshal`函数,传入JSON数据和用于存储解码结果的变量的指针。
以下是具体的示例代码:
``` go
type Person struct { Name string Age int64 Weight float64 } func main() { p1 := Person{ Name: "小明", Age: 18, Weight: 71.5, } // struct -> json string b, err := json.Marshal(p1) if err != nil { fmt.Printf("json.Marshal failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) // json string -> struct var p2 Person err = json.Unmarshal(b, &p2) if err != nil { fmt.Printf("json.Unmarshal failed, err:%v\n", err) return } fmt.Printf("p2:%#v\n", p2) }输出:
go
str:{"Name":"小明","Age":18,"Weight":71.5} p2:main.Person{Name:"小明", Age:18, Weight:71.5}结构体tag
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
go
``` `key1:"value1" key2:"value2"` ``
**总结:**
1. 结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。
2. 同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。
#### 使用json tag指定字段名
序列化与反序列化默认情况下使用结构体的字段名,我们可以通过给结构体字段添加tag来指定json序列化生成的字段名。
``` go
```// 使用json tag指定序列化与反序列化时的行为 type Person struct { Name string `json:"name"` // 指定json序列化/反序列化时使用小写name Age int64 Weight float64 }``
#### 忽略某个字段
如果你想在json序列化/反序列化的时候忽略掉结构体中的某个字段,可以按如下方式在tag中添加`-`。
``` go
```// 使用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。
举个例子:
``` go
```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) }``
输出结果:
``` go
str:{"name":"小明","email":"","hobby":null}如果想要在最终的序列化结果中去掉空值字段,可以像下面这样定义结构体:使用omitempty。
go
```// 在tag中添加omitempty忽略空值 // 注意这里 hobby,omitempty 合起来是json tag值,中间用英文逗号分隔 type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` }``
此时,再执行上述的omitemptyDemo,输出结果如下:
``` go
str:{"name":"小明"} // 序列化结果中没有email和hobby字段在Go语言中使用gorm操作数据库时,确实可能会遇到需要在序列化或表单提交时忽略特定字段的情况。这通常涉及结构体中的标签(tag)使用,特别是与JSON处理相关的json标签。
在Go的encoding/json包中,可以通过在结构体的字段上使用json标签来指定序列化和反序列化的行为。如果你希望在JSON展示时包含某个字段,但在表单提交时忽略它,可以使用-来标记该字段,这样它就不会被序列化到JSON中。
例如,假设有一个User结构体,其中包含一个Profile字段,它是一个关联实体,你只想在JSON响应中展示它,但在表单提交时忽略它:
go
```type User struct { ID int Name string Profile Profile `json:"profile" gorm:"foreignKey:UserID"` // Profile关联实体 } type Profile struct { UserID int Bio string `json:"-"` // 忽略此字段的序列化 }``
在这个例子中,`Profile`结构体的`Bio`字段使用了`json:"-"`标签,这告诉`encoding/json`包在序列化`Profile`结构体时忽略这个字段。
如果你使用的是`gorm`的批量赋值特性来更新模型,并且想要忽略某些字段,可以使用`Omit`方法:
``` go
db.Model(&user).Updates(User{ Name: "New Name", }, "Profile.Bio") // 忽略更新Profile的Bio字段这段代码将更新User的Name字段,但会忽略Profile中的Bio字段。
忽略嵌套结构体空值字段
首先来看几种结构体嵌套的示例:
go
```type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` Profile } type Profile struct { Website string `json:"site"` Slogan string `json:"slogan"` } func nestedStructDemo() { 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) }``
匿名嵌套Profile时序列化后的json串为单层的:
``` go
str:{"name":"小明","hobby":["足球","蓝球"],"site":"","slogan":""}想要变成嵌套的json串,需要改为具名嵌套或定义字段tag:
go
```type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` Profile `json:"profile"` } // str:{"name":"小明","hobby":["足球","篮球"],"profile":{"site":"","slogan":""}}``
想要在嵌套的结构体为空值时,忽略该字段,仅添加omitempty是不够的:
``` go
```type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` Profile `json:"profile,omitempty"` } // str:{"name":"小明","hobby":["足球","篮球"],"profile":{"site":"","slogan":""}}``
还需要使用嵌套的结构体指针:
``` go
```type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` Hobby []string `json:"hobby,omitempty"` *Profile `json:"profile,omitempty"` //这里是重点 } // str:{"name":"小明","hobby":["足球","篮球"]}``
#### 不修改原结构体忽略空值字段
我们需要json序列化User,但是不想把密码也序列化,又不想修改User结构体,这个时候就可以使用创建另外一个结构体PublicUser匿名嵌套原User,同时指定Password字段为匿名结构体指针类型,并添加omitemptytag,示例代码如下:
``` go
```type User struct { Name string `json:"name"` Password string `json:"password"` } type PublicUser struct { *User // 匿名嵌套 Password *struct{} `json:"password,omitempty"` } func omitPasswordDemo() { u1 := User{ Name: "小明", Password: "123456", } b, err := json.Marshal(PublicUser{User: &u1}) if err != nil { fmt.Printf("json.Marshal u1 failed, err:%v\n", err) return } fmt.Printf("str:%s\n", b) // str:{"name":"小明"} }``
#### 优雅处理字符串格式的数字
有时候,前端在传递来的json数据中可能会使用字符串类型的数字,此时可以在结构体tag中添加string来告诉json包从字符串中解析相应字段的数据:
``` go
```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} }``
### 总结
本文概述了Go语言中处理JSON数据的关键技巧,涵盖序列化与反序列化的基础操作、结构体标签(Tag)的巧妙应用,以及嵌套结构体的灵活处理。JSON作为项目开发中的核心数据格式,其高效处理显得尤为关键。
## 欢迎关注 ❤
我们搞了一个**免费的面试真题共享群**,互通有无,一起刷题进步。
**没准能让你能刷到自己意向公司的最新面试题呢。**
感兴趣的朋友们可以加我微信:**wangzhongyang1993**,备注:掘金面试群。