Golang中如何将不同对象的JSON数组绑定到实体

Golang中如何将不同对象的JSON数组绑定到实体 大家好! 我有TypeScript背景,现在正尝试转向Go。不幸的是,在没有泛型和继承的情况下进行数据建模让我有些困扰。我正在尝试编写一个处理以下请求的API:

GET /forms/myform

{
    "title": "Hello",
    "fields": [
        {
           "type": "textField",
            "name": "ip",
            "value": "192.168.1.2",
            "label": "My IP",
            "errorMessage": ""
        },
        {
           "type": "range",
            "name": "interval",
            "value": 1000,
            "label": "SomeInterval",
            "errorMessage": ""
        }
    ]
}

多亏了空接口,我成功为GET请求编写了一个处理器。

我还需要处理PATCH请求。请求体将包含一个键值对对象,如下所示:

{
   "ip": "192.168.1.60",
   "interval": 4020
}

预期的结果应该是更新后的表单:

{
    "title": "Hello",
    "fields": [
        {
           "type": "textField",
            "name": "ip",
            "value": "192.168.1.60",
            "label": "My IP",
            "errorMessage": ""
        },
        {
           "type": "range",
            "name": "interval",
            "value": 4020,
            "label": "SomeInterval",
            "errorMessage": ""
        }
    ]
}

如果用户发送了无效请求,例如:

{
   "ip": "192.168.1.3abc",
   "interval": 50000
}

预期的响应应该是:

{
    "title": "Hello",
    "fields": [
        {
           "type": "textField",
            "name": "ip",
            "value": "192.168.1.3abc",
            "label": "My IP",
            "errorMessage": "字段 ip 必须是有效的 IPv4 地址"
        },
        {
           "type": "range",
            "name": "interval",
            "value": 60000,
            "label": "SomeInterval",
            "errorMessage": "间隔不允许大于 5000"
        }
    ]
}

我想你应该明白了。我的主要问题是如何在Go中对此进行建模?如何将输入结构体绑定到包含字段的切片或数组?或者我应该绑定到一个RequestStruct,然后将其映射到我的字段切片?我尝试了不同的方法,例如将字段存储为带有索引字段的结构体,并最终将它们编组为JSON数组,以及摆弄反射等。我所有的尝试似乎都不对或感觉不自然。不幸的是,我在网上找不到示例,希望更有经验的人能为我指明正确的方向。

谢谢


更多关于Golang中如何将不同对象的JSON数组绑定到实体的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

我首先会尝试使用现有的验证工具,这里有一个我常用的:https://github.com/go-playground/validator

如果你找不到你需要的,你可能需要使用结构体标签来编写你自己的验证器。结构体标签和反射可以帮助你。

更多关于Golang中如何将不同对象的JSON数组绑定到实体的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这段代码展示了如何解码输入的JSON并编码响应。这里是在Go Playground上的链接,你可以运行它来查看输出。

package main

import (
	"encoding/json"
	"fmt"
)

type Req struct {
	IP       string `json:"ip"`
	Interval int    `json:"interval"`
}

type RspField struct {
	Type         string      `json:"type"`
	Name         string      `json:"name"`
	Value        interface{} `json:"value"`
	Label        string      `json:"label"`
	ErrorMessage string      `json:"errorMessage"`
}

type Rsp struct {
	Title  string     `json:"title"`
	Fields []RspField `json:"fields"`
}

func main() {
	// decoding jsonReq into the Req structure
	jsonReq := []byte(`{ "ip": "192.168.1.3abc", "interval": 50000 }`)
	var req Req
	if err := json.Unmarshal(jsonReq, &req); err != nil {
		panic(err)
	}
	fmt.Println("req:", req)

	// encoding the response in json
	rsp := Rsp{
		Title: "Hello",
		Fields: []RspField{
			{
				Type:         "textField",
				Name:         "ip",
				Value:        "192.168.1.3abc",
				Label:        "My IP",
				ErrorMessage: "The field ip must be a valid IPv4",
			},
			{
				Type:         "range",
				Name:         "interval",
				Value:        60000,
				Label:        "SomeInterval",
				ErrorMessage: "The interval is not allowed to be greater than 5000",
			},
		},
	}
	jsonRsp, err := json.Marshal(&rsp)
	if err != nil {
		panic(err)
	}
	fmt.Println("rsp:", string(jsonRsp))
}

