在Go语言中,可以这样使用Json

在Go语言中,处理JSON数据通常涉及编码(将Go结构体转换为JSON字符串)和解码(将JSON字符串转换为Go结构体)。Go标准库中的encoding/json包提供了这些功能。第三方插件可以使用"github.com/goccy/go-json"也有同样的功能

Marshal函数将会递归遍历整个对象,依次按成员类型对这个对象进行编码,类型转换规则如下:

  • bool类型 转换为JSONBoolean

  • 整数,浮点数等数值类型 转换为JSONNumber

  • string 转换为JSON的字符串(带""引号)

  • struct 转换为JSONObject,再根据各个成员的类型递归打包

  • 数组或切片 转换为JSONArray

  • []byte 会先进行base64编码然后转换为JSON字符串

  • map转换为JSONObjectkey必须是string

  • interface{} 按照内部的实际类型进行转换

  • nil 转为JSONnull

  • channel,func等类型 会返回UnsupportedTypeError

1、使用标准库中的encoding/json

字符串输出&格式化输出&解码

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
)

type ColorGroup struct {
	ID     int
	Name   string
	Colors []string
}

// 创建一个ColorGroup类型的变量来保存解码后的数据
var decodedGroup ColorGroup

func main() {
	group := ColorGroup{
		ID:     1,
		Name:   "Reds",
		Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
	}

	// 将结构体编码为JSON字符串
	jsonData1, err := json.Marshal(group)
	jsonData2, err := json.MarshalIndent(group, "", "	")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// 打印JSON字符串
	fmt.Println(string(jsonData1))
	fmt.Println(string(jsonData2))
	// Output:
	//{"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
	//	{
	//		"ID": 1,
	//		"Name": "Reds",
	//		"Colors": [
	//			"Crimson",
	//			"Red",
	//			"Ruby",
	//			"Maroon"
	//		]
	//	}

	// 将JSON字符串解码到ColorGroup结构体中
	err = json.Unmarshal([]byte(jsonData1), &decodedGroup)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// 打印解码后的数据
	fmt.Printf("ID: %d, Name: %s, Colors: %v\n", decodedGroup.ID, decodedGroup.Name, decodedGroup.Colors)
	// Output: ID: 1, Name: Reds, Colors: [Crimson Red Ruby Maroon]
	fmt.Println(decodedGroup.Colors[0])
	fmt.Println(decodedGroup.Colors[1])
}

2、使用第三方包

标准输出&格式化输出&解码

Go 复制代码
package main

import (
	"fmt"
	"github.com/goccy/go-json"
	"os"
)

type ColorGroup struct {
	ID     int
	Name   string
	Colors []string
}

func main() {
	group := ColorGroup{
		ID:     1,
		Name:   "Reds",
		Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
	}

	b1, err := json.Marshal(group)
	if err != nil {
		fmt.Println("error:", err)
	}
	println(os.Stdout.Write(b1)) //os.Stdout.Write(b1)将字节切片b(即JSON字符串的字节表示)写入到标准输出

	fmt.Println("---------------格式化输出----------------")

	// 使用 MarshalIndent 来格式化输出
	b2, err := json.MarshalIndent(group, "", "  ") // 第二个参数是空字符串,表示不添加前缀;第三个参数是缩进字符串
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	// 使用 fmt.Println 来打印字符串
	// MarshalIndent返回的是字节切片,我们需要使用string(b2)来将其转换为字符串
	fmt.Println(string(b2)) // 将字节切片转换为字符串并打印
	// 输出将会是格式化后的 JSON 字符串

	// 创建一个ColorGroup类型的变量来保存解码后的数据
	var decodedGroup ColorGroup

	// 将JSON字符串解码到ColorGroup结构体中
	err = json.Unmarshal([]byte(b1), &decodedGroup)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// 打印解码后的数据
	fmt.Printf("ID: %d, Name: %s, Colors: %v\n", decodedGroup.ID, decodedGroup.Name, decodedGroup.Colors)
	// Output: ID: 1, Name: Reds, Colors: [Crimson Red Ruby Maroon]
	fmt.Println(decodedGroup.Colors[0])
	fmt.Println(decodedGroup.Colors[1])
}

