dr__noob
dr__noob

Reputation: 113

How to return value of a key in nested map

I want to write a generic function func GetVal(map[interface{}]interface{}, key interface{}) interface{}. This will take a map and a key to search for and return either the value or nil.

The map can have any data type and can go to any level of nesting. For example,

var s1 = "S1"
var s2 = "S2"
var s3 = s1 + "==" + s2 + "==S3"
var s4 = s3 + "==S4"
var nestedMap = map[interface{}]interface{}{
    "data": map[interface{}]interface{}{
        "TEST_KEY": "1234353453",
        "metadata": map[interface{}]interface{}{
            "created_time": "2022-08-06",
        },
        "custom_metadata": map[interface{}][]interface{}{
            "destroyed": []interface{}{
                &s1,
                map[string]interface{}{
                    "auth": []interface{}{
                        "val3", "val4", "val45",
                    },
                },
            },
        },
        &s2: &[]*string{
            &s1, &s2,
        },
        &s1: &[]int{
            10, 20, 233,
        },
        123: &s3,
    },
    s3: []interface{}{
        []interface{}{
            map[string]*string{
                s4: &s4,
            },
        },
    },
}

Expected return values GetVal(nestedMap, "metadata") should return {"created_time": "2022-08-06"} GetVal(nestedMap, "destroyed") should return

{  &s1,
   map[string]interface{}{
      "auth": []interface{}{
         "val3", "val4", "val45",
      },
   },
}

Is there a way to do it without an external library?

This question looks similar to Accessing Nested Map of Type map[string]interface{} in Golang but in my case the fields are not limited or always same

Upvotes: 1

Views: 1742

Answers (2)

wan_keen
wan_keen

Reputation: 166

// Supports getting value by path: i.e. k2.nestedK3.superNestedK1
// Thanks Lukasz Szymik for his answer, which inspired me to implement this functionality based on his code.
func GetValueByPathFromMap(data map[string]any, key string, passedKey string) (result any, found bool) {

keyAndPath := strings.SplitN(key, ".", 2)
currentKey := keyAndPath[0]
if passedKey != "" {
    passedKey = passedKey + "." + currentKey
} else {
    passedKey = currentKey
}

if _, isKeyExistInData := data[currentKey]; !isKeyExistInData {
    logrus.Warnf("[W] key path { %s } not found", passedKey)
    return
} else {

    if len(keyAndPath) > 1 {
        remainingPath := keyAndPath[1]
        switch data[currentKey].(type) {
        case map[string]any:
            if result, found = GetValueByPathFromMap(data[currentKey].(map[string]any), remainingPath, passedKey); found {
                return
            }
        }
    } else {
        return data[currentKey], true
    }
  }

return nil, false
}

Upvotes: 0

Lukasz Szymik
Lukasz Szymik

Reputation: 21

The question is kind of cryptic because the example is overcomplicated. If you want to get knowledge regarding the recurrent functions, you should start with something simpler like:

var nestedMap = map[string]any{
"k1": "v1",
"k2": map[string]any{
    "nestedK1": "nestedV1",
    "nestedK2": "nestedV2",
    "nestedK3": map[string]any{
        "superNestedK1" : "FOUND!!!",
    },
},}

Otherwise, an explanation will be hard.

Then you can work on functions like:

func GetVal(data map[string]any, key string) (result any, found bool) {
for k, v := range data {
    if k == key {
        return v, true
    } else {
        switch v.(type) {
        case map[string]any:
            if result, found = GetVal(v.(map[string]any), key); found {
                return
            }
        }
    }
}
return nil, false}

Later you can think about adding support for fancy stuff like map[interface{}][]interface{}

However, if you really need such complicated structure, I am not sure if the whole design of application is ok.

Maybe you should also think about adding searching for a full path inside the map k2.nestedK3.superNestedK1. It will remove ambiguity.

Upvotes: 2

Related Questions