Home > Mobile >  How to unmarshal a slice of interfaces from JSON in Golang
How to unmarshal a slice of interfaces from JSON in Golang

Time:06-04

I have a variables package in which I have an interface Variable and two structs which implement the methods on the interface, NumericalVariable and TextualVariable, like this...

package variables

type Variable interface {
    GetColumnIndex() int
    IsActive() bool
    StartDataLoad()
    AddData(value string) (bool, error)
    FinishDataLoad()
    VectorLength() int
    SetVectorOffset(offset int)
    LoadIntoVector(sv string, vector []float64) float64
}

type NumericVariable struct {
    Column_idx    int
    Name          string
    Vector_offset int
    Mean          float64
    Sum           float64
    SumSq         float64
    SD            float64
    Count         float64
    Max           float64
    Min           float64
    Vector_max    float64
    Vector_min    float64
    values        []float64
    Active        bool
}

type Category struct {
    Count         int
    Vector_offset int
    Coefficient   float64
}

type TextualVariable struct {
    Column_idx    int
    Name          string
    Vector_offset int
    Categories    map[string]Category
    Categories_k  float64
    Active        bool
    Count         int
}

I have another module model which defines a Model type which includes a slice of Variable interfaces, like this...

package model

type Model struct {
    Vars []variables.Variable
}

In my code elsewhere I am loading and processing a stream/file of data and I create either NumericalVariable or TextualVariable instances depending on the data I am presented with. These get added to the Vars slice on an instance of Model

I want to be able to read and write the Model struct to a JSON file.

Writing is dead easy, I take advantage of the marshaling in Golang's json package (so long as I am happy with Capitalized variable names)

func (_self Model) Write(filename string) {
    file, err := json.MarshalIndent(_self, "", " ")
    if err != nil {
        log.Fatal(err)
        return
    }
    err = ioutil.WriteFile(filename, file, 0644)
    if err != nil {
        log.Fatal(err)
        return
    }
}

However reading from JSON is proving trickier. The issue is that model.Vars is a slice of interfaces. The marshaler handles that just fine, but when reading back in I don't have the information about the type that was written.

I thought I could get to this by reflection, and I am nearly there, but I am stuck.

My reader looks like this...

func (_self Model) Read(filename string) {
    _model, err := ioutil.ReadFile(filename)
    if err != nil {
        log.Fatal(err)
        return
    }

    var dat map[string][]interface{}
    err = json.Unmarshal([]byte(_model), &dat)
    if err != nil {
        log.Fatal(err)
        return
    }
    _vars := dat["Vars"]
    for _, _vi := range _vars {
        k := reflect.ValueOf(_vi)
        if k.Kind() == reflect.Map {
            if len(k.MapKeys()) == 13 {
                // I know this is a numeric variable
                nv := variables.NumericVariable()
                // How do I get the reflect kind to load up nv
            }
            if len(k.MapKeys()) == 8 {
                // I know this is a textual variable
                tv := variables.TextualVariable()
                // How do I get the reflect kind to load up tv
            }
        }
    }

I can reliably (if a bit kludgily) detect when I have each type in the reflection, but how do I then get it to load the values into a struct. Ideally I want an automatic unmarshal into the struct variables of the same names. I don't want to have to do it field by field (although I may have to resort to that), but how do I even do that?

CodePudding user response:

directly convert your map[string]interface{} to struct by asserting type to value of map


var nv NumericVariable 

for key,value:= range vars {
   nv.Column_idx = vars[key].(int) // interface{} will be converted to int
   nv.Name = vars[key].(string) // interface{} will be converted to string
   ..
   ..
   }


Asserting type with convert interface values of map to your given type in struct.

CodePudding user response:

Take a look at the playground link provided in this answer. They are proposing a solution to a similar problem. You could perhaps use this idea to assert the type and unmarshal directly into the type you need after that.

  • Related