Ryan Walls
Ryan Walls

Reputation: 7092

Unmarshal into array of structs determined at runtime in Go

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

Answers (1)

Mr_Pink
Mr_Pink

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

Related Questions