Reputation: 416
Lets consider the following code
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"
}
Now if I am trying to bind an instance of A
type as follows
var bodyJSON A
ShouldBindWith(&bodyJson, binding.JSON)
I am getting the following error
json: cannot unmarshal object into Go struct field A.entity of type package.CustomInterface
Am I doing anything very silly here?
PS: I just started exploring go. Apologies if this question is very noob level.
Upvotes: 0
Views: 2051
Reputation: 56
The json.Unmarshal
function by itself does not let you unmarshal into interface types except for the empty interfaces (interface{}
) that don't have any methods:
To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:
bool
, for JSON booleansfloat64
, for JSON numbersstring
, for JSON strings[]interface{}
, for JSON arraysmap[string]interface{}
, for JSON objectsnil
for JSON null
However, in some simple cases the following scheme can work.
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"`
}
If we know that an entity is either an employee or a customer, then we can define an Entity
that embeds each:
type Entity struct {
CustomerEntity
EmployeeEntity
}
We can give it methods to check whether it's a customer or an employee:
func (s Entity) IsCustomer() bool {
return s.CustomerEntity != CustomerEntity{}
}
func (s Entity) IsEmployee() bool {
return s.EmployeeEntity != EmployeeEntity{}
}
Really, these are just checking that at least one field is set.
Then we unmarshal the following JSON:
{
"entity": {
"employee_name": "Bob",
"employee_id": 77
}
}
Here's a complete example:
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)
}
}
which outputs
main.Example{Entity:main.Entity{CustomerEntity:main.CustomerEntity{CustomerName:"", CustomerAddress:""}, EmployeeEntity:main.EmployeeEntity{EmployeeName:"Bob", EmployeeID:77}}}
employee Bob has id 77
as we might expect.
There are a few caveats. First, this won't work if there's overlap in either the JSON or the Go field names for the entity types. Second, nothing stops you from (accidentally) initializing some fields in both the customer and employee types and causing it to return true for both IsCustomer
and IsEmployee
.
If your JSON data has a "type"
field, then you could use it to decide what is held instead:
type Entity struct {
Type string `json:"type"`
CustomerEntity
EmployeeEntity
}
although this has the same drawbacks as the other solution mentioned above.
Upvotes: 1