将json请求体解组为具有自定义接口类型的struct成员的结构

时间:2018-02-06 13:02:33

标签: json go encoding interface

让我们考虑以下代码

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:我刚开始探索去吧。如果这个问题非常高,那就道歉。

1 个答案:

答案 0 :(得分:0)

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

  

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

     
      
  • bool,适用于JSON布尔值
  •   
  • float64,代表JSON号码
  •   
  • string,用于JSON字符串
  •   
  • []interface{},适用于JSON数组
  •   
  • map[string]interface{},用于JSON对象
  •   
  • nil for 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
}

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