将GO YAML解组到地图或字符串

I'm trying to unmarshal YAML entries that can be either a string or a list of key: value strings (a map as per Go). I cannot figure out how to get this done sadly. I know I can write my own unmarshaller but that seems to only work with structs.

I have the first part working:

package main

import (
    "log"

    "gopkg.in/yaml.v2"
)

type Data struct {
    Entry []Entry `yaml:"entries"`
}

type Entry map[string]string

var dat string = `
entries: 
  - keya1: val1
    keya2: val2
  - keyb1: val1
    keyb2: val2
  - val3`

func main() {
    out := Data{}
    if err := yaml.Unmarshal([]byte(dat), &out); err != nil {
        log.Fatal(err)
    }

    log.Printf("%+v", out)
}

But the - val3 entry causes an error now, obviously. How can I get it to recognise both lists and single string entries?

Thank you

This has been answered in various ways before, but long story short it is easy unmarshall into an interface and then deal with both cases

type Entry interface{}

for _, entry := range out.Entry {
        switch i := entry.(type) {
        case string:
            log.Printf("i is a string %+v
", i)
        case map[interface{}]interface{}:
            log.Printf("i is a map %+v
", i)
        }

}

As, go is static type language, you can't leave val3 only a list item if you convert it to defined struct. It should be key value pair. E.g. (val3: "") (if you want it empty.)

Here is modified code

package main

import (
    "log"

    "gopkg.in/yaml.v2"
)

type Data struct {
    Entry []Entry `yaml:"entries"`
}

type Entry map[string]string

var dat string = `
entries: 
  - keya1: val1
    keya2: val2
  - keyb1: val1
    keyb2: val2
  - val3: ""`

func main() {
    out := Data{}
    if err := yaml.Unmarshal([]byte(dat), &out); err != nil {
        log.Fatal(err)
    }

    log.Printf("%+v", out)
}

And output:

2018/02/02 01:07:36 {Entry:[map[keya1:val1 keya2:val2] map[keyb2:val2 keyb1:val1] map[val3:]]}

This is just a followup to the excellent @Benjamin Kadish answer above, but here's a somewhat more complete version and this uses yaml.v3, which makes it just a bit more obvious. Note that the type of the unmarshalled items is map[string]interface{} instead of map[interface{}]interface{} in yaml v3.


package main

import (
    "gopkg.in/yaml.v3"
    "log"
)

type Data struct {
    Entry []Entry `yaml:"entries"`
}

type Entry interface {}

var dat string = `
entries: 
  - keya1: val1
    keya2: val2
  - keyb1: val1
    keyb2: val2
  - val3`

func main() {
    out := Data{}
    if err := yaml.Unmarshal([]byte(dat), &out); err != nil {
        log.Fatal(err)
    }

    for _, entry := range out.Entry {

        switch i := entry.(type) {
        case string:
            log.Printf("i is a string: %+v
", i)
        case map[string]interface{}:
            log.Printf("i is a map.")
            for k,v := range i {
                log.Printf("%s=%v
",k,v)
            }
        default:
            log.Printf("Type i=%s", i)
        }
    }
}