为什么在Unmarshal实现时不解码嵌入式结构?

时间:2015-03-11 00:20:24

标签: go

我有一个ServiceAccount类型,它嵌入了另外两种类型(用户和策略)。但是,由于User类型的Unmarshaler实现,似乎完全忽略了Policy类型的字段。   这种行为有什么好的理由吗?这看起来像是一个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
}

Execute

1 个答案:

答案 0 :(得分:2)

我不认为这是一个错误,只是接口和嵌入的工作方式。它恰好不是你想要/期望的。

json.Unmarshal计划通过this line使用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,然后将编译器错误“模糊选择器srvAcc.UnmarshalJSON ”,原因很清楚。没有这样的调用,代码是合法的,方法只是从方法集中排除。

所以我认为解决方案之一是:

  • 如果要整理结果,请不要嵌入实现json.Unmarshaller的内容,例如:改为使用命名字段。
  • 确保在进行此类嵌入时自行为外部类型明确实施json.Unmarshaller(例如,向UnmarshalJSON添加*ServiceAccount方法)。
  • 确保至少有两个嵌入式内容实现json.Unmarshaller,然后它们将单独工作¹但“外部”类型将获得默认行为。

最后一个选项对我来说似乎是一个黑客攻击。 (顺便说一句,可以通过以下方式完成:

    type ServiceAccount struct {
        User
        Policy
        dummyMarshaller
    }

    type dummyMarshaller struct{}
    func (dummyMarshaller) MarshalJSON([]byte) error {panic("ouch")}

但看起来真的 hacky对我来说。)

另见:

¹Further testing表明解码这样的匿名(即嵌入字段),他们的UnmarshalJSON方法不会被调用。