想到一个场景,由于数据库操作感觉新建修改各种字段不太方便,想搞一个字段能够装下所有,但是还要限制一下字段的内容及类型。
譬如说如下场景,接口传来了如下json数据,但是只想保存学生结构体中的字段。
json数据如下:
{
"name": "张三",
"age": 18,
"email": "[email protected]",
"status": "game"
}
学生结构体如下:
type Student struct {
Name string `json:"name"`
Age float64 `json:"age"`
Phone string `json:"phone"`
}
结构体原始的数值为
{
"name": "李四", "age": 12, "phone": "18688886666"}
那么想搞一个函数直接把json中在结构体中存在的字段给更新了,其他的保持不变,以下就记录一下这个方法怎么实现。
首先想到的就是通过反射获取结构体中的json tag,然后在json中查找tag是否存在,如果存在看类型一致就给更新一下。
func UpdateStructByJson(v reflect.Value, js string) {
var data map[string]interface{
}
err := json.Unmarshal([]byte(js), &data)
if err != nil {
return
}
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
tag := field.Tag.Get("json")
if tag == "" {
continue
}
value, ok := data[tag]
if !ok {
continue
}
f := v.Field(i)
if !f.CanSet() {
continue
}
fv := reflect.ValueOf(value)
if fv.Type().AssignableTo(f.Type()) {
f.Set(fv)
}
}
}
其中接收了一个 reflect.Value 的参数,因为传入的类型如果是any在函数内部不类型转换无法拆包,因此入参数是一个反射类型,函数内部就屏蔽了结构类型的影响。
在外部获取一下反射类型的
v := reflect.ValueOf(&d).Elem()
这样基本上就能够完美的实现根据json更新struct的内容了。后端服务就能够不改变数据库的表结构,根据实际需求增删字段,版本迭代效率也会大大提高。
以下是完整测试代码。
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Student struct {
Name string `json:"name"`
Age float64 `json:"age"`
Phone string `json:"phone"`
}
func UpdateStructByJson(v reflect.Value, js string) {
var data map[string]interface{
}
err := json.Unmarshal([]byte(js), &data)
if err != nil {
return
}
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
tag := field.Tag.Get("json")
if tag == "" {
continue
}
value, ok := data[tag]
if !ok {
continue
}
f := v.Field(i)
if !f.CanSet() {
continue
}
fv := reflect.ValueOf(value)
if fv.Type().AssignableTo(f.Type()) {
f.Set(fv)
}
}
}
func main() {
newJsonString := `{"name": "张三", "age": 18, "email": "[email protected]", "status": "game"}`
oldJsonString := `{"name": "李四", "age": 12, "phone": "18688886666"}`
var d Student
json.Unmarshal([]byte(oldJsonString), &d)
v := reflect.ValueOf(&d).Elem()
UpdateStructByJson(v, newJsonString)
// 转换为JSON字符串
jsonBytes, err := json.Marshal(d)
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
}