likes
comments
collection
share

扩展包(下):json包的使用技巧本文概述了Go语言中处理JSON数据的关键技巧,涵盖序列化与反序列化的基础操作、结构体

作者站长头像
站长
· 阅读数 96

JSON数据与结构体转换的技巧

在Go语言开发中,处理JSON数据与结构体之间的转换确实是一个常见且重要的任务。Go语言的encoding/json包提供了一套强大的API来实现JSON数据的编码和解码。以下是encoding/json包的基本使用方法:

  1. 定义结构体:首先,需要定义一个或多个结构体来表示需要编码或解码的数据结构。
  2. 编码(Marshaling) :使用json.Marshal函数将结构体实例转换为JSON格式的字节切片。
  3. 解码(Unmarshaling) :使用json.Unmarshal函数将JSON格式的字节切片转换为结构体实例。
  4. 处理错误:编码和解码过程可能会返回错误,需要妥善处理这些错误。
  5. 自定义编码和解码行为:可以通过实现encoding/jsonMarshalerUnmarshaler接口来自定义结构体的编码和解码行为。
  6. 处理 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.Marshaljson.Unmarshalencoding/json包中用于处理JSON数据的两个核心函数,以下是它们的基本用法。

  1. json.Marshal(序列化)

    1. 功能:将Go语言中的数据结构(通常是结构体)转换为JSON格式的字节切片。
    2. 使用方法:调用json.Marshal函数,传入需要序列化的数据结构的变量。
  2. json.Unmarshal(反序列化)

    1. 功能:将JSON格式的字节切片转换为Go语言中的数据结构。
    2. 使用方法:调用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"`

总结:

  1. 结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。
  2. 同一个结构体字段可以设置多个键值对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字段

这段代码将更新UserName字段,但会忽略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
评论
请登录