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?
CodePudding user response:
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
CodePudding user response:
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)
}
}
}
}
