【问题标题】:Unmarshal json request body to a structure which has a struct member of custom interface type将 json 请求正文解组到具有自定义接口类型的结构成员的结构
【发布时间】:2018-02-06 13:02:32
【问题描述】:

让我们考虑下面的代码

type A struct {
    Column1 string `json:"column1"`
    Entity CustomInterface `json:"entity"`
}

type CustomInterface interface {
    GetType() string
}

type Entity1 struct {
    ColumnX string `json:"columnx"`
    ColumnY string `json:"columny"`
}

type Entity2 struct {
    ColumnP string `json:"columnp"`
    ColumnQ string `json:"columnq"`
}

func (*e Entity1) GetType() string {
    return "ENTITY1"
}

func (*e Entity2) GetType() string {
    return "ENTITY2"
}

现在,如果我尝试绑定A 类型的实例,如下所示

var bodyJSON A
ShouldBindWith(&bodyJson, binding.JSON)

我收到以下错误

json: cannot unmarshal object into Go struct field A.entity of type package.CustomInterface

我在这里做了什么很傻的事情吗?

PS:我刚开始探索围棋。如果这个问题非常菜鸟级别,请道歉。

【问题讨论】:

  • 试试这个var bodyJSON *A
  • 如果你不提供具体类型,包不知道要解组到什么。
  • @JimB 是的,我同意。否则,运行时无法确定要解组到哪种类型。我可以使它工作的方法是将其读取为interface{},然后根据特定条件将其转换为适当的类型。请让我知道是否有更好的方法来实现这一点。

标签: json go encoding interface


【解决方案1】:

json.Unmarshal 函数本身不允许您解组接口类型,除了没有任何方法的空接口 (interface{}):

为了将 JSON 解组为接口值,Unmarshal 将 one of these 存储在接口值中:

  • bool,用于 JSON 布尔值
  • float64,用于 JSON 数字
  • string,用于 JSON 字符串
  • []interface{},用于 JSON 数组
  • map[string]interface{},用于 JSON 对象
  • nil 为 JSON null

但是,在一些简单的情况下,以下方案可以工作。

type CustomerEntity struct {
    CustomerName string `json:"customer_name"`
    Address      string `json:"customer_address"`
}

type EmployeeEntity struct {
    EmployeeName string `json:"employee_name"`
    ID           int    `json:"employee_id"`
}

如果我们知道一个实体是员工或客户,那么我们可以定义一个嵌入每个实体的Entity

type Entity struct {
    CustomerEntity
    EmployeeEntity
}

我们可以给它提供方法来检查它是客户还是员工:

func (s Entity) IsCustomer() bool {
    return s.CustomerEntity != CustomerEntity{}
}
func (s Entity) IsEmployee() bool {
    return s.EmployeeEntity != EmployeeEntity{}
}

真的,这些只是检查是否设置了至少一个字段。

然后我们解组以下 JSON:

{
    "entity": {
        "employee_name": "Bob",
        "employee_id": 77
    }
}

这是一个完整的例子:

import (
    "encoding/json"
    "fmt"
)

type Example struct {
    Entity Entity `json:"entity"`
}

type Entity struct {
    CustomerEntity
    EmployeeEntity
}

func (s Entity) IsCustomer() bool {
    return s.CustomerEntity != CustomerEntity{}
}
func (s Entity) IsEmployee() bool {
    return s.EmployeeEntity != EmployeeEntity{}
}

type CustomerEntity struct {
    CustomerName    string `json:"customer_name"`
    CustomerAddress string `json:"customer_address"`
}

type EmployeeEntity struct {
    EmployeeName string `json:"employee_name"`
    EmployeeID   int    `json:"employee_id"`
}

func main() {
    var example Example
    if err := json.Unmarshal([]byte(`{"entity":{"employee_name":"Bob", "employee_id":77}}`), &example); err != nil {
        panic("won't fail")
    }
    fmt.Printf("%#v\n", example)
    if example.Entity.IsCustomer() {
        fmt.Printf("customer %s lives at %d\n", example.Entity.CustomerName, example.Entity.CustomerAddress)
    }
    if example.Entity.IsEmployee() {
        fmt.Printf("employee %s has id %d\n", example.Entity.EmployeeName, example.Entity.EmployeeID)
    }
}

哪个输出

main.Example{Entity:main.Entity{CustomerEntity:main.CustomerEntity{CustomerName:"", CustomerAddress:""}, EmployeeEntity:main.EmployeeEntity{EmployeeName:"Bob", EmployeeID:77}}}
employee Bob has id 77

正如我们所料。

有一些注意事项。首先,如果实体类型的 JSON 或 Go 字段名称有重叠,这将不起作用。其次,没有什么能阻止您(意外地)初始化客户和员工类型中的某些字段,并导致它为 IsCustomerIsEmployee 返回 true。

如果您的 JSON 数据有一个 "type" 字段,那么您可以使用它来决定保留什么:

type Entity struct {
    Type string `json:"type"`
    CustomerEntity
    EmployeeEntity
}

尽管这与上述其他解决方案具有相同的缺点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多