英文:
How to unmarshal a recursive json with type
问题
我有一个具有以下形式的JSON:
{
"Filter": {
"Filters": [
{
"Filter": {
"someKey": "someValue1",
},
"FilterType": "trait"
},
{
"Filter": {
"someKey": "someValue2",
},
"FilterType": "trait"
}
]
},
"FilterType": "and" // 这也可以是or
}
每个过滤器都有一个类型,与我在代码中拥有的特定结构相对应。正如你所看到的,这具有递归的性质:一个过滤器可以与多个子过滤器(也可以是and或or等)进行“and”操作。
因此,根据类型,我需要递归地将其映射到正确的结构。有什么建议吗?我研究了使用mapstructure,但即使是这样,代码也变得相当混乱。
请注意,此示例仅使用“and”和“trait”过滤器,但请假设还有其他类型,如“or”。
英文:
I've got a JSON with the following form:
{
"Filter": {
"Filters": [
{
"Filter": {
"someKey": "someValue1",
},
"FilterType": "trait"
},
{
"Filter": {
"someKey": "someValue2",
},
"FilterType": "trait"
}
]
},
"FilterType": "and" // this can be an or too
}
Each filter has a type which maps to a particular struct I've got in code. As you can see this has a recursive nature: a filter can be and
with multiple subfilters (which can also be and
s or or
s, etc).
So, depending on the type, I need to recursively map it to the correct struct. Any recommendations? I looked into using mapstructure but even then the code becomes pretty nasty.
Note, this example only uses and
and trait
filters but assume there are more like or
答案1
得分: 1
在没有明确的特征规范的情况下,这是你需要表示接近你在问题中发布的JSON的结构。
type FilterContainer struct {
Filter map[string]interface{} `json:"Filter,omitempty"`
Filters []FilterContainer `json:"Filters,omitempty"`
FilterType string `json:"FilterType"`
}
你可以在Go中这样初始化你的过滤器:
f := FilterContainer{
Filters: []FilterContainer{
{
Filter: map[string]interface{}{"someKey": "someValue1"},
FilterType: "trait",
},
{
Filter: map[string]interface{}{"someKey": "someValue2"},
FilterType: "trait",
},
},
FilterType: "and",
}
当过滤器被编组为JSON并进行漂亮的打印时,它们看起来像这样:
{
"Filters": [
{
"Filter": {
"someKey": "someValue1"
},
"FilterType": "trait"
},
{
"Filter": {
"someKey": "someValue2"
},
"FilterType": "trait"
}
],
"FilterType": "and"
}
上述JSON与你问题中的JSON并不完全匹配;它缺少顶级的Filter
字段。我认为我在这个答案中提出的结构将简化实现和心理模型。
在Go Playground中尝试一下:https://go.dev/play/p/6cLPT28DG2O
如果你真的非常需要与你问题中的JSON匹配
这是你需要的结构:
type FilterContainer struct {
Filter interface{} `json:"Filter,omitempty"` // 可以是map[string]interface{}或FilterContainer
Filters []FilterContainer `json:"Filters,omitempty"`
FilterType string `json:"FilterType,omitempty"`
}
并且它可以这样填充:
f := FilterContainer{
Filter: FilterContainer{
Filters: []FilterContainer{
{
Filter: map[string]interface{}{"someKey": "someValue1"},
FilterType: "trait",
},
{
Filter: map[string]interface{}{"someKey": "someValue2"},
FilterType: "trait",
},
},
},
FilterType: "and",
}
在Go Playground中试一下:https://go.dev/play/p/uB_LmJc6NGH
英文:
In the absence of clarifying feature specifications this is the struct you need to represent something close to the JSON you posted in your question.
type FilterContainer struct {
Filter map[string]interface{} `json:"Filter,omitempty"`
Filters []FilterContainer `json:"Filters,omitempty"`
FilterType string `json:"FilterType"`
}
Your filters would be initialized in Go like this
f := FilterContainer{
Filters: []FilterContainer{
{
Filter: map[string]interface{}{"someKey": "someValue1"},
FilterType: "trait",
},
{
Filter: map[string]interface{}{"someKey": "someValue2"},
FilterType: "trait",
},
},
FilterType: "and",
}
When the filters get marshaled to JSON and pretty printed they look like this
{
"Filters": [
{
"Filter": {
"someKey": "someValue1"
},
"FilterType": "trait"
},
{
"Filter": {
"someKey": "someValue2"
},
"FilterType": "trait"
}
],
"FilterType": "and"
}
The above JSON doesn't exactly match the JSON in your question; it lacks the top-level Filter
field. I think the struct I propose in this answer will simplify both the implementation as well as the mental model.
Give it a try in the Go Playground at https://go.dev/play/p/6cLPT28DG2O
If you really, really need to match the JSON in your question
here's the struct you'll need
type FilterContainer struct {
Filter interface{} `json:"Filter,omitempty"` // Either a map[string]interface{} or a FilterContainer
Filters []FilterContainer `json:"Filters,omitempty"`
FilterType string `json:"FilterType,omitempty"`
}
and it is populated this way
f := FilterContainer{
Filter: FilterContainer{
Filters: []FilterContainer{
{
Filter: map[string]interface{}{"someKey": "someValue1"},
FilterType: "trait",
},
{
Filter: map[string]interface{}{"someKey": "someValue2"},
FilterType: "trait",
},
},
},
FilterType: "and",
}
Try it out in Go Playground at https://go.dev/play/p/uB_LmJc6NGH
答案2
得分: 1
这种多态的JSON类型很常见,通常不容易处理。我在这里解释的是一种使用单独的结构体进行编组的方法,并依赖后处理来构建过滤器。
你可以定义一个FilterMarshal
结构体,其中包含所有可能的过滤器字段:
type FilterMarshal struct {
FilterType string `json:"FilterType"`
Filters []*FilterMarshal `json:"Filters"`
Filter *FilterMarshal `json:"Filter"`
SomeKey string `json:"someKey"`
...
}
这个实现假设没有名称冲突。例如,如果一个过滤器类型的某个字段是Filter
类型,而另一个类型的某个字段是不兼容的类型,那么这种方法将无法工作,你必须使用其他方法。
当你在FilterMarshal
中解组JSON时,你将得到一个与输入JSON匹配的对象树。然后,你可以构建实际的过滤器结构:
func unmarshalFilter(input *FilterMarshal) Filter {
switch input.FilterType {
case "and":
// 递归处理input.Filters
return AndFilter{Filters: processFilters(input.Filters)}
case "trait":
return TraitFilter{SomeKey: input.SomeKey}
}
}
英文:
This type of polymorphic JSON is common, and usually not easy to deal with. What I explain here is one way of doing it using a separate struct that is only used for marshaling, and relies on post-processing to construct the filters.
You can define a FilterMarshal
struct that contains all possible fields of a filter:
type FilterMarshal struct {
FilterType string `json:"FilterType"`
Filters []*FilterMarshal `json:"Filters"`
Filter *FilterMarshal `json:"Filter"`
SomeKey string `json:"someKey"`
...
}
This implementation assumes that there are no name clashes. For instance, if a filter type has Filter
field as one type, and another type has Filter
field as an incompatible type, this will not work and you have to use other methods.
When you unmarshal the JSON in a FilterMarshal
, you will get an object tree matching the input JSON. Then, you can construct the actual filter structure:
func unmarshalFilter(input *FilterMarshal) Filter {
switch input.FilterType {
case "and":
// process input.Filters recursively
return AndFilter{Filters: processFilters(input.Filters))
case "trait":
return TraitFilter{SomeKey: input.SomeKey}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论