4
votes

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
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
@mkopriva Updated the question with the results - Alex Mokeev
It would be helpful to show some sample yaml as well. - Marc
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
@mkopriva in that case go says: invalid type assertion: url.(string) (non-interface type int on left) - Alex Mokeev

1 Answers

4
votes

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) )
}