'Idiomatic way to deserialise JSON to type based on string in Go
I'm using Go v1.17.3
I'm pretty new with Go and coming from an OOP background I'm very aware I'm not in the Gopher mindset yet! So I've split the question in 2 sections, the first is the problem I'm trying to solve, the second is what I've done so far. That way if I've approached the solution in a really strange way from what idiomatic Go should look like the problem should still be clear.
1. Problem I'm trying to solve:
Deserialise a JSON request to a struct where the struct name is specified in one of the fields on the request.
Example code
Request:
{
"id": "1",
"name": "example-document",
"dtos": [
{
"type": "DTOA",
"attributes": {
"name": "Geoff"
}
},
{
"type": "DTOB",
"attributes": {
"length": "24cm"
}
}
]
}
And I want to end up with a collection of interface types.
2. What I've done so far
I've got a package called dto which models the behviours each DTO is capable of.
package dto
type DTO interface {
Deserialize(attributes json.RawMessage) error
ToEntity() (*entity.Entity, error)
}
type RawDTO struct {
Type string `json:"type"`
Attributes json.RawMessage
}
type DTOA {
Name string `json:"name"`
}
func (dto *DTOA) Deserialize(attributes json.RawMessage) error {
// Unmarshall json to address of t
}
func (dto *DTOA) ToEntity() (*entity.Entity, error) {
// Handle creation of EntityA
}
type DTOB {
Length string `json:"length"`
}
func (dto *DTOB) Deserialize(attributes json.RawMessage) error {
// Unmarshall json to address of t
}
func (dto *DTOB) ToEntity() (*entity.Entity, error) {
// Handle creation of EntityB
}
For context, Entity is an interface in another package.
I've created a type registry by following the answers suggested from this StackOverflow question
This looks like:
package dto
var typeRegistry = make(map[string]reflect.Type)
func registerType(typedNil interface{}) {
t := reflect.TypeOf(typedNil).Elem()
typeRegistry[t.PkgPath()+"."+t.Name()] = t
}
func LoadTypes() {
registerType((*DTOA)(nil))
registerType((*DTOB)(nil))
}
func MakeInstance(name string) (DTO, error) {
if _, ok := typeRegistry[name]; ok {
return reflect.New(typeRegistry[name]).Elem().Addr().Interface().(DTO), nil
}
return nil, fmt.Errorf("[%s] is not a registered type", name)
}
When I bring this all together:
package commands
type CreateCommand struct {
ID string `json:"id"`
Name string `json:"name"`
DTOs []dto.RawDTO `json:"dtos"`
}
func CreateCommandHandler(w http.ResponseWriter, r *http.Request) {
var cmd CreateCommand
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
err = json.Unmarshal(bodyBytes, &cmd)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var entities []*entity.Entity
for _, v := range cmd.DTOs {
// I have a zero instance of a type that implements the DTO interface
dto, err := dto.MakeInstance("path_to_package." + v.Type)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Each registered type implements Deserialize
err = dto.Deserialize(v.Attributes)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Each registered type implements ToEntity
e, err := dto.ToEntity()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
entities = append(entities, e)
}
w.WriteHeader(http.StatusOK)
}
The issue
When I execute this code and send a request, I get the following error:
http: panic serving 127.0.0.1:34020: interface conversion: *dto.DTOA is not dto.DTO: missing method ToEntity goroutine 18 [running]:
I can't figure out why this is happening. The Deserialize method works fine.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
