英文:
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。最终,我在博客上发布了一篇关于如何正确实现的文章。基本上有两种主要的解决方案:
- 将所需的JSON字符串解析为
map[string]*json.RawMessage
,然后从那里开始处理。 - 为接口列表创建一个别名,并为该别名实现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:
- Parse the required JSON string into a
map[string]*json.RawMessage
and work your way from there. - 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论