json - 将 json 请求主体解码为具有自定义接口(interface)类型的结构成员的结构

标签 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"
}

现在,如果我尝试按如下方式绑定(bind) A 类型的实例

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

我收到以下错误

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

我是不是在做什么傻事?

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

最佳答案

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

To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:

  • bool, for JSON booleans
  • float64, for JSON numbers
  • string, for JSON strings
  • []interface{}, for JSON arrays
  • map[string]interface{}, for JSON objects
  • 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"`
}

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

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
}

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

关于json - 将 json 请求主体解码为具有自定义接口(interface)类型的结构成员的结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48643718/

相关文章:

mysql - 如何使用 Express 访问 get 请求的结果(对象)?

go - 如何在不尝试更新其他模块的情况下将 'go get' 或 'go mod vendor' 用于特定模块?

types - 是否可以为命名类型/结构定义相等性?

iphone - 使用 JSON-Framework 将 NSMutableArray 作为 JSON 发送

java - Gson库的toJson方法输出Json不正确

java - Go 中 Java 静态属性的等价性

java - 如何在 Netbeans 控制台中打印希伯来语字符

java - Charset.defaultCharset() 在 JDK1.7 和 JDK 1.6 下得到不同的结果

python - 如何打开其中包含表情符号的文本文件?

python - 如何使用 grequest 从多个 url 打印相同的字典对象?