Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > golang  json序列化

golang通过反射手动实现json序列化的方法

作者:程序猿-瑞瑞

在 Go 语言中,JSON 序列化和反序列化通常通过标准库 encoding/json 来实现,本文给大家介绍golang  通过反射手动实现json序列化的方法,感兴趣的朋友一起看看吧

一、json

在 Go 语言中,JSON 序列化和反序列化通常通过标准库 encoding/json 来实现。这个包提供了简单易用的接口来将 Go 数据结构转换为 JSON 格式字符串(序列化),以及从 JSON 字符串解析出 Go 数据结构(反序列化)。

1.1 序列化(将 Go 对象转换为 JSON)

使用 json.Marshal() 函数可以将一个 Go 对象转换为 JSON 字符串。

import (
	"encoding/json"
	"fmt"
	"testing"
)
type TestJson struct {
	UserId       int    `json:"user_id"` // 使用结构体标签可以指定字段在 JSON 中的键名
	UserNickname string // 如果没有指定标签,则默认使用字段名。
	UserAge      int    `json:"age,omitempty"` // 使用omitempty可以在序列化时忽略空值
}
func Test1(t *testing.T) {
	tJson1 := TestJson{1, "Jackson", 18}
	tJson2 := new(TestJson)
	jsonStr1, _ := json.Marshal(tJson1)
	jsonStr2, _ := json.Marshal(tJson2)
	fmt.Println(string(jsonStr1))
	fmt.Println(string(jsonStr2))
}

输出

{"user_id":1,"UserNickname":"Jackson","age":18}
{"user_id":0,"UserNickname":""}

美观打印的序列化

如果需要生成格式良好的、可读性更高的输出,可以使用 json.MarshalIndent()

func Test2(t *testing.T) {
	tJson1 := TestJson{1, "Jackson", 18}
	jsonStr1, _ := json.MarshalIndent(tJson1, "", "\t")
	fmt.Println(string(jsonStr1))
}

输出

{
    "user_id": 1,
    "UserNickname": "Jackson",
    "age": 18
}

1.2 反序列化(将 JSON 转换为 Go 对象)

使用 json.Unmarshal() 函数可以将一个 JSON 字符串解析到相应的 Go 数据结构中。

func Test3(t *testing.T) {
	jsonStr :=
		`{
			"user_id": 1,
			"UserNickname": "Jackson",
			"age": 18
		}`
	var tJson TestJson
	// 将字符串转为字节数组,再传入解析后的对象指针
	// 第二个参数必须是指向目标数据类型变量的指针,以便函数能够修改该变量。
	err := json.Unmarshal([]byte(jsonStr), &tJson)
	if err != nil {
		fmt.Println("解析错误:", err)
	} else {
		fmt.Println(tJson)
	}
}

输出

{1 Jackson 18}

1.3 注意

func Test5(t *testing.T) {
	jsonStr :=
		`{
			"user_id": 1,
			"UserNickname": "Jackson",
			"age": 18,
			"addr": ["地址1","地址2"],
			"info":{
				"id":"2",
				"name":"a"
			}
		}`
	var tJson TestJson
	err := json.Unmarshal([]byte(jsonStr), &tJson)
	if err != nil {
		fmt.Println("解析错误:", err)
	} else {
		fmt.Println(tJson)
	}
}

输出

{1 Jackson 18}

灵活性:对于未知或动态数据,可以考虑使用 map 或 interface{} 来接收解码结果,但这会丧失一些类型安全特性。

func Test4(t *testing.T) {
	jsonStr :=
		`{
			"user_id": 1,
			"UserNickname": "Jackson",
			"age": 18,
			"addr": ["地址1","地址2"],
			"info":{
				"id":"2",
				"name":"a"
			}
		}`
	var tJson map[string]any
	err := json.Unmarshal([]byte(jsonStr), &tJson)
	if err != nil {
		fmt.Println("解析错误:", err)
	} else {
		fmt.Println(tJson)
	}
}

输出

map[UserNickname:Jackson addr:[地址1 地址2] age:18 info:map[id:2 name:a] user_id:1]

二、反射手动实现

encoding/json 包在内部大量使用了反射来实现其功能。

JSON 序列化中的反射

JSON 反序列化中的反射

2.1 json简单序列化

思路:模仿 encoding/json 库,读取结构体标签指定序列化的字段名

func serializeSimple(data interface{}) string {
	var resultStr string = "{"
	//1、反射传入的结构体
	reflectDataValue := reflect.ValueOf(data)
	reflectDataType := reflectDataValue.Type()
	if reflectDataValue.Kind() == reflect.Ptr {
		reflectDataValue = reflectDataValue.Elem()
	}
	if reflectDataType.Kind() == reflect.Ptr {
		reflectDataType = reflectDataType.Elem()
	}
	//2、获取字段
	for i := 0; i < reflectDataType.NumField(); i++ {
		field := reflectDataType.Field(i)
		// 字段名
		var filedName = field.Name
		// 字段的值
		filedValue := reflectDataValue.Field(i).Interface()
		//3、判断字段的标签
		if value, ok := field.Tag.Lookup("json"); ok {
			// 如果有json标签,则使用其定义的命名
			filedName = strings.ReplaceAll(value, ",omitempty", "")
			// 是否忽略空值
			if strings.Contains(value, "omitempty") {
				if filedValue == "" || filedValue == 0 {
					continue
				}
			}
		}
		// 拼接json
		resultStr += fmt.Sprintf("\"%s\":\"%v\"", filedName, filedValue)
		resultStr += ","
	}
	//4、拼接字符串
	resultStr += "}"
	// 去掉结尾的,号
	return strings.ReplaceAll(resultStr, ",}", "}")
}
func Test6(t *testing.T) {
	tJson1 := TestJson{1, "Jackson", 18}
	tJson2 := new(TestJson)
	jsonStr1, _ := json.Marshal(tJson1)
	jsonStr2, _ := json.Marshal(tJson2)
	fmt.Println(string(jsonStr1))
	fmt.Println(string(jsonStr2))
	fmt.Println("=========自定义实现=========")
	fmt.Println(serializeSimple(tJson1))
	fmt.Println(serializeSimple(tJson2))
}

