Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unmarshal GO YAML to either a Map or a String

Tags:

yaml

go

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

like image 880
Naatan Avatar asked Feb 01 '18 18:02

Naatan


2 Answers

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\n", i)
        case map[interface{}]interface{}:
            log.Printf("i is a map %+v\n", i)
        }

}
like image 143
Benjamin Kadish Avatar answered Nov 15 '22 04:11

Benjamin Kadish


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\n", i)
        case map[string]interface{}:
            log.Printf("i is a map.")
            for k,v := range i {
                log.Printf("%s=%v\n",k,v)
            }
        default:
            log.Printf("Type i=%s", i)
        }
    }
}


like image 38
rfay Avatar answered Nov 15 '22 05:11

rfay