英文:
Why embedded structs are not decoded when there is an Unmarshal implementation?
问题
我有一个类型为ServiceAccount
的结构体,它嵌套了另外两个类型(User和Policy)。然而,由于Policy
类型的Unmarshaler实现,似乎完全忽略了User
类型的字段。
这种行为有什么合理的原因吗?对我来说,这看起来像是一个bug,因为json包可以通过反射看到我们嵌套了两个类型,而不仅仅是Policy
类型。
我知道我可以通过在ServiceAccount
类型上实现Unmarshaler接口来“修复”这个问题。
package main
import (
"encoding/json"
"fmt"
)
type ServiceAccount struct {
User
Policy
}
type User struct {
UserID string `json:"userID"`
}
type Policy struct {
Scopes string `json:"scopes,omitempty"`
}
// PolicyRaw is the Policy type as received from client.
type PolicyRaw struct {
Scopes string `json:"scopes,omitempty"`
}
func main() {
s := `{"userID":"xyz", "scopes":"some scopes"}`
srvAcc := &ServiceAccount{}
if err := json.Unmarshal([]byte(s), srvAcc); err != nil {
panic(err)
}
fmt.Printf("srvAcc %v", *srvAcc)
}
func (p *Policy) UnmarshalJSON(b []byte) error {
pr := new(PolicyRaw)
if err := json.Unmarshal(b, pr); err != nil {
return err
}
p.Scopes = pr.Scopes
return nil
}
英文:
I have a type ServiceAccount
which embeds two other types (User and Policy). However it seems the fields of the User
type are totally ignored due the Unmarshaler implementation of the Policy
type.
Is there any good reason for this behaviour? It looks like a bug to me because the json package can see through reflection that we have two types embedded and not only the type Policy
.
I'm aware that I can "fix" the issue by implementing the Unmarshaler interface on type ServiceAccount too.
package main
import (
"encoding/json"
"fmt"
)
type ServiceAccount struct {
User
Policy
}
type User struct {
UserID string `json:"userID"`
}
type Policy struct {
Scopes string `json:"scopes,omitempty"`
}
// PolicyRaw is the Policy type as received from client.
type PolicyRaw struct {
Scopes string `json:"scopes,omitempty"`
}
func main() {
s := `{"userID":"xyz", "scopes":"some scopes"}`
srvAcc := &ServiceAccount{}
if err := json.Unmarshal([]byte(s), srvAcc); err != nil {
panic(err)
}
fmt.Printf("srvAcc %v", *srvAcc)
}
func (p *Policy) UnmarshalJSON(b []byte) error {
pr := new(PolicyRaw)
if err := json.Unmarshal(b, pr); err != nil {
return err
}
p.Scopes = pr.Scopes
return nil
}
答案1
得分: 3
我不认为这是一个 bug,而只是接口和嵌入的工作方式。这只是恰好不符合你想要/期望的情况。
json.Unmarshal
通过这一行来确定使用 UnmarshalJSON
方法:
if u, ok := v.Interface().(Unmarshaler); ok
正如你所知,如果某个东西具有正确的方法集,它就实现了一个接口,而 *Policy
和 *ServiceAccount
就是这样。所以预期的是,外部类型的 JSON 解码将只调用适当的方法并认为完成了。
有趣的是,如果你尝试添加一个虚拟方法,例如:
func (u *User) UnmarshalJSON([]byte) error {return errors.New("not impl")}
那么尽管 *User
和 *Policy
现在都实现了该接口,*ServiceAccount
将不再实现该接口。原因很明显,如果你尝试显式调用 srvAcc.UnmarshalJSON
,编译器将报错“ambiguous selector srvAcc.UnmarshalJSON”。如果没有这样的调用,代码是合法的,并且该方法只是从方法集中排除。
所以我认为解决方案有以下几种:
- 如果你想要编组结果,请不要嵌入实现了
json.Unmarshaller
的内容,而是使用一个命名字段。 - 当进行这种嵌入时,确保你自己明确实现了外部类型的
json.Unmarshaller
(例如,在*ServiceAccount
中添加一个UnmarshalJSON
方法)。 - 确保至少有两个嵌入的内容实现了
json.Unmarshaller
,然后它们将分别工作,但“外部”类型将获得默认行为。
最后一种选项对我来说似乎是一种 hack。(顺便说一下,可以通过以下方式故意实现这种效果:
type ServiceAccount struct {
User
Policy
dummyMarshaller
}
type dummyMarshaller struct{}
func (dummyMarshaller) MarshalJSON([]byte) error {panic("ouch")}
但我觉得这看起来非常 hacky)。
另请参阅:
¹ 进一步测试显示,对于这种匿名的(即嵌入字段),它们的 UnmarshalJSON
方法不会被调用。
英文:
I don't think it's a bug but just just how interfaces and embedding works. It just happens not to be what you want/expect here.
json.Unmarshal
figures out to use the UnmarshalJSON
method via this line:
if u, ok := v.Interface().(Unmarshaler); ok
As you know, something implements an interface if it has the right method set which *Policy
and *ServiceAccount
do. So it's expected that JSON decoding of the the outer type would just call the appropriate method and think it's done.
Interestingly, if you were to experiment and add a dummy method such as:
func (u *User) UnmarshalJSON([]byte) error {return errors.New("not impl")}
then although *User
and *Policy
would now both implement the interface,
*ServiceAccount
will no longer implement that interface. The reason is clear if you try and explicitly call srvAcc.UnmarshalJSON
which would then give a compiler error of "ambiguous selector srvAcc.UnmarshalJSON". Without such a call the code is legal and the method is just excluded from the method set.
So I think the solution is one of:
- Just don't embed things that implement
json.Unmarshaller
if you want to marshal the result, e.g. use a named field instead. - Make sure you explicitly implement
json.Unmarshaller
for the outer type yourself when doing such embedding (e.g. add anUnmarshalJSON
method to*ServiceAccount
). - Make sure at least two embedded things implement
json.Unmarshaller
and then <strike>they'll work individually</strike>¹ but the "outer" type will get the default behaviour.
The last option seems like a hack to me. (And btw could be done on purpose with something like:
type ServiceAccount struct {
User
Policy
dummyMarshaller
}
type dummyMarshaller struct{}
func (dummyMarshaller) MarshalJSON([]byte) error {panic("ouch")}
but that looks really hacky to me).
See also:
¹ Further testing shows that decoding such anonymous (i.e. embedded fields), that their UnmarshalJSON
methods do not get called.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论