扩展包(下):json包的使用技巧本文概述了Go语言中处理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
包:
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数据的两个核心函数,以下是它们的基本用法。
-
json.Marshal(序列化)
- 功能:将Go语言中的数据结构(通常是结构体)转换为JSON格式的字节切片。
- 使用方法:调用
json.Marshal
函数,传入需要序列化的数据结构的变量。
-
json.Unmarshal(反序列化)
- 功能:将JSON格式的字节切片转换为Go语言中的数据结构。
- 使用方法:调用
json.Unmarshal
函数,传入JSON数据和用于存储解码结果的变量的指针。
以下是具体的示例代码:
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)
}
输出:
str:{"Name":"小明","Age":18,"Weight":71.5}
p2:main.Person{Name:"小明", Age:18, Weight:71.5}
结构体tag
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
总结:
- 结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。
- 同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。
使用json tag指定字段名
序列化与反序列化默认情况下使用结构体的字段名,我们可以通过给结构体字段添加tag来指定json序列化生成的字段名。
// 使用json tag指定序列化与反序列化时的行为
type Person struct {
Name string `json:"name"` // 指定json序列化/反序列化时使用小写name
Age int64
Weight float64
}
忽略某个字段
如果你想在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}
如果想要在最终的序列化结果中去掉空值字段,可以像下面这样定义结构体:使用omitempty。
// 在tag中添加omitempty忽略空值
// 注意这里 hobby,omitempty 合起来是json tag值,中间用英文逗号分隔
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
}
此时,再执行上述的omitemptyDemo,输出结果如下:
str:{"name":"小明"} // 序列化结果中没有email和hobby字段
在Go语言中使用gorm
操作数据库时,确实可能会遇到需要在序列化或表单提交时忽略特定字段的情况。这通常涉及结构体中的标签(tag)使用,特别是与JSON处理相关的json
标签。
在Go的encoding/json
包中,可以通过在结构体的字段上使用json
标签来指定序列化和反序列化的行为。如果你希望在JSON展示时包含某个字段,但在表单提交时忽略它,可以使用-
来标记该字段,这样它就不会被序列化到JSON中。
例如,假设有一个User
结构体,其中包含一个Profile
字段,它是一个关联实体,你只想在JSON响应中展示它,但在表单提交时忽略它:
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
方法:
db.Model(&user).Updates(User{
Name: "New Name",
}, "Profile.Bio") // 忽略更新Profile的Bio字段
这段代码将更新User
的Name
字段,但会忽略Profile
中的Bio
字段。
忽略嵌套结构体空值字段
首先来看几种结构体嵌套的示例:
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串为单层的:
str:{"name":"小明","hobby":["足球","蓝球"],"site":"","slogan":""}
想要变成嵌套的json串,需要改为具名嵌套或定义字段tag:
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是不够的:
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":""}}
还需要使用嵌套的结构体指针:
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,示例代码如下:
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包从字符串中解析相应字段的数据:
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作为项目开发中的核心数据格式,其高效处理显得尤为关键。
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
转载自:https://juejin.cn/post/7414734289131438099