Home > other >  recursively flatten a map golang
recursively flatten a map golang

Time:06-03

Using the below json Im trying to flatten it for ease of accessibility.

Example having 2 resource there can be n number of in this structure

"config": {
    "type": "r1",
    "properties": {
        "p1": "10",
        "p2": "10"
    },
    "connected": [
        {
            "type": "r3",
            "properties": {
              "p1": "10",
              "p2": "10"
            },
            "connected": [
                {}
        },
    ],
}

Custom flatterning logic

func keyValuePairs(m interface{}) map[string]interface{} {
    kvs := make(map[string]interface{})
    if reflect.ValueOf(m).Kind() == reflect.Map {
        mp, ok := m.(map[string]interface{})
        if ok {
            var key string
            var value interface{}
            for k, v := range mp {
                switch k {
                case "type":
                    key = v.(string)
                case "properties":
                    value = v
                case "connected":
                    if collection, ok := v.([]interface{}); ok {
                        for _, c := range collection {
                            for nk, nv := range keyValuePairs(c) {
                                nnv, _ := nv.(map[string]interface{})
                                _, ok := nnv["connectedTo"]
                                if !ok {
                                   nnv["connectedTo"] = key
                                }
                                kvs[nk] = nv
                            }
                        }
                    }
                default:
                    for nk, nv := range keyValuePairs(v) {
                        kvs[nk] = nv
                    }
                }
            }
            if key != "" {
                kvs[key] = value
            }
        } else {
            for k, v := range m.(map[string]interface{}) {
                kvs[k] = v
            }
        }
    }
    return kvs
}

The desired output is kinda fluctuationg, some runs I get the output what I require and some executions "connectedTo" attribute is empty.

{
"r1": {
        "p1": "10",
        "p2": "10"
    },
"r3" : {
        "connectedTo": "r1",
        "p1": "10",
        "p2": "10"
    },
}

I think the executions are not sequential. Please do correct me if Im wrong.

CodePudding user response:

Iteration order over a map is random. For details, see Why are iterations over maps random? and How to iterate maps in insertion order?

Now look at your loop:

var key string
var value interface{}
for k, v := range mp {
    switch k {
    case "type":
        key = v.(string)
    case "properties":
        value = v
    case "connected":
        if collection, ok := v.([]interface{}); ok {
            for _, c := range collection {
                for nk, nv := range keyValuePairs(c) {
                    nnv, _ := nv.(map[string]interface{})
                    _, ok := nnv["connectedTo"]
                    if !ok {
                       nnv["connectedTo"] = key
                    }
                    kvs[nk] = nv
                }
            }
        }
    default:
        for nk, nv := range keyValuePairs(v) {
            kvs[nk] = nv
        }
    }
}

You are using the key variable in the "connected" branch, but the order in which "type" and "connected" will be reached is non-deterministic (random).

The "type" branch is what sets key, but if "connected" is reached first, key will be empty.

You must not rely on map iteration order.

An easy fix is to first get the value associated with"type" first and assign it to key (which you use in the "connected" branch), before the loop.

For example:

key, _ := mp["type"].(string)
value := mp["properties"]
// Now process other properties:
for k, v := range mp {
    switch k {
    case "type", "properties": // Already handled
    case "connected":
        if collection, ok := v.([]interface{}); ok {
            for _, c := range collection {
                for nk, nv := range keyValuePairs(c) {
                    nnv, _ := nv.(map[string]interface{})
                    _, ok := nnv["connectedTo"]
                    if !ok {
                       nnv["connectedTo"] = key
                    }
                    kvs[nk] = nv
                }
            }
        }
    default:
        for nk, nv := range keyValuePairs(v) {
            kvs[nk] = nv
        }
    }
}
  • Related