json.Unmarshal into an interface value

1,024 views
Skip to first unread message

madevi...@gmail.com

unread,
May 31, 2016, 1:26:13 AM5/31/16
to golang-nuts
This is not possible:

    type Entity interface { ... }

    func GetEntity() Entity {
      return Apple{}
    }
    // ...
    v := GetEntity()
    err := json.Unmarshal(b, &v)

It fails with "json: cannot unmarshal object into Go value of type Entity". Apparently json.Unmarshal() needs a value that is *Apple.

If GetEntity() returns &Apple{}, the example works. It seems the unmarshaler doesn't understand interfaces containing non-pointer values.

Is there a solution? Can you use reflection to turn v into the right pointer, without knowing exactly what it contains?

To make things more concrete, here's the use case that led me to this point; maybe there's a better way to do this:

I want to unmarshal polymorphic JSON that cannot be mapped to a single type. For example, we may have messages such as this:

    {"apple":{"color": "green"}}

or

    {"boat":{"type":"sailboat"}}

These should unmarshal into Apple{Color: "green"} and Sailboat{Type:"sailboat"}, respectively.

However, the central logic that performs unmarshaling does not know about the concrete types. In this case, it's a message bus that doesn't know anything messages other than the fact that messages it gets can be marshaled into various formats. A big "switch" to check the key is not possible. (Imagine this is a library and none of the structs aren't even declared in this package.) The entire API is:

    Send(Entity)
    Receive() Entity

The naive solution is to have a central registry of names:

    var registry = map[string]reflect.Type{}

    func RegisterType(name string, t reflect.Type) {
      registry[name] = t
    }

    func UnmarshalJSON(msg []byte) Entity {
      var raw map[string]*json.RawMessage
      if err := json.Unmarshal(msg, &raw); err != nil {
        return err
      }
      if len(raw) != 1 { // error }
      for key, value := range raw {
        if t, ok := registry[key]; ok {
          v := reflect.New(t).Interface()
          if err := json.Unmarshal(*value, &v); err != nil {
            // ...
          }
          return v
        }
      }
      return nil
    }

And then callers do:

    RegisterType("apple", reflect.TypeOf(&Apple{}))
    RegisterType("sailboat", reflect.TypeOf(&Sailboat{}))

This works, but:

(1) Requires the use of pointer structs. It always returns *Apple, etc.

(2) It relies on reflection, which is not super fast.

(3) Every struct becomes heap-allocated.

My hope was to avoid those three problems altogether. My entity values are tiny and immutable, and are best passed around as copies.

My other idea was to use a map of concrete types:

    var registry = map[string]Entity

    func Register(name string, e Entity) {
      registry[name] = e
    }

...and then rely on copying. This is where it falls down, since json.Unmarshal() does not want to deserialize into non-pointers.

I could, of course, ask every Entity implementation to implement unmarshaling:

    type Entity interface {
      json.Unmarshaler
    }

And then simply call `UnmarshalJSON()` myself. But every single implementation of it would look like this:

    func (apple *Apple) UnmarshalJSON(b []byte) error {
      return json.Unmarshal(b, apple)
    }

This seems a bit stupid.

Dan Kortschak

unread,
May 31, 2016, 1:41:35 AM5/31/16
to madevi...@gmail.com, golang-nuts
On Mon, 2016-05-30 at 18:16 -0700, madevi...@gmail.com wrote:
> If GetEntity() returns &Apple{}, the example works. It seems the
> unmarshaler doesn't understand interfaces containing non-pointer
> values.

It's not that it doesn't understand, it's that it knows that working on
a non-pointer value is fruitless since the result of the work will be
thrown away.

> Is there a solution? Can you use reflection to turn v into the right
> pointer, without knowing exactly what it contains?

https://play.golang.org/p/lIrb5vUPSK

and if you want to preserve the value in the pointed-to-value

https://play.golang.org/p/xI4E5yDdYT


Henrik Johansson

unread,
May 31, 2016, 1:41:45 AM5/31/16
to madevi...@gmail.com, golang-nuts
You can marshal into a map[string]interface{} perhaps and have each type implement a "Populate" method. Maybe.

At least you put the responsibility onto the implementors of the type to understand its own data.

Not sure but maybe it can work.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages