Reputation: 7092
I'm trying to dynamically create a slice of structs that I can marshal my data into, but doing it at runtime based on some unknown struct type. I'm doing this because I want to have one common bit of code that can unmarshal anything that implements a specific interface.
e.g. (pseudo code)
func unmarshalMyData(myInterface MyInterface, jsonData []byte) {
targetSlice := myInterface.EmptySlice()
json.Unmarshal(jsonData, &targetSlice)
}
I've tried several different alternatives. The one that seems the most promising is using the reflect
package to construct the slice. Unfortunately, after unmarshalling, the dynamically created slice has a type of []interface{}
. If I print out the type of the dynamically created slice before unmarshalling it prints []*main.myObj
. Can anyone explain why? See playground link: https://play.golang.org/p/vvf1leuQeY
Is there some other way to dynamically create a slice of structs that unmarshals correctly?
I'm aware of json.RawMessage
, but there are a few reasons I can't use it... there is no "type" field in my json. Also, the code where I am unmarshalling has no compile time knowledge of the struct that it is unmarshalling. It only knows that the struct implements a specific interface.
Some code that baffles me as to why dynamically created slice doesn't maintain its type after unmarshalling:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func main() {
input := `[{"myField":"one"},{"myField":"two"}]`
myObjSlice := []*myObj{}
fmt.Printf("Type of myObjSlice before unmarshalling %T\n", myObjSlice)
err := json.Unmarshal([]byte(input), &myObjSlice)
if err != nil {
panic(err)
}
fmt.Printf("Type of myObjSlice after unmarshalling %T\n", myObjSlice)
myConstructedObjSlice := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(&myObj{})), 0, 0).Interface()
fmt.Printf("Type of myConstructedObjSlice before unmarshalling %T\n", myConstructedObjSlice)
err = json.Unmarshal([]byte(input), &myConstructedObjSlice)
if err != nil {
panic(err)
}
fmt.Printf("Type of myConstructedObjSlice after unmarshalling %T\n", myConstructedObjSlice)
}
type myObj struct {
myField string
}
The output:
Type of myObjSlice before unmarshalling []*main.myObj
Type of myObjSlice after unmarshalling []*main.myObj
Type of myConstructedObjSlice before unmarshalling []*main.myObj
Type of myConstructedObjSlice after unmarshalling []interface {}
Upvotes: 3
Views: 1120
Reputation: 109404
You're building a []*myObj
using reflect, but you're then passing an *interface{}
to json.Unmarshal
. The json package sees the target of the pointer as the type interface{}
, and therefore uses its default types to unmarshal into. You need to create a pointer to the slice type you create, so the call to the Interface()
method returns the exactly type you want to unmarshal into, which is *[]*myObj
.
sliceType := reflect.SliceOf(reflect.TypeOf(&myObj{}))
slicePtr := reflect.New(sliceType)
err = json.Unmarshal([]byte(input), slicePtr.Interface())
Upvotes: 6