如何使用类型解组递归的 JSON 数据?

huangapple go评论109阅读模式
英文:

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 ands or ors, 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}
   }
}

huangapple
  • 本文由 发表于 2023年2月28日 06:57:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75586352.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定