请注意,在解码时,你需要将JSON字符串转换为[]byte,并且传入结构体的指针(使用&)。这样,解码后的数据才会被写入到结构体中

3、decode

Go 复制代码
package main

import (
	"fmt"
	"github.com/goccy/go-json"
)

// Animal 定义结构体来表示单个JSON对象
type Animal struct {
	Name  string
	Order string
}

func main() {

	//创建一个JSON字节切片
	var jsonBlob = []byte(`[   
		{"Name": "Platypus", "Order": "Monotremata"},   
		{"Name": "Quoll", "Order": "Dasyuromorphia"}   
	]`)

	var animals []Animal
	err := json.Unmarshal(jsonBlob, &animals)
	if err != nil {
		fmt.Println("error:", err)
	}
	fmt.Printf("%+v", animals)
	fmt.Println()
	// 打印解码后的数据
	for _, animal := range animals {
		fmt.Printf("Name: %s, Order: %s\n", animal.Name, animal.Order)
	}
}

4、注意

结构体

结构体必须是大写字母开头的成员才会被JSON处理到,小写字母开头的成员不会有影响。

Mashal时,结构体的成员变量名将会直接作为JSON Objectkey打包成JSONUnmashal时,会自动匹配对应的变量名进行赋值,大小写不敏感。

Unmarshal时,如果JSON中有多余的字段,会被直接抛弃掉;如果JSON缺少某个字段,则直接忽略不对结构体中变量赋值,不会报错。

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
)

type Message struct {
	Name  string
	Body  string
	Time  int64
	inner string
}

func main() {
	var m = Message{
		Name:  "Alice",
		Body:  "Hello",
		Time:  1294706395881547000,
		inner: "ok",
	}
	b := []byte(`{"nAmE":"Bob","Food":"Pickle", "inner":"changed"}`)
	err := json.Unmarshal(b, &m)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}
	fmt.Printf("%v", m)
    //Output: {Bob Hello 1294706395881547000 ok}
}
StructTag/结构体标签

如果希望手动配置结构体的成员和JSON字段的对应关系,可以在定义结构体的时候给成员打标签:

使用omitempty熟悉,如果该字段为nil或0值(数字0,字符串"",空数组[]等),则打包的JSON结果不会有这个字段。

案例一
Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
)

type Message struct {
	Name string `json:"msg_name"`       // 对应JSON的msg_name
	Body string `json:"body,omitempty"` // 如果为空置则忽略字段
	Time int64  `json:"-"`              // 直接忽略字段
}

func main() {
	var m = Message{
		Name: "Alice",
		Body: "",
		Time: 1294706395881547000,
	}
	data, err := json.Marshal(m)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}
	fmt.Println(string(data))
	//Output:{"msg_name":"Alice"}
}
案例二
Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"
)

// 定义一个用于JSON映射的结构体
type User struct {
	Name     string     `json:"username"` // 自定义字段名称映射
	Email    string     `json:"email"`
	LastSeen CustomTime `json:"last_seen"` // 嵌套对象
	Active   bool       `json:"-"`         // 忽略此字段,即使JSON中存在也不解码
}

// CustomTime 是一个用于表示时间的结构体
type CustomTime struct {
	time.Time
}

// 实现 json.Unmarshaler 接口的 UnmarshalJSON 方法
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	// 解析自定义时间格式
	parsedTime, err := time.Parse(time.RFC3339, s)
	if err != nil {
		return err
	}

	ct.Time = parsedTime
	return nil
}

