goviper-go

Problem accessing nested YAML structure in Viper


Recently trying out Viper with my Cobra app to parse my config, but turned out that I couldn't parse the inner nested block. The map for ClustersOuter is always empty, and I am sure the mapstructure is tagged correctly. I would prefer to use Viper Unmarshall feature rather than Getting the data type manually.

Output

map[zones:[{ClustersOuter:map[] Id:1} {ClustersOuter:map[] Id:2}]]

Somehow it seems like I can only retrieve the zone map and its ID, but not any map for the clusters map.

My inputs: config.yml

zones:
  - id: 1
    clusters:
      - cluster_id: 1
        k3s_config_file: "k3s-cluster-1-config.yaml"
      - cluster_id: 2
        k3s_config_file: "k3s-cluster-2-config.yaml"
      - cluster_id: 3
        k3s_config_file: "k3s-cluster-3-config.yaml" 
  - id: 2
    clusters:
      - cluster_id: 1
        k3s_config_file: "k3s-cluster-1-config.yaml"
      - cluster_id: 2
        k3s_config_file: "k3s-cluster-2-config.yaml"
      - cluster_id: 3
        k3s_config_file: "k3s-cluster-3-config.yaml"   

configuration.go

package config

type Zones struct {
    ClustersOuter map[string][]ClustersOuter `mapstructure:"zones"`
    Id            int                        `mapstructure:"id"`
}
type ClustersInner struct {
    ClusterId     int    `mapstructure:"cluster_id"`
    K3sConfigFile string `mapstructure:"k3s_config_file"`
}

type ClustersOuter struct {
    ClustersInner map[string][]ClustersInner `mapstructure:"clusters"`
}

type ConfigSuper map[string][]Zones

main.go

    viper.SetConfigName("config")
    viper.AddConfigPath(".")
    viper.SetConfigType("yaml")

    if err := viper.ReadInConfig(); err != nil {
        log.Fatalf("Error reading config file, %s", err)
        return false
    }

    var configuration config.ConfigSuper
    err := viper.Unmarshal(&configuration)
    if err != nil {
        log.Fatalf("unable to decode into struct, %v", err)
    }

    fmt.Printf("%+v\n", configuration)

Solution

  • Your struct types do not reflect the YAML type, it looks like you've combined maps and slices together when you're only dealing with slices. In YAML, maps look like this:

    myMap:
        key1: value1
    

    and arrays look like this:

    myArray:
        - foo: bar
    

    Unfortunately, your config object is messed up enough that I can't understand the intent so I re-wrote it for you. These types can be decoded by Viper without any issues.

    type Config struct {
        Zones []Zone `mapstructure:"zones"`
    }
    
    type Zone struct {
        ID       int        `mapstructure:"id"`
        Clusters []Cluster `mapstructure:"clusters"`
    }
    
    type Cluster struct {
        ClusterID     int    `mapstructure:"cluster_id"`
        K3sConfigFile string `mapstructure:"k3s_config_file"`
    }
    

    The Config struct is the root object, make sure to unmarshal into that.

    config := Config{}
    err = viper.Unmarshal(&config)