dictionarygogenericsreflectiongo-map

Check if a map is subset of another map


This question is already answered in many other languages. In golang with simple maps (no nesting) how to find out if a map is subset of another. for example: map[string]string{"a": "b", "e": "f"} is subset of map[string]string{"a": "b", "c": "d", "e": "f"}. I want a generic method. My code:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := map[string]string{"a": "b", "c": "d", "e": "f"}
    b := map[string]string{"a": "b", "e": "f"}
    c := IsMapSubset(a, b)
    fmt.Println(c)
}

func IsMapSubset(mapSet interface{}, mapSubset interface{}) bool {

    mapSetValue := reflect.ValueOf(mapSet)
    mapSubsetValue := reflect.ValueOf(mapSubset)

    if mapSetValue.Kind() != reflect.Map || mapSubsetValue.Kind() != reflect.Map {
        return false
    }
    if reflect.TypeOf(mapSetValue) != reflect.TypeOf(mapSubsetValue) {
        return false
    }
    if len(mapSubsetValue.MapKeys()) == 0 {
        return true
    }

    iterMapSubset := mapSubsetValue.MapRange()

    for iterMapSubset.Next() {
        k := iterMapSubset.Key()
        v := iterMapSubset.Value()

        if value := mapSetValue.MapIndex(k); value == nil || v != value { // invalid: value == nil
            return false
        }
    }

    return true
}

When I want to check if subset map key exists in set map, MapIndex returns zero value of type and make it impossible to compare it with anything.

Afterall can I do the same job better?


Solution

  • Value.MapIndex() returns a reflect.Value which is a struct, and nil is not a valid value for structs. You can't compare a struct value to nil.

    Value.MapIndex() states that:

    It returns the zero Value if key is not found in the map or if v represents a nil map.

    So to tell if the key was not found in the map, check if the returned reflect.Value is its zero value. For that you may use the Value.IsValid() method.

    You also can't (shouldn't) compare reflect.Value values. Instead obtain their wrapped value using Value.Interface(), and compare those.

    if v2 := mapSetValue.MapIndex(k); !v2.IsValid() || v.Interface() != v2.Interface() {
        return false
    }
    

    Testing it:

    a := map[string]string{"a": "b", "c": "d", "e": "f"}
    b := map[string]string{"a": "b", "e": "f"}
    fmt.Println(IsMapSubset(a, b))
    
    c := map[string]string{"a": "b", "e": "X"}
    fmt.Println(IsMapSubset(a, c))
    

    Output will be (try it on the Go Playground):

    true
    false