Unmarshal GO YAML to either a Map or a String

11,215

Solution 1

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)
        }

}

Solution 2

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)
        }
    }
}


Share:
11,215
Naatan
Author by

Naatan

Updated on June 09, 2022

Comments

  • Naatan
    Naatan about 2 years

    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

  • Naatan
    Naatan over 6 years
    This just isn't true, it just means the code parsing the yaml needs to be typed properly. The input format should not affect this beyond making it a bit more challenging to parse.
  • Naatan
    Naatan over 6 years
    Much simpler than what I was thinking (I was writing an unmarshaller). Thank you!
  • Abu Hanifa
    Abu Hanifa over 6 years
    Yes, I thought, you want to convert to your struct. So, if struct is okay, then needs to change yaml :)