Search code examples
goviper-go

why the "switch t := policy.Parameter.(type)" does not work with viper.Unmarshal()


I want to unmarshal several types from JSON and use the interface to represent the actual struct that it is different. But when I send the struct as interface{} it converts it to a map. The animal.json is:

"{"type":"cat","policies":[{"name":"kitty","parameter":{"duration":600,"percent":90}}]}"
package main

import (
    "reflect"

    log "github.com/sirupsen/logrus"
    "github.com/spf13/viper"
)

func main() {
    var err error
    animal := New()
    viper.SetConfigType("json")
    viper.SetConfigName("animal")
    viper.AddConfigPath("~/Desktop/")
    viper.AddConfigPath(".")
    if err := viper.ReadInConfig(); err != nil {
        return
    }

    if err = viper.Unmarshal(&animal); err != nil {
        return
    }
    for _, policy := range animal.Policies {
        log.Info(policy.Name)
        log.Info(policy.Parameter)
        //INFO[0000] map[duration:600 percent:90]
        log.Info(reflect.TypeOf(policy.Parameter))
        //INFO[0000] map[string]interface {}, Why is it not an interface{} and how do I get it?
        switch t := policy.Parameter.(type) {
        //why does the switch not work?
        case *CatParameter:
            log.Info("cat", t)
        case *DogParameter:
            log.Info("dog", t)
        }
    }
}

func New() *Animal {
    var animal Animal
    animal.Type = "cat"
    return &animal
}

type Animal struct {
    Type     string   `json:"type" form:"type"`
    Policies []Policy `json:"policies" form:"policies"`
}

type CatParameter struct {
    Duration int `json:"duration"`
    Percent  int `json:"percent"`
}

type DogParameter struct {
    Percent   int    `json:"percent"`
    Duration  int    `json:"duration"`
    Operation string `json:"operation"`
}

type Policy struct {
    Name      string      `json:"name"`
    Parameter interface{} `json:"parameter"`
}



Solution

  • It's json unmarshal feature

    If you use an interface{} as a decoder, the default json object for interface{} is map[string]interface{}

    You can see it here:

    https://godoc.org/encoding/json#Unmarshal

    bool, for JSON booleans
    float64, for JSON numbers
    string, for JSON strings
    []interface{}, for JSON arrays
    map[string]interface{}, for JSON objects
    nil for JSON null
    

    So in t := policy.Parameter.(type), the t is map[string]interface{}

    For solving your problem, you can try to define another field to distinguish CatParameter or DogParameter

    Maybe:

    type Policy struct {
        Name      string      `json:"name"`
        Parameter Parameter   `json:"parameter"`
    }
    
    type Parameter struct {
        Name      string `json:"name"`   // cat or dog
        Percent   int    `json:"percent,omitempty"`
        Duration  int    `json:"duration,omitempty"`
        Operation string `json:"operation,omitempty"`
    }