func main() {
	// 模拟从HTTP请求中获取的JSON数据
	jsonData := []byte(`{
		"username": "johndoe",
		"email": "john.doe@example.com",
		"last_seen": "2023-04-01T12:34:56Z",
		"active": true
	}`)

	// 创建一个 User 实例
	var user User

	// 使用 json.Unmarshal 解码 JSON 数据
	if err := json.Unmarshal(jsonData, &user); err != nil {
		log.Fatal("Error unmarshaling JSON:", err)
	}

	// 打印解码后的信息
	fmt.Printf("Name: %s\n", user.Name)
	fmt.Printf("Email: %s\n", user.Email)
	fmt.Printf("Last Seen: %v\n", user.LastSeen)
	// Active 字段将不会被解码,即使JSON中存在
	fmt.Printf("Active: %v\n", user.Active)
	//输出:
	//Name: johndoe
	//Email: john.doe@example.com
	//	Last Seen: 2023-04-01 12:34:56 +0000 UTC
	//Active: false
}

5、更灵活地使用JSON

使用json.RawMessage

json.RawMessage其实就是[]byte类型的重定义。可以进行强制类型转换。

现在有这么一种场景,结构体中的其中一个字段的格式是未知的:

Go 复制代码
type Command struct {
	ID   int
	Cmd  string
	Args *json.RawMessage
}

使用json.RawMessage的话,Args字段在Unmarshal时不会被解析,直接将字节数据赋值给Args。我们可以能先解包第一层的JSON数据,然后根据Cmd的值,再确定Args的具体类型进行第二次Unmarshal

这里要注意的是,一定要使用指针类型*json.RawMessage,否则在Args会被认为是[]byte类型,在打包时会被打包成base64编码的字符串。

案例一
Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Command struct {
	ID   int
	Cmd  string
	Args *json.RawMessage // 未解析的JSON片段
}

func main() {
	//json字节切片
	jsonData := []byte(`{  
		"ID": 1,  
		"Cmd": "example",  
		"Args": ["arg1", "arg2"]  
	}`)

	var cmd Command
	//解码/反序列化
	if err := json.Unmarshal(jsonData, &cmd); err != nil {
		log.Fatalf("Error unmarshaling JSON: %v", err)
	}

	fmt.Printf("Command: %+v\n", cmd)
	// 如果需要,可以进一步处理cmd.Args字段
	// 例如,将其解析为特定的Go类型
	var args []string
	if err := json.Unmarshal(*cmd.Args, &args); err != nil {
		log.Printf("解析错误: %v", err)
	} else {
		fmt.Printf("Args: %v\n", args)
	}

    //输出
    //Command: {ID:1 Cmd:example Args:0xc0000080f0}
    //Args: [arg1 arg2]
}
案例二
Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Command struct {
	ID   int
	Cmd  string
	Args *json.RawMessage // 未解析的JSON片段
}

// UnmarshalJSON 自定义JSON解码方法,Command实现了Unmarshaler接口
func (c *Command) UnmarshalJSON(data []byte) error {
	fmt.Println("--------------使用自定义解码--------------")
	// 定义一个辅助结构体,用于解码除Args外的其他字段
	type alias Command
	var aux struct {
		alias // 嵌入别名类型以获取其他字段
	}

	// 先解码除Args外的所有字段
	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}
	fmt.Printf("Command ID: %+v, Cmd: %+v\n", aux.alias.ID, aux.alias.Cmd)

	// 将别名结构体中的字段复制到c中
	*c = Command(aux.alias)

	// 检查JSON中是否有Args字段,并处理它
	var m map[string]json.RawMessage
	if err := json.Unmarshal(data, &m); err != nil {
		// 如果这里出错,可能是因为JSON格式不正确,但我们可能仍然想要保留已经解析的字段
		// 因此,我们可以只记录一个错误,但不返回它
		log.Printf("Error parsing Args field: %v", err)
	} else {
		// 如果Args字段存在,将其赋值给c.Args
		if rawArgs, ok := m["Args"]; ok {
			c.Args = &rawArgs // 注意这里我们取了rawArgs的地址

			var args []string
			if err := json.Unmarshal(*c.Args, &args); err != nil {
				log.Printf("Error parsing Args contents: %v", err)
			} else {
				fmt.Printf("Args: %v\n", args)
			}
		}
	}

	// 如果没有错误,返回nil
	return nil
}

