FAQ
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.

--
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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Search Discussions

  • Dan Kortschak at May 31, 2016 at 5:41 am

    On Mon, 2016-05-30 at 18:16 -0700, madevilgenius@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


    --
    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+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Henrik Johansson at May 31, 2016 at 5:41 am
    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.
    On Tue, May 31, 2016, 07:26 wrote:

    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.

    --
    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+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
    --
    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+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupgolang-nuts @
categoriesgo
postedMay 31, '16 at 5:26a
activeMay 31, '16 at 5:41a
posts3
users3
websitegolang.org

People

Translate

site design / logo © 2021 Grokbase