我在寻找一种不涉及引入一个额外的“通用”字段的溶液等Value,Data等这将是该变种领域的占位符。
我有一个 JSON 规范,它描述了几个大型结构,其中大部分包含简单的值,但偶尔也会包含一个结构本身的值,其动态类型取决于某个字段的值。
例如,这两个 JSON 文档都应该解组为同一个 Go 结构:
{
"some_data": "foo",
"dynamic_field": { "type": "A", "name": "Johnny" },
"other_data": "bar"
}
Run Code Online (Sandbox Code Playgroud)
和
{
"some_data": "foo",
"dynamic_field": { "type": "B", "address": "Somewhere" },
"other_data": "bar"
}
Run Code Online (Sandbox Code Playgroud)
JSON 结构已设置,我无法更改它。
Go 结构体必须如下所示:
type BigStruct struct {
SomeData string `json:"some_data"`
DynamicField Something `json:"dynamic_field"`
OtherData string `json:"other_data"`
}
Run Code Online (Sandbox Code Playgroud)
问题是如何真正做到这一点以及那种Something类型应该是什么。
我已经开始使它成为一个界面:
type Something interface {
GetType() string
}
Run Code Online (Sandbox Code Playgroud)
并有几个结构体和函数与之配合:
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
}
Run Code Online (Sandbox Code Playgroud)
原因是,当我获得 的实例时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
}
Run Code Online (Sandbox Code Playgroud)
但是,然后我被卡住了 - 这种安排如何适用UnmarshalJSON?我认为BigStruct应该实现UnmarshalJSON它会以某种方式检查 的Type领域,dynamic_field然后基于它,制作DynamicFieldaDynamicTypeA或DynamicTypeB.
但是如何?由于递归而可能不起作用的一种方法是:
DynamicField为json:"-"UnmarshalJSON为 BigStruct实施map[string]interface{}在BigStruct的UnmarshalJSON,dynamic_field地图中的值,手动构建DynamicTypeA或DynamicTypeBBigStruct DynamicField使用手动创建的值修复...但是当我尝试将数据解组为BigStruct将调用UnmarshalJSON当前正在执行的相同函数时,这将导致第 5 步中的无限递归。
type BigStruct struct {
SomeData string `json:"some_data"`
DynamicField DynamicType `json:"dynamic_field"`
OtherData string `json:"other_data"`
}
type DynamicType struct {
Value interface{}
}
func (d *DynamicType) UnmarshalJSON(data []byte) error {
var typ struct {
Type string `json:"type"`
}
if err := json.Unmarshal(data, &typ); err != nil {
return err
}
switch typ.Type {
case "A":
d.Value = new(TypeA)
case "B":
d.Value = new(TypeB)
}
return json.Unmarshal(data, d.Value)
}
type TypeA struct {
Name string `json:"name"`
}
type TypeB struct {
Address string `json:"address"`
}
Run Code Online (Sandbox Code Playgroud)
https://play.golang.com/p/oKMKQTdzp7s
如果您不想或不能更改 DynamicField 的类型,您可以将 UnmarshalJSON 方法放在 BigStruct 上并声明一个临时类型以避免递归。
func (b *BigStruct) UnmarshalJSON(data []byte) error {
var typ struct {
DF struct {
Type string `json:"type"`
} `json:"dynamic_field"`
}
if err := json.Unmarshal(data, &typ); err != nil {
return err
}
switch typ.DF.Type {
case "A":
b.DynamicField = new(DynamicTypeA)
case "B":
b.DynamicField = new(DynamicTypeB)
}
type tmp BigStruct // avoids infinite recursion
return json.Unmarshal(data, (*tmp)(b))
}
Run Code Online (Sandbox Code Playgroud)
https://play.golang.com/p/at5Okp3VU2u