5

I'm trying to parse some YAML file into go struct, but the file itself can not be treated as ordinary go structure: some of values may be either string or map[string][]string.

What I have tried is to do custom unmarshal func:

func (domain *Domain) UnmarshalYAML(unmarshal func(interface{}) error) error {
    fmt.Println("Parsing domain")

    var hostUrl interface{}
    unmarshal(&hostUrl)
    fmt.Println(reflect.TypeOf(hostUrl))

    switch hostUrl.(type) {
    case string:
        domain.Host = hostUrl.(string)
    case map[interface {}]interface {}:
        fmt.Println("got map")
        v := reflect.ValueOf(hostUrl)
        fmt.Println(v.MapKeys()[0])
        for _, host := range v.MapKeys() {
            domain.Host = host.Interface().(string)

            fmt.Println(v.MapIndex(host)) 
            //HERE I NEED TO DO SMTH LIKE THIS:
            //domain.Urls = v.MapIndex(host).([]string)

        }
    default:
        return errors.New("invalid config file, cant parse domains")
    }

    return nil
}

My domain structure looks like this:

type Domain struct {
    Host        string
    Urls        []string
}

But this code causes an error:

invalid type assertion: v.MapIndex(host).([]string) (non-interface type reflect.Value on left) 

So my question may sound like "how to convert {}interface to []string?" or it may become more complex: "How to parse YAML file into go struct if some key can be either simple string or map[string][]string?"

UPDATE: Responding to @mkopriva:

fmt.Println(v.MapIndex(host))
for url := range v.MapIndex(host).Interface().([] interface{}) {
        fmt.Println(url)
}

Didnt help me, as now it just prints some integers (0), but there should be a string. Converting it to an array of strings throws another error:

panic: interface conversion: interface {} is []interface {}, not []string
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Alex Mokeev
  • 304
  • 3
  • 17
  • 1
    `reflect.Value.MapIndex` does not return a value of type `interface{}` but of type `reflect.Value` therefore the type assertion won't compile. `reflect.Value` has a method called `Interface` that returns it's underlying value as `interface{}`, on it you can do type assertion. Or you can use `reflect.Value`'s `Kind` method to figure out the underlying type of the interface and go from there. – mkopriva Jan 28 '18 at 15:48
  • @mkopriva Updated the question with the results – Alex Mokeev Jan 28 '18 at 16:03
  • It would be helpful to show some sample yaml as well. – Marc Jan 28 '18 at 16:08
  • 1
    https://play.golang.org/p/tcig_e8o0bk FYI you cannot type assert `[]interface{}` to `[]T`, you need to loop over each of the `interface{}` items and assert them individually. Also note the example code is not guarding against possible panics due to invalid type assertions, you should use the "comma ok" idiom to avoid those. – mkopriva Jan 28 '18 at 16:09
  • @mkopriva in that case go says: `invalid type assertion: url.(string) (non-interface type int on left)` – Alex Mokeev Jan 28 '18 at 16:11
  • it means `url` is not interface so you cannot do type assertion on that value as already mentioned in my first comment, it also tells you what the actual type is (int) so if you want to store that as a string somewhere you should use the `strconv` package to do that. – mkopriva Jan 28 '18 at 16:12
  • oh noo. Everything was so much easier. I was doing iteration wrong: range return two values: index and value itself. I was only catching first one and of course there was an error casting it to string – Alex Mokeev Jan 28 '18 at 16:14

1 Answers1

5

Thanks to @mkopriva and the snippet from the sandbox. The reason of integer appearing during iterating over v.MapIndex(host).Interface().([] interface{}) is that range returns two values: index and corresponding to that index value. I was only catching the first one. It is why I wasn't able to cast it to string.

Working loop:

for _, url := range v.MapIndex(host).Interface().([] interface{}) {
    fmt.Println(url.(string))
    domain.Urls = append(domain.Urls,url.(string) )
}
Alex Mokeev
  • 304
  • 3
  • 17