在Go中处理这种多态JSON数组的常用方法是使用自定义的UnmarshalJSON方法。这里提供一个完整的解决方案:

package main

import (
	"encoding/json"
	"fmt"
)

// 基础字段接口
type Field interface {
	GetName() string
	GetValue() interface{}
	SetValue(interface{}) error
	Validate() string
}

// 基础字段结构体
type BaseField struct {
	Type         string `json:"type"`
	Name         string `json:"name"`
	Label        string `json:"label"`
	ErrorMessage string `json:"errorMessage"`
}

func (b *BaseField) GetName() string {
	return b.Name
}

// TextField 实现
type TextField struct {
	BaseField
	Value string `json:"value"`
}

func (f *TextField) GetValue() interface{} {
	return f.Value
}

func (f *TextField) SetValue(v interface{}) error {
	if str, ok := v.(string); ok {
		f.Value = str
		return nil
	}
	return fmt.Errorf("invalid type for TextField")
}

func (f *TextField) Validate() string {
	// 这里添加IP地址验证逻辑
	if f.Name == "ip" {
		// 简化的IP验证示例
		if len(f.Value) < 7 {
			return "字段 ip 必须是有效的 IPv4 地址"
		}
	}
	return ""
}

// RangeField 实现
type RangeField struct {
	BaseField
	Value int `json:"value"`
}

func (f *RangeField) GetValue() interface{} {
	return f.Value
}

func (f *RangeField) SetValue(v interface{}) error {
	if num, ok := v.(float64); ok {
		f.Value = int(num)
		return nil
	}
	return fmt.Errorf("invalid type for RangeField")
}

func (f *RangeField) Validate() string {
	if f.Name == "interval" && f.Value > 5000 {
		return "间隔不允许大于 5000"
	}
	return ""
}

// Form 结构体
type Form struct {
	Title  string  `json:"title"`
	Fields []Field `json:"fields"`
}

// 自定义UnmarshalJSON方法
func (f *Form) UnmarshalJSON(data []byte) error {
	type Alias Form
	aux := &struct {
		Fields []json.RawMessage `json:"fields"`
		*Alias
	}{
		Alias: (*Alias)(f),
	}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	for _, raw := range aux.Fields {
		var base BaseField
		if err := json.Unmarshal(raw, &base); err != nil {
			return err
		}

		switch base.Type {
		case "textField":
			var field TextField
			if err := json.Unmarshal(raw, &field); err != nil {
				return err
			}
			f.Fields = append(f.Fields, &field)
		case "range":
			var field RangeField
			if err := json.Unmarshal(raw, &field); err != nil {
				return err
			}
			f.Fields = append(f.Fields, &field)
		}
	}

	return nil
}

// 处理PATCH请求
type PatchRequest map[string]interface{}

func (f *Form) ApplyPatch(patch PatchRequest) {
	for _, field := range f.Fields {
		if value, exists := patch[field.GetName()]; exists {
			field.SetValue(value)
			if errMsg := field.Validate(); errMsg != "" {
				// 设置错误信息到基础字段
				if baseField, ok := field.(interface{ SetErrorMessage(string) }); ok {
					baseField.SetErrorMessage(errMsg)
				}
			}
		}
	}
}

// 为BaseField添加设置错误信息的方法
func (b *BaseField) SetErrorMessage(msg string) {
	b.ErrorMessage = msg
}

