基于类型密钥解组动态JSON [英] Unmarshal dynamic JSON based on a type key
问题描述
我正在寻找不涉及引入其他通用"字段(例如Value
,Data
等)的解决方案,该字段将用作变体字段的占位符.
I'm looking for a solution which DOESN'T involve introducing an additional "generic" field like Value
, Data
, etc. which would be a placeholder for the variant field.
我有一个JSON规范,它描述了几个大型结构,这些结构大多数都包含简单的值,但有时却是一个结构本身的值,其动态类型取决于某个字段的值.
I have a JSON spec which describes several large structs, which hold mostly simple values, but occasionally a value which is a struct itself, with a dynamic type depending on the value of a certain field.
例如,这两个JSON文档都应解组为同一Go结构:
For example, both these JSON documents should unmarshal to the same Go struct:
{
"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结构,我无法更改.
The JSON structure is set, I can't change it.
Go结构必须如下所示:
The Go struct must look like this:
type BigStruct struct {
SomeData string `json:"some_data"`
DynamicField Something `json:"dynamic_field"`
OtherData string `json:"other_data"`
}
问题是如何实际执行操作以及Something
类型应该是什么.
The question is how to actually do it and what that Something
type should be.
我首先将其设置为界面:
I've started by making it an interface:
type Something interface {
GetType() string
}
并且有几个结构和函数可以配合使用:
And have several structs and funcs to go with it:
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
的实例时,我可以这样做:
The reason is, that when I get an instance of BigStruct
, I can do this:
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
.
However, then I got stuck - how could this arrangement work with UnmarshalJSON
? I think that BigStruct
should implement UnmarshalJSON
which would somehow inspect the Type
field of the dynamic_field
and then based on it, make DynamicField
either a DynamicTypeA
or DynamicTypeB
.
但是如何?一种可能由于递归而不起作用的方法是:
But how? One way which probably doesn't work because of recursion would be:
- 将
DynamicField
标记为json:"-"
- BigStruct的实现
UnmarshalJSON
- 将JSON解组到
BigStruct
的UnmarshalJSON
中的map[string]interface{}
中, - 检查地图中的
dynamic_field
值,手动构造DynamicTypeA
或DynamicTypeB
- 再次将相同数据解组到
BigStruct
- 使用手动创建的值修复
DynamicField
- Mark
DynamicField
asjson:"-"
- Implement
UnmarshalJSON
for BigStruct - unmarshal the JSON into a
map[string]interface{}
in theBigStruct
'sUnmarshalJSON
, - inspect the
dynamic_field
value in the map, manually construct eitherDynamicTypeA
orDynamicTypeB
- unmarshal the same data again into
BigStruct
- fixup the
DynamicField
with the manually created values
...但是在第五步中,当我尝试将数据解组到BigStruct
时将导致无限递归,该BigStruct
将调用当前正在执行的同一UnmarshalJSON
函数.
... but that will lead to infinite recursion in the 5th step when I try to unmarshal the data into a BigStruct
which would call the same UnmarshalJSON
function which is currently executing.
推荐答案
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"`
}
https://play.golang.com/p/oKMKQTdzp7s
如果您不想或不能更改DynamicField的类型,可以将UnmarshalJSON方法放在BigStruct上,并声明一个临时类型以避免递归.
If you don't want to, or can't, change the type of the DynamicField you can put the UnmarshalJSON method on the BigStruct and declare a temporary type to avoid recursion.
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))
}
https://play.golang.com/p/at5Okp3VU2u
这篇关于基于类型密钥解组动态JSON的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!