0

XY problem: I'm trying to read in a YAML file such as the one below, and output a set of tuples that combine certain keys and values from the YAML file. E.g. given this YAML data:

--- fruit: apple: colour: - green 'banana': colour: - yellow pear: colour: - green - yellow 

I want to combine each key under "fruit" with each value under "colour" into tuples. My tuples would look like this:

apple:green banana:yellow pear:green pear:yellow 

To do this, I'm using a map[string]interface{} to Unmarshal my YAML data into Go - I can't use a struct because the names of the keys below "fruit" could be anything, so I need to use a dynamic type. This is my code so far:

package main import ( "fmt" "log" "gopkg.in/yaml.v3" ) var data string = ` --- fruit: apple: colour: - green 'banana': colour: - yellow pear: colour: - green - yellow ` func main() { m := make(map[string]interface{}) err := yaml.Unmarshal([]byte(data), &m) if err != nil { log.Fatal("Failed to parse YAML file") } for _, v := range m { fruits := v.(map[string]interface{}) for fruit, v2 := range fruits { colours := v2.(map[string]interface{})["colour"] for colour := range colours { fmt.Println("%v:%v\n", fruit, colour) } } } } 

Playground link: https://go.dev/play/p/v8iuzmMLtjX

The problem is for colour := range colours - I get the error:

cannot range over colours (type interface {}) 

I found this answer to a similar question which says that I cannot directly convert a []interface{} to []string, and must instead iterate over the values. That's what I've tried to do here. The v2 variable works out to a map[string]interface {} type, which for example could be map[colour:[green yellow]]. Then I've tried converting that into another map[string]interface{} to get the value of "colour", which works out to a []interface{} type [green yellow] and is stored in the colours variable.

But I can't iterate over colours for some reason. I don't understand what's different about my solution and icza's solution in the linked answer (I've linked their Playground link). The data type of colours in my solution is []interface{}, and in icza's solution the data type of t is also []interface{} - but in that case it is possible to iterate through the slice and access the values within.

Another solution I tried was from this answer to a different question, which was to try directly converting the []interface{} to a []string:

c := colours.([]string) 

That also didn't work:

panic: interface conversion: interface {} is []interface {}, not []string 

What do I need to do to make this solution work?

4
  • You can be much more specific than map[string]interface{}: go.dev/play/p/Le00DlzOz8T. This should make it trivial to build your values. Commented Jan 6, 2022 at 17:35
  • @CeriseLimón That helps me iterate through colours - but then I can't assert to a string; for colour := range c { cs := colour.(string) } --> invalid type assertion: colour.(string) (non-interface type int on left). Still trying to work out why it thinks I'm converting to an int. Commented Jan 6, 2022 at 17:42
  • @Peter I've had issues trying to Unmarshal to a struct when the keys are dynamic/arbitrarily named and haven't found any wisdom from other questions on the topic. Do you know how I would use the structs you suggested to Unmarshal my YAML data? Commented Jan 6, 2022 at 17:43
  • Ah, bingo! That's solved it for me, thank you @CeriseLimón :)) Commented Jan 6, 2022 at 17:52

2 Answers 2

1

Since only the keys of the map are unknown at compile time but the structure is known you can be much more specific than map[string]interface{}:

type Document struct { Fruits map[string]Fruit `yaml:"fruit"` } type Fruit struct { Colours []string `yaml:"colour"` } 

This makes it trivial to build your values:

package main import ( "fmt" "gopkg.in/yaml.v3" ) var data string = ` --- fruit: apple: colour: - green 'banana': colour: - yellow pear: colour: - green - yellow ` func main() { var m Document yaml.Unmarshal([]byte(data), &m) for name, fruit := range m.Fruits { for _, colour := range fruit.Colours { fmt.Printf("%s:%s\n", name, colour) } } } 

Try it on the playground: https://go.dev/play/p/phnvRriQmMh

Sign up to request clarification or add additional context in comments.

1 Comment

Excellent, thanks! So with Cerise's help I've fixed my original solution and with your help I've got a different but also working solution. Next working day I'll try applying this to my real use-case and see which works better. Either way thanks for the help!
1

Thanks to Cerise Limón for providing the corrections that helped me to fix my solution!

package main import ( "fmt" "log" "gopkg.in/yaml.v3" ) var data string = ` --- fruit: apple: colour: - green 'banana': colour: - yellow pear: colour: - green - yellow ` func main() { m := make(map[string]interface{}) err := yaml.Unmarshal([]byte(data), &m) if err != nil { log.Fatal("Failed to parse YAML file") } for _, v := range m { fruits := v.(map[string]interface{}) for fruit, v2 := range fruits { colours := v2.(map[string]interface{})["colour"] c := colours.([]interface{}) for _, colour := range c { cs := colour.(string) fmt.Printf("%v:%v\n", fruit, cs) } } } } 

https://go.dev/play/p/dZT84tDLMjE

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.