How to define a generic types that will hold many other diferent types

Issue

Some context to the problem:

We are creating a lambda function in aws with golang and in this function we need to retrieve some information from our database using graphql.

We were able to do so but we are currently facing some issues when parsing the response into a valid type because we wanted to use a single type to hold all possible scenarios.

To better explain this, look into "Examples" and then see the "Goal".

Examples:

We will be receiving something like this:

{
  "data": {
    "getUser": {
      "id": "example1",
      "name": "example_name"
    }
  }
}

and like this (and many more):

{
  "data": {
    "getEvent": {
        "id": "event1",
        "name": "example_name"
      }
    }
  }
}

Goal:

So, what we wanted to create was a type that represented the "data" field and that it could also have multiple representations, like this:

type Response {
  Data <???> `json:"data"`
}

Where the is a type that can be, at the same type, a "getUser" and a "getEvent" (and many more different types):

type GetPerson {
  Id string `json:"id"`
  Name string `json:"name"`
}

type GetEvent {
  Id string `json:"id"`
  Name string `json:"name"`
}

P.S: We are open to different suggestions as to better approach this issue and if needed, we can also add more information.

Solution

You can use a fat interface:

type Data struct {
   Person *GetPerson `json:"user"`
   Event *GetEvent `json:"event"`
   ...
}

Then, unmarshal an instance of Data, see which one of the included elements is not nil, and use that.

You can also implement a polymorphic interface. Below is the sketch of the idea:

type Data struct {
  Item interface{}
}

type dataMarshal struct {
   Item map[string]json.RawMessage
}

// Map JSON keys to factory functions
var factory:=map[string]func()interface{} {
   "user": func() interface{} { return &GetUser{} },
   "event": func() interface{} {return &GetEvent{} },
}

func (d *Data) UnmarshalJSON(in []byte) error {
  var data dataMarshal
  // Unmarshal data with key and raw message
  err:=json.Unmarshal(in,&data)
  for k,v:=range data.Item {
     // Based on the key, create a new object
     obj:=factory[k]()
      // Unmarshal new object
     err:=json.Unmarshal(v,obj)
     d.Item=obj
  }
  return nil
}

Answered By – Burak Serdar

Answer Checked By – David Goodson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.