// 使用示例
func main() {
	// 模拟GET请求的JSON数据
	jsonData := `{
		"title": "Hello",
		"fields": [
			{
				"type": "textField",
				"name": "ip",
				"value": "192.168.1.2",
				"label": "My IP",
				"errorMessage": ""
			},
			{
				"type": "range",
				"name": "interval",
				"value": 1000,
				"label": "SomeInterval",
				"errorMessage": ""
			}
		]
	}`

	var form Form
	if err := json.Unmarshal([]byte(jsonData), &form); err != nil {
		fmt.Printf("Error unmarshaling: %v\n", err)
		return
	}

	// 模拟PATCH请求
	patch := PatchRequest{
		"ip":       "192.168.1.3abc",
		"interval": 60000.0,
	}

	form.ApplyPatch(patch)

	// 输出更新后的表单
	updatedJSON, _ := json.MarshalIndent(form, "", "  ")
	fmt.Println(string(updatedJSON))
}

对于更简洁的实现,可以使用结构体标签和反射:

package main

import (
	"encoding/json"
	"reflect"
)

// 使用结构体标签定义字段类型
type FieldConfig struct {
	Type  string `json:"type"`
	Name  string `json:"name"`
	Label string `json:"label"`
}

// 动态字段结构体
type DynamicField struct {
	FieldConfig
	Value        interface{} `json:"value"`
	ErrorMessage string      `json:"errorMessage"`
}

// 表单结构体
type DynamicForm struct {
	Title  string         `json:"title"`
	Fields []DynamicField `json:"fields"`
}

// 验证器接口
type Validator interface {
	Validate(value interface{}) (bool, string)
}

// 注册验证器
var validators = map[string]Validator{
	"ip":       &IPValidator{},
	"interval": &IntervalValidator{},
}

type IPValidator struct{}

func (v *IPValidator) Validate(value interface{}) (bool, string) {
	// 实现IP验证逻辑
	return true, ""
}

type IntervalValidator struct{}

func (v *IntervalValidator) Validate(value interface{}) (bool, string) {
	// 实现间隔验证逻辑
	return true, ""
}

// 处理PATCH请求
func (f *DynamicForm) ApplyDynamicPatch(patch map[string]interface{}) {
	for i, field := range f.Fields {
		if value, exists := patch[field.Name]; exists {
			// 类型转换
			fieldType := reflect.TypeOf(field.Value)
			patchValue := reflect.ValueOf(value)
			
			if patchValue.CanConvert(fieldType) {
				convertedValue := patchValue.Convert(fieldType).Interface()
				f.Fields[i].Value = convertedValue
				
				// 验证
				if validator, exists := validators[field.Name]; exists {
					if valid, errMsg := validator.Validate(convertedValue); !valid {
						f.Fields[i].ErrorMessage = errMsg
					}
				}
			}
		}
	}
}

对于生产环境,建议使用第三方库如mapstructure

package main

import (
	"encoding/json"
	"github.com/mitchellh/mapstructure"
)

type FormWithMapstructure struct {
	Title  string                   `json:"title"`
	Fields []map[string]interface{} `json:"fields"`
}

func (f *FormWithMapstructure) UpdateFromPatch(patch map[string]interface{}) error {
	for i, field := range f.Fields {
		name, ok := field["name"].(string)
		if !ok {
			continue
		}
		
		if value, exists := patch[name]; exists {
			// 使用mapstructure进行类型转换
			var result interface{}
			config := &mapstructure.DecoderConfig{
				Result: &result,
				TagName: "json",
			}
			
			decoder, _ := mapstructure.NewDecoder(config)
			decoder.Decode(value)
			
			f.Fields[i]["value"] = result
		}
	}
	return nil
}

这些方案提供了从简单到复杂的多种实现方式,可以根据具体需求选择合适的方案。第一种方案提供了完整的类型安全和接口设计,第二种方案更灵活,第三种方案利用了现有库简化开发。

回到顶部