我正在寻找一种解决方案,它不需要引入额外的“通用”字段,如
我有一个JSON规范,描述了几个大的结构体,其中大部分是简单值,但偶尔会有一个值本身就是一个结构体,其动态类型取决于某个字段的值。
例如,这两个JSON文档都应该解码为同一个Go结构体:
问题是如何实际做到这一点以及那个
我首先将其制作为一个接口:
原因在于,当我得到一个
然而,我陷入了困境 - 这种安排如何与
但是如何做到呢?一种可能不起作用的方式是:
- 将
Value
、Data
等,这将成为变体字段的占位符。我有一个JSON规范,描述了几个大的结构体,其中大部分是简单值,但偶尔会有一个值本身就是一个结构体,其动态类型取决于某个字段的值。
例如,这两个JSON文档都应该解码为同一个Go结构体:
{
"some_data": "foo",
"dynamic_field": { "type": "A", "name": "Johnny" },
"other_data": "bar"
}
并且
{
"some_data": "foo",
"dynamic_field": { "type": "B", "address": "Somewhere" },
"other_data": "bar"
}
JSON结构已经确定,我无法更改。
Go结构必须像这样:
type BigStruct struct {
SomeData string `json:"some_data"`
DynamicField Something `json:"dynamic_field"`
OtherData string `json:"other_data"`
}
问题是如何实际做到这一点以及那个
Something
类型应该是什么。我首先将其制作为一个接口:
type Something interface {
GetType() string
}
并且还有多个结构体和函数与之配合使用:
type BaseDynamicType struct {
Type string `json:"type"`
}
type DynamicTypeA struct {
BaseDynamicType
Name string `json:"name"`
}
type DynamicTypeB struct {
BaseDynamicType
Address string `json:"address"`
}
func (d *BaseDynamicType) GetType() string {
return d.Type
}
原因在于,当我得到一个
BigStruct
实例时,我可以这样做:switch big.DynamicField.GetType() {
case "A": // do something with big.DynamicField cast to DynamicTypeA
case "B": // do something with big.DynamicField cast to DynamicTypeB
}
然而,我陷入了困境 - 这种安排如何与
UnmarshalJSON
一起使用?我认为BigStruct
应该实现UnmarshalJSON
,它将在某种程度上检查dynamic_field
的Type
字段,然后根据此字段,使DynamicField
成为DynamicTypeA
或DynamicTypeB
。但是如何做到呢?一种可能不起作用的方式是:
- 将
DynamicField
标记为json:"-"
- 为BigStruct实现UnmarshalJSON
- 在BigStruct
的UnmarshalJSON
中将JSON解组成map[string]interface{}
- 检查地图中的dynamic_field
值,手动构造DynamicTypeA
或DynamicTypeB
- 再次将相同数据解组到BigStruct
中
- 使用手动创建的值修复DynamicField
但是,在第5步尝试将数据解组成BigStruct
时,这将导致无限递归,因为它会调用当前正在执行的相同的UnmarshalJSON
函数。
json.Unmarshal
调用了该方法。文档中说道:*"要将 JSON 反序列化为实现 Unmarshaler 接口的值,Unmarshal会调用该值的 UnmarshalJSON 方法,即使输入是 JSON null 也是如此。"* - mkoprivaDynamicTypeA
,而_那个_是DynamicTypeB
,例如:https://play.golang.com/p/7ck36wnmPiz - fullStackChris