简体   繁体   中英

Golang merge deeply two maps

I'm trying to deep copy of two yaml, trying to simulate the behaviour that use helm when you pass multiple values during an installation. Because of a project restriction, I'm getting the values from strings and not directly form yamls. The problem is that the copy is not done as I expect.

I add an example:

value1 :

test:
  somekey: 1
  otherkey: 2
othertest:
  somekey: 3

value2 :

test:
  somekey: NEWVALUE1
  NEWKEY: NEWVALUE2

EXPECTED OUTPUT :

test:
  somekey: NEWVALUE1
  otherkey: 2
  NEWKEY: NEWVALUE2
othertest:
  somekey: 3

RESULT :

othertest:
  somekey: 3
test:
  NEWKEY: NEWVALUE2
  somekey: NEWVALUE1

So as you can see in the example, it is overwritting all the test map, when I would like to merge both test maps.

Here I add the code I'm using

package main

import (
    "github.com/prometheus/common/log"
    "gopkg.in/yaml.v2"
)

func main() {
    value1 := "test:\n  somekey: 1\n  otherkey: 2\nothertest:\n  somekey: 3"
    value2 := "test:\n  somekey: NEWVALUE1\n  NEWKEY: NEWVALUE2"
    values := []string{value1, value2}
    result, err := getValuesFromString(values)
    if err != nil {
        log.Info(err)
    }

    //expecting: test:\n  somekey: NEWVALUE1\n  otherkey: 2\n  NEWKEY: NEWVALUE2\nothertest:\n  somekey: 3
    //getting: othertest:\n  somekey: 3\ntest:\n  NEWKEY: NEWVALUE2\n  somekey: NEWVALUE1\n
    log.Info("FINAL VALUES: " + result)
}

func getValuesFromString(values []string) (string, error) {

    var resultValues map[string]interface{}
    var bs []byte
    for _, value := range values {
        log.Info(values)
        var override map[string]interface{}
        bs = []byte(value)

        if err := yaml.Unmarshal(bs, &override); err != nil {
            log.Info(err)
            continue
        }

        //check if is nil. This will only happen for the first value
        if resultValues == nil {
            resultValues = override
        } else {
            resultValues = mergeMaps(resultValues, override)
        }
    }

    bs, err := yaml.Marshal(resultValues)
    if err != nil {
        log.Info(err)
        return "", err
    }

    return string(bs), nil
}

func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
    out := make(map[string]interface{}, len(a))
    for k, v := range a {
        out[k] = v
    }
    for k, v := range b {
        if v, ok := v.(map[string]interface{}); ok {
            if bv, ok := out[k]; ok {
                if bv, ok := bv.(map[string]interface{}); ok {
                    out[k] = mergeMaps(bv, v)
                    continue
                }
            }
        }
        out[k] = v
    }
    return out
}

I think the error is that I'm generating incorrectly the map from the string var resultValues map[string]interface{} , but I didn't find the way to convert the string to a map

Use map[interface{}]interface{} instead of map[string]interface{} . You could have a deeper look at yaml.Unmarshal .

func mergeMaps(a, b map[interface{}]interface{}) map[interface{}]interface{} {
    out := make(map[interface{}]interface{}, len(a))
    for k, v := range a {
        out[k] = v
    }
    for k, v := range b {
        // If you use map[string]interface{}, ok is always false here.
        // Because yaml.Unmarshal will give you map[interface{}]interface{}.
        if v, ok := v.(map[interface{}]interface{}); ok {
            if bv, ok := out[k]; ok {
                if bv, ok := bv.(map[interface{}]interface{}); ok {
                    out[k] = mergeMaps(bv, v)
                    continue
                }
            }
        }
        out[k] = v
    }
    return out
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM