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
我首先会尝试使用现有的验证工具,这里有一个我常用的: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
}
这些方案提供了从简单到复杂的多种实现方式,可以根据具体需求选择合适的方案。第一种方案提供了完整的类型安全和接口设计,第二种方案更灵活,第三种方案利用了现有库简化开发。

