解析具有深层结构和数组值的yaml

im trying to parse the following yaml and I got error, the yaml is valid according https://codebeautify.org/yaml-validator/cb4510d0 I try to use interface and still I dont able to parse the file. what I need is:

  1. print the values like runner1 runner2 and runner3
  2. cmd values

any idea why interface is not working in this case?

the error is:

2019/03/19 22:42:11 Error : yaml: unmarshal errors:
  line 6: cannot unmarshal !!seq into map[string]interface {}
  line 22: cannot unmarshal !!seq into map[string]interface {}

this is what I tried

package main

import (
    "fmt"
    "log"

    "gopkg.in/yaml.v2"
)

var runContent = []byte(`
version: "1.1"

run-parameters:
  before:
    - runner: runner1
      options: 
        cmd:
         - value1
         - value2
      supported: k8s

    - runner: runner2
      options: 
        cmd:
         - value3
         - value4
      supported: aws


  after:
    - runner: runner3
      options: 
        cmd:
         - value6
         - value7
      supported: aws


`)

type FTD struct {
    Version     string     `yaml:"version,omitempty"`
    BuildParams *RunParams `yaml:"run-parameters,omitempty"`
}

type RunParams struct {
    BeforeExec map[string]interface{} `yaml:"before,omitempty"`
    AfterExec  map[string]interface{} `yaml:"after,omitempty"`
}

func main() {
    runners := &FTD{}
    // parse mta yaml
    err := yaml.Unmarshal(runContent, runners)
    if err != nil {
        log.Fatalf("Error : %v", err)
    }
    // for _, v := range runners.BuildParams.BeforeExec {
    //  fmt.Printf("%#v
", v)
    // }

    // fmt.Println(runners.BuildParams.BeforeExec["run-parameters"])

    // if _, ok := runners.BuildParams.BeforeExec["run-parameters"]; ok {
    //  run := runners.BuildParams.BeforeExec["run-parameters"].(map[interface{}]interface{})["run"]
    //  fmt.Println("run: ", run)
    // }

    runParams, ok := runners.BuildParams.BeforeExec["run-parameters"]

    if !ok {
        // handle lack of "run-parameters" in BeforeExec
    }

    runParamsMap, ok := runParams.(map[interface{}]interface{})

    if !ok {
        // handle "run-parameters" not being a map
    }

    run, ok := runParamsMap["run"]

    if !ok {
        // handle lack of "run" inside "run-parameters"
    }

    runStr, ok := run.(string)

    if !ok {
        // handle "run" not being a string
    }

    fmt.Println("run: ", runStr)

}

I'm not sure marshaling into a type an interface{} will work. Marshalers rely on reflection to know where to put the data, so they need a concrete type value to know how to store each field/item.

If you have a complex YAML config, you can paste it into an online YAML-to-go generator to yield a compatible type definition:

type AutoGenerated struct {
    Version       string `yaml:"version"`
    RunParameters struct {
        Before []struct {
            Runner  string `yaml:"runner"`
            Options struct {
                Cmd []string `yaml:"cmd"`
            } `yaml:"options"`
            Supported string `yaml:"supported"`
        } `yaml:"before"`
        After []struct {
            Runner  string `yaml:"runner"`
            Options struct {
                Cmd []string `yaml:"cmd"`
            } `yaml:"options"`
            Supported string `yaml:"supported"`
        } `yaml:"after"`
    } `yaml:"run-parameters"`
}

It's recommended to pull out the sub-types and define them explicitly - for reuse.

Finally, if a field is optional, then change it to a pointer - the caveat being you will have to check for nil when accessing any sub-fields to avoid runtime panics.

You need to change

type RunParams struct {
    BeforeExec map[string]interface{} `yaml:"before,omitempty"`
    AfterExec  map[string]interface{} `yaml:"after,omitempty"`
}

to unmarshall to array of map

type RunParams struct {
    BeforeExec []map[string]interface{} `yaml:"before,omitempty"`
    AfterExec  []map[string]interface{} `yaml:"after,omitempty"`
}

And there would be changes that you need to make to your code for supporting array of map.

try this:

type RunParams struct {
    BeforeExec []Runners `yaml:"before,omitempty"`
    AfterExec  []Runners `yaml:"after,omitempty"`
}

type Runners struct {
    Runner string `yaml:"runner,omitempty"`
    Options map[string]interface{} `yaml:"options,omitempty"`
    Supported string `yaml:"supported,omitempty"`
}