func main() {
	//json字节切片
	jsonData := []byte(`{  
		"ID": 1,  
		"Cmd": "example",  
		"Args": ["arg1", "arg2"]  
	}`)

	var cmd Command
	//解码/反序列化
	if err := json.Unmarshal(jsonData, &cmd); err != nil {
		log.Fatalf("Error unmarshaling JSON: %v", err)
	}
}
案例三
Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Command struct {
	ID   int
	Cmd  string
	Args *json.RawMessage // 未解析的JSON片段
}

// UnmarshalJSON 自定义JSON解码方法,Command实现了Unmarshaler接口
func (c *Command) UnmarshalJSON(data []byte) error {
	fmt.Println("--------------使用自定义解码--------------")
	// 检查JSON中是否有Args字段,并处理它
	var m map[string]json.RawMessage
	if err := json.Unmarshal(data, &m); err != nil {
		// 如果这里出错,可能是因为JSON格式不正确,但我们可能仍然想要保留已经解析的字段
		// 因此,我们可以只记录一个错误,但不返回它
		log.Printf("Error parsing Args field: %v", err)
	} else {
		// 如果Args字段存在,将其赋值给c.Args
		if rawArgs, ok := m["Args"]; ok {
			c.Args = &rawArgs // 注意这里我们取了rawArgs的地址

			var args []string
			if err := json.Unmarshal(*c.Args, &args); err != nil {
				log.Printf("Error parsing Args contents: %v", err)
			} else {
				fmt.Printf("Args: %v\n", args)
			}
		}
	}

	// 如果没有错误,返回nil
	return nil
}

func main() {
	//json字节切片
	jsonData := []byte(`{  
		"ID": 1,  
		"Cmd": "example",  
		"Args": ["arg1", "arg2"]  
	}`)

	var cmd Command
	//解码/反序列化
	if err := json.Unmarshal(jsonData, &cmd); err != nil {
		log.Fatalf("Error unmarshaling JSON: %v", err)
	}
}

调用的json.Unmarshal,并不是调用json.Unmarshaler,为什么会调用UnmarshalJSON

调用 json.Unmarshal 函数时,您并没有直接调用 json.Unmarshaler 接口的方法。但是,json.Unmarshal 函数在内部会检查目标类型是否实现了 json.Unmarshaler 接口。如果实现了该接口,json.Unmarshal 就会使用您为该类型定义的 UnmarshalJSON 方法来解码 JSON 数据。

这是 json.Unmarshal 函数内部逻辑的一部分,用于确定如何解码 JSON 数据。具体步骤如下:

  1. json.Unmarshal 接收一个字节切片(包含 JSON 数据)和一个目标值的指针。
  2. 它首先会检查目标值的类型是否实现了 json.Unmarshaler 接口。
  3. 如果实现了 json.Unmarshaler 接口,json.Unmarshal 就会调用该类型的 UnmarshalJSON 方法,并将 JSON 数据的字节切片作为参数传递给它。
  4. 如果目标值没有实现 json.Unmarshaler 接口,json.Unmarshal 就会使用默认的解码逻辑来填充目标值的字段。

这种机制使得开发者能够灵活地控制 JSON 数据到 Go 结构体之间的转换过程。通过实现 json.Unmarshaler 接口,您可以:

  • 处理 JSON 数据中不存在的字段。
  • 自定义字段名称的映射规则。
  • 处理 JSON 数据中的嵌套对象或数组。
  • 执行额外的验证或数据处理逻辑。

以下是简单的示例,展示了如何为一个类型实现 json.Unmarshaler 接口

处理 JSON 数据中不存在的字段

假设我们有一个结构体,它能够处理JSON中可能缺失的字段,并且为这些字段提供默认值。

