Yaml: Parsing array of string or single string

Created on 3 May 2015  路  6Comments  路  Source: go-yaml/yaml

I need to parse some files and some fields can either include an array of strings or can just include 1 element that's inlined. How do I parse such a file?

field: value

or

field:
  - value1
  - value2

I tried field []string but that of course only works for the bottom example and field string only works for the top one. I thought maybe field []string ",inline" but that's limited to structs. Any idea on how to achieve it? One, could think of the inlined string as an array of 1 length...

Most helpful comment

You closed the issue, but I still think this is an issue. It would be great if one could do something like:

type Data struct {
   Field []string `yaml:"alwaysarray"`
}

And no matter if someone writes:

field: foobar

That would put the value as the only element of the array.

All 6 comments

I have the same problem. Did you find a solution meanwhile?

I managed to get something working using the Unmarshall interface

package main

import (
    "fmt"
    "gopkg.in/yaml.v2"
)

const (
    data = `
attrs:
  foo: bar
  bar:
   - 1
   - 2
   - 3
`
)

type SingleOrMulti struct {
    Values []string
}

func (sm *SingleOrMulti) UnmarshalYAML(unmarshal func(interface{}) error) error {
    var multi []string
    err := unmarshal(&multi)
    if err != nil {
        var single string
        err := unmarshal(&single)
        if err != nil {
            return err
        }
        sm.Values = make([]string, 1)
        sm.Values[0] = single
    } else {
        sm.Values = multi
    }
    return nil
}

type Data struct {
    Attrs map[string]SingleOrMulti
}

func main() {
    var t Data
    yaml.Unmarshal([]byte(data), &t)
    fmt.Printf("%d\n", len(t.Attrs))
    for k, e := range t.Attrs {
        fmt.Printf("%v: %v\n", k, e.Values)
    }
}

Using this I always have a slice in the unmarshaled struct, which gets a single element in case the YAML was a single value.

Note that in this example, attrs: had arbitrary key names so I used a map. You can as well use a struct, if the names are fixed:

type Data struct {
    Foo SingleOrMulti
    Bar SingleOrMulti
}

@dmacvicar I had left this as a todo for later on so thank you very much for finding a solution. You have just taught me how to use this library even more effectively. I did not realize there was an Unmarshaler type! I can now parse some other more complex values right from the beginning. Very awesome.

You closed the issue, but I still think this is an issue. It would be great if one could do something like:

type Data struct {
   Field []string `yaml:"alwaysarray"`
}

And no matter if someone writes:

field: foobar

That would put the value as the only element of the array.

@dmacvicar A slightly modified version of the code above gives you something similar to what you're asking for.

type StringArray []string

func (a *StringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
    var multi []string
    err := unmarshal(&multi)
    if err != nil {
        var single string
        err := unmarshal(&single)
        if err != nil {
            return err
        }
        *a = []string{single}
    } else {
        *a = multi
    }
    return nil
}

type Data struct {
   Field StringArray
}

Now you can access Data.Field as a string array no matter what.

What's the status? It would be awesome to land this feature.

Was this page helpful?
0 / 5 - 0 ratings