在包含接口列表的结构体上进行UnmarshalJSON操作

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

UnmarshalJSON on struct containing interface list

问题

我想要解析包含接口的结构体的JSON,如下所示:

type Filterer interface {
    Filter(s string) error
}

type FieldFilter struct {
    Key string
    Val string
}    

func (ff *FieldFilter) Filter(s string) error {
    // Do something
}

type Test struct {
    Name    string
    Filters []Filterer
}

我的想法是发送以下JSON:

{
    "Name": "testing",
    "Filters": [
        {
            "FieldFilter": {
                "Key": "key",
                "Val": "val"
            }
        }
    ]
}

然而,当将此JSON发送给解析器时,会返回以下异常:json: cannot unmarshal object into Go struct field Test.Filters of type Filterer

我完全理解问题,但不知道如何明智地解决这个问题。希望能得到在Go中解决这个问题的惯用方法的建议。

英文:

I would like to UnmarshalJSON a struct containing an interface as follows:

type Filterer interface {
    Filter(s string) error
}

type FieldFilter struct {
    Key string
    Val string
}    

func (ff *FieldFilter) Filter(s string) error {
    // Do something
}

type Test struct {
    Name string
    Filters []Filterer
}

My idea was to send a json like so:

{
	"Name": "testing",
	"Filters": [
		{
			"FieldFilter": {
				"Key": "key",
				"Val": "val"
			}
		}
	]
}

However, when sending this json to the unmarshaler, the following exception returns: json: cannot unmarshal object into Go struct field Test.Filters of type Filterer

I understand the problem fully, but do not know how to approach this problem wisely. Looking for advice on an idiomatic way to solving this problem in go.

答案1

得分: 4

根据你的问题,我研究了如何为接口列表实现UnmarshalJSON。最终,我在博客上发布了一篇关于如何正确实现的文章。基本上有两种主要的解决方案:

  1. 将所需的JSON字符串解析为map[string]*json.RawMessage,然后从那里开始处理。
  2. 为接口列表创建一个别名,并为该别名实现UnmarshalJSON。然而,你仍然需要使用map[string]*json.RawMessage并进行一些手动操作。没有什么是不付出代价的!

我强烈建议采用第二种方法。虽然这两种解决方案可能会导致相同数量的代码行,但利用类型别名并减少对json.RawMessage类型的依赖将使代码更易于管理,特别是当需要支持UnmarshalJSON实现上的多个接口时。

直接回答问题,首先为接口列表创建一个类型别名:

type Filterers []Filterer

然后继续实现JSON的解码:

func (f *Filterers) UnmarshalJSON(b []byte) error {
    var FilterFields map[string]*json.RawMessage
    if err := json.Unmarshal(b, &FilterFields); err != nil {
        return err
    }
    for LFKey, LFValue := range FilterFields {
        if LFKey == "FieldFilter" {
            var MyFieldFilters []*json.RawMessage
            if err := json.Unmarshal(*LFValue, &MyFieldFilters); err != nil {
                return err
            }
            for _, MyFieldFilter := range MyFieldFilters {
                var filter FieldFilter
                if err := json.Unmarshal(*MyFieldFilter, &filter); err != nil {
                    return err
                }
                *f = append(*f, &filter)
            }
        }
    }
    return nil
}

关于第二种方法的详细解释(包括一些示例和完整的工作代码片段)可以在我的博客上找到。

英文:

Following my own question, I researched how one could implement UnmarshalJSON for interface lists. Ultimately this led me to publish a blog post on how to do this properly. Basically there are 2 main solutions:

  1. Parse the required JSON string into a map[string]*json.RawMessage and work your way from there.
  2. Make an alias for the interface list and implement UnmarshalJSON for that alias. However, you'll still need to work with map[string]*json.RawMessage and some manual work. Nothing comes without a price!

I highly suggest taking the seconds approach. While these two solutions may result in the same amount of code lines, taking advantage of type aliasing and being less dependent on json.RawMessage types will make a more easy to manage code, especially when it is required to support multiple interfaces on the UnmarshalJSON implementation

To directly answer the question, start with making a type alias for the interface list:

type Filterers []Filterer

Now continue with implementing the decoding of the JSON:

func (f *Filterers) UnmarshalJSON(b []byte) error {
    var FilterFields map[string]*json.RawMessage
    if err := json.Unmarshal(b, &FilterFields); err != nil {
        return err
    }
    for LFKey, LFValue := range FilterFields {
        if LFKey == "FieldFilter" {
            var MyFieldFilters []*json.RawMessage
            if err := json.Unmarshal(*LFValue, &MyFieldFilters); err != nil {
                return err
            }
            for _, MyFieldFilter := range MyFieldFilters {
                var filter FieldFilter
                if err := json.Unmarshal(*MyFieldFilter, &filter); err != nil {
                    return err
                }
                *f = append(*f, &filter)
            }
        }
    }
    return nil
}

A detailed explanation (with some examples and a full working code snippets) of the second approach is available on my own blog

答案2

得分: 2

Unmarshal无法知道应该使用哪种类型。唯一的情况是,如果被要求将其解组为interface{},它可以随便“编造一些东西”,在这种情况下,它将使用文档中的规则。由于这些类型都无法放入[]Filterer中,因此无法解组该字段。如果您想解组为struct类型,必须指定该字段为该类型。

您始终可以解组为中间的struct或map类型,然后自行将其转换为所需的任何类型。

英文:

There is no way for Unmarshal to know what type it should use. The only case where it can just "make something up" is if it's asked to unmarshal into an interface{}, in which case it will use the rules in the documentation. Since none of those types can be put into a []Filterer, it cannot unmarshal that field. If you want to unmarshal into a struct type, you must specify the field to be of that type.

You can always unmarshal into an intermediate struct or map type, and then do your own conversion from that into whatever types you want.

huangapple
  • 本文由 发表于 2017年6月29日 21:23:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/44826376.html
匿名

发表评论

匿名网友

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

确定