Parsing array of string or single string
ldesplat opened this issue ยท 12 comments
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...
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.
Any update on this? It should be very good to have this feature.
+1
Building off of dolfelt's code, here's how you could workaround this if you're using v3 of this package:
type StringArray []string
func (a *StringArray) UnmarshalYAML(value *yaml.Node) error {
var multi []string
err := value.Decode(&multi)
if err != nil {
var single string
err := value.Decode(&single)
if err != nil {
return err
}
*a = []string{single}
} else {
*a = multi
}
return nil
}
type Data struct {
Field StringArray
}
I ran into this issue recently and have added a patch in a fork here. I'll work to get a full patch submitted as a PR on this repo.
PR added #974. Please let me know if there's anything missing and/or necessary. Happy to update where necessary.
Can we move that ticket to some FAQ ?
Right now it looks like an open issue in the ticket list, but actually it's an answered common question.