输出

{"user_id":1,"UserNickname":"Jackson","age":18}
{"user_id":0,"UserNickname":""}
=========自定义实现=========
{"user_id":"1","UserNickname":"Jackson","age":"18"}
{"user_id":"0","UserNickname":""}

2.2 json简单反序列化

思路

func parseSimple(str string, dataTemp interface{}) {
	// 判断是否标准的json格式数据
	if str != "" && str[0] == '{' && str[len(str)-1] == '}' {
		// 替换掉前后的{}
		str = strings.ReplaceAll(strings.ReplaceAll(str, "{", ""), "}", "")
		// 将结构体的标签解析后,塞入map中备用 :map [字段名] 字段地址
		structMap := map[string]reflect.Value{}
		// 通过反射获取字段名
		rValue := reflect.ValueOf(dataTemp)
		if rValue.Kind() == reflect.Ptr {
			rValue = rValue.Elem()
		}
		rType := rValue.Type()
		for i := 0; i < rType.NumField(); i++ {
			name := rType.Field(i).Name
			// 如果有定义标签,则使用标签的字段名
			if lookup, ok := rType.Field(i).Tag.Lookup("json"); ok {
				name = strings.ReplaceAll(lookup, ",omitempty", "")
			}
			// 将字段名和值映射起来
			structMap[name] = rValue.Field(i)
		}
		// 按照,切割每个键值对
		splitList := strings.Split(str, ",")
		for i := range splitList {
			s := splitList[i]
			// 按照:切割出key 和 value
			keyValue := strings.Split(s, ":")
			key := keyValue[0]
			key = strings.ReplaceAll(strings.TrimSpace(key), "\"", "") // 去除前后的空格
			value := keyValue[1]
			value = strings.ReplaceAll(strings.TrimSpace(value), "\"", "")
			// 按照key将value塞回结构体
			switch structMap[key].Type().Kind() {
			// 注意判断类型,目前只写int和string,其他的类似
			case reflect.Int:
				intValue, _ := strconv.Atoi(value)
				structMap[key].SetInt(int64(intValue))
			case reflect.String:
				structMap[key].SetString(value)
			default:
				panic("暂不支持的数据类型")
			}
		}
	}
}
func Test7(t *testing.T) {
	jsonStr :=
		`{
			"user_id": 1,
			"UserNickname": "Jackson",
			"age": 18
		}`
	var tJson1 TestJson
	var tJson2 TestJson
	err := json.Unmarshal([]byte(jsonStr), &tJson1)
	if err != nil {
		fmt.Println("解析错误:", err)
	} else {
		fmt.Println(tJson1)
	}
	fmt.Println("=========自定义实现=========")
	parseSimple(jsonStr, &tJson2)
	fmt.Println(tJson2)
}

2.3 问题

所以手动实现 JSON 序列化和反序列化只是帮助我们更好地理解数据格式与程序语言之间的映射关系,在实际开发中,使用标准库 encoding/json 是更高效且可靠的方法,因为它已经考虑了许多复杂情况,并进行了性能优化。

2.4 总结

2.4.1 单引号和双引号定义的字符串有什么区别

在 json简单反序列化的例子中,使用了单引号和双引号定义不同类型的字符数据。具体的区别是啥呢?

单引号 (')

双引号 (")

2.4.2 string如何转为int

在 json简单反序列化的例子中,使用了strconv.Atoi函数将字符串(string)转换为整数(int),除此之外还有strconv.ParseInt

func Test8(t *testing.T) {
	str := "123"
	// 第二个参数是基数,10表示十进制
	// 第三个参数是位大小,0表示int64,32表示int32,使用32或64取决于你的系统架构
	num, err := strconv.ParseInt(str, 10, 0)
	if err != nil {
		fmt.Println("转换错误:", err)
	} else {
		fmt.Println("转换结果:", num)
		// 如果你需要int32,可以这样转换
		num32 := int32(num)
		fmt.Println("转换为int32结果:", num32)
		// 如果你需要int,可以这样转换(注意:在32位系统上这将是int32,在64位系统上这将是int64)
		numInt := int(num)
		fmt.Println("转换为int结果:", numInt)
	}
}

输出:

转换结果: 123
转换为int32结果: 123
转换为int结果: 123

到此这篇关于golang 通过反射手动实现json序列化的文章就介绍到这了,更多相关golang json序列化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文