4

See this playground: http://play.golang.org/p/dWku6SPqj5

Basically, the library I'm working on receives an interface{} as a parameter and then needs to json.Unmarshal that from a byte array. Under the covers, the interface{} parameter is a struct that matches the json structure of the byte array but the library doesn't have a reference to that struct (but it does have a reference to the corresponding reflect.Type through).

Why can't the json package detect the underlying type? For some reason it's giving back a simple map instead of the actual struct.

Here's the code:

package main

import "fmt"
import "encoding/json"
import "reflect"

func main() {
    good()
    bad()
}

func good() {
    var ping Ping = Ping{}
    deserialize([]byte(`{"id":42}`), &ping)
    fmt.Println("DONE:", ping.ID)
}

func bad() {
    var ping interface{} = Ping{}
    deserialize([]byte(`{"id":42}`), &ping)
    fmt.Println("DONE:", ping) // It's a simple map now, not a Ping. Why?
}

func deserialize(stuff []byte, thing interface{}) {
    value := reflect.ValueOf(thing)
    fmt.Printf("%+v | %v\n", value, value.Kind())

    err := json.Unmarshal(stuff, thing)
    if err != nil {
        panic(err)
    }
}

type Ping struct {
    ID int `json:"id"`
}
Michael Whatcott
  • 5,603
  • 6
  • 36
  • 50

1 Answers1

4

You've passed to json a pointer to an abstract interface. You should simply pass a pointer to Ping as an abstract interface:

func bad() {
    var ping interface{} = &Ping{} // <<<< this
    deserialize([]byte(`{"id":42}`), ping) // << and this
    fmt.Println("DONE:", ping) // It's a simple map now, not a Ping. Why?
}

But if as you said you don't have a pointer to cast ton an interface{}, you can use reflect to create a new pointer, deserialize into it, and copy the value back:

func bad() {
    var ping interface{} = Ping{}
    nptr := reflect.New(reflect.TypeOf(ping))
    deserialize([]byte(`{"id":42}`), nptr.Interface())
    ping = nptr.Interface()
    fmt.Println("DONE:", ping) // It's a simple map now, not a Ping. Why?
}
Not_a_Golfer
  • 47,012
  • 14
  • 126
  • 92
  • That would work, but I can't do that. The library has no knowledge of the underlying types used by the larger application. The 'ping' is received as an `interface{}` and that's that. Any ideas? – Michael Whatcott Jan 30 '14 at 23:05
  • You can perhaps use `reflect` to tell the underlying type of the `interface{}` and create a new `interface{}` representing a pointer to it. – Not_a_Golfer Jan 30 '14 at 23:10
  • I'd love to see that--care to modify the playground example and post your modifications? – Michael Whatcott Jan 30 '14 at 23:33
  • 1
    well, if you don't mind creating a new instance of ping (I'm not sure what's the assumptions here), it's pretty simple. But I have a hunch it won't cut it: http://play.golang.org/p/KAnA-uCb6b – Not_a_Golfer Jan 30 '14 at 23:57
  • I would suggest using `reflect` as well. It basically allows you to deal with generic data types without generics. I'd even say `reflect` is the answer to this question. – Brenden Jan 31 '14 at 00:30
  • 1
    @Not_a_Golfer - that playground might just do the job becuase the library may actually have access to the reflect.TypeOf(Ping) so it would be trivial for us to call reflect.New(...). Thanks! I'll edit my question to convey that the library doesn't have a reference to the struct itself but it may have a reflect.Type. If you'll edit your answer to include the reflect.New stuff I'll accept it. – Michael Whatcott Jan 31 '14 at 03:28