在这个例子中,Age 字段在JSON中不存在,因此它将被赋予其类型的零值(对于int类型是0)。

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type User struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email,omitempty"`
}

func main() {
	var user User
	// JSON 中没有 "age" 字段,将使用 Age 的零值 0
	jsonData := []byte(`{"name": "John", "email": "john@example.com"}`)
	if err := json.Unmarshal(jsonData, &user); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Name: %s, Age: %d, Email: %s\n", user.Name, user.Age, user.Email)
	//Name: John, Age: 0, Email: john@example.com
}

自定义字段名称的映射规则

使用结构体标签中的json键来指定JSON字段名。

在这个例子中,结构体的字段名和JSON字段名不匹配,我们通过在结构体标签中指定json来实现映射。

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type User struct {
	Username string `json:"user_name"`
	Password string `json:"pass"`
}

func main() {
	var user User
	jsonData := []byte(`{"user_name": "johndoe", "pass": "secret"}`)
	if err := json.Unmarshal(jsonData, &user); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Username: %s, Password: %s\n", user.Username, user.Password)
	//Username: johndoe, Password: secret
}

处理 JSON 数据中的嵌套对象或数组

解码一个包含嵌套结构体的JSON数据。

在这个例子中,Address 是一个嵌套在 User 结构体中的对象。

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Address struct {
	City    string `json:"city"`
	Country string `json:"country"`
}

type User struct {
	Name    string  `json:"name"`
	Address Address `json:"address"` // 嵌套对象
}

func main() {
	var user User
	jsonData := []byte(`{"name": "Jane", "address": {"city": "New York", "country": "USA"}}`)
	if err := json.Unmarshal(jsonData, &user); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Name: %s, Lives in %s, %s\n", user.Name, user.Address.City, user.Address.Country)
	//Name: Jane, Lives in New York, USA
}

执行额外的验证或数据处理逻辑

UnmarshalJSON方法中添加额外的验证逻辑。

在这个例子中,我们为User类型实现了自定义的UnmarshalJSON方法。在解码过程中,如果Age字段的值是负数,将返回一个错误,这是一个额外的验证逻辑。

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func (u *User) UnmarshalJSON(data []byte) error {
	type Alias User                         // 影子类型,避免递归调用 UnmarshalJSON
	aux := &Alias{Name: u.Name, Age: u.Age} // 使用辅助结构体来解耦
	if err := json.Unmarshal(data, aux); err != nil {
		return err
	}
	*u = User(*aux) // 将解耦的结构体赋值给当前结构体
	if u.Age < 0 {  //年龄不能为负数
		return fmt.Errorf("age cannot be negative")
	}
	return nil
}

func main() {
	var user User
	jsonData := []byte(`{"name": "Alice", "age": -5}`)
	if err := json.Unmarshal(jsonData, &user); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Name: %s, Age: %d\n", user.Name, user.Age)
}

在上面的示例中,User类型实现了 json.Unmarshaler 接口的 UnmarshalJSON 方法,使得 json.Unmarshal 函数在解码 JSON 数据时会调用这个方法,而不是使用默认的解码逻辑。这允许我们自定义解码逻辑,例如只接受特定格式的 JSON 数据。

使用interface{}

interface{}类型在Unmarshal时,会自动将JSON转换为对应的数据类型:

JSON的boolean 转换为bool
JSON的数值 转换为float64
JSON的字符串 转换为string
JSON的Array 转换为[]interface{}
JSON的Object 转换为map[string]interface{}
JSON的null 转换为nil

需要注意的有两个。一个是所有的JSON数值自动转换为float64类型,使用时需要再手动转换为需要的intint64等类型。第二个是JSONobject自动转换为map[string]interface{}类型,访问时直接用JSON ``Object的字段名作为key进行访问。再不知道JSON数据的格式时,可以使用interface{}

相关推荐
一点媛艺1 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风1 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生2 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程2 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man3 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang