adambabik
12/6/2016 - 11:16 AM

Go Maps Merge

Go Maps Merge

package main

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestMergeOneLevelDeepMaps(t *testing.T) {
	a := map[string]interface{}{
		"key1": 1,
	}
	b := map[string]interface{}{
		"key1": 2,
	}
	c := MergeMaps(a, b)
	assert.Equal(t, map[string]interface{}{
		"key1": 2,
	}, c)
}

func TestMergeTwoLevelDeepMaps(t *testing.T) {
	a := map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": 1,
		},
	}
	b := map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": 2,
		},
	}
	c := MergeMaps(a, b)
	assert.Equal(t, map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": 2,
		},
	}, c)
}

func TestMergeUnevenMaps(t *testing.T) {
	a := map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": 1,
		},
	}
	b := map[string]interface{}{
		"key1": 2,
	}
	c := MergeMaps(a, b)
	assert.Equal(t, map[string]interface{}{
		"key1": 2,
	}, c)

	c = MergeMaps(b, a)
	assert.Equal(t, map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": 1,
		},
	}, c)
}

func TestMergeMapsWithSlices(t *testing.T) {
	a := map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": []int{1, 2},
		},
	}
	b := map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": []int{3, 4},
		},
	}
	c := MergeMaps(a, b)
	assert.Equal(t, map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": []int{1, 2, 3, 4},
		},
	}, c)
}

func TestMergeWithAdditionalKeys(t *testing.T) {
	a := map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": []int{1, 2},
			"key12": 3,
		},
		"key2": 4,
	}
	b := map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": []int{3, 4},
		},
	}
	c := MergeMaps(a, b)
	assert.Equal(t, map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": []int{1, 2, 3, 4},
			"key12": 3,
		},
		"key2": 4,
	}, c)

	c = MergeMaps(b, a)
	assert.Equal(t, map[string]interface{}{
		"key1": map[string]interface{}{
			"key11": []int{3, 4, 1, 2},
			"key12": 3,
		},
		"key2": 4,
	}, c)
}
package main

import "reflect"

// CopyOmmitedKeys copies only keys from the source which do not exist in dest.
func CopyOmmitedKeys(dest, source map[string]interface{}) {
	leftV, rightV := reflect.ValueOf(dest), reflect.ValueOf(source)
	leftKeysV, rightKeysV := leftV.MapKeys(), rightV.MapKeys()

	leftKeysSet := map[string]bool{}
	for _, leftKeyV := range leftKeysV {
		leftKeysSet[leftKeyV.Interface().(string)] = true
	}

	for _, rightKeyV := range rightKeysV {
		rightKeyStr := rightKeyV.Interface().(string)
		if _, ok := leftKeysSet[rightKeyStr]; ok {
			continue
		}

		dest[rightKeyStr] = rightV.MapIndex(rightKeyV).Interface()
	}
}

// MergeMaps merges sources into dest by comparing keys.
func MergeMaps(dest, source map[string]interface{}) map[string]interface{} {
	result := map[string]interface{}{}
	resultV := reflect.ValueOf(result)

	leftV, rightV := reflect.ValueOf(dest), reflect.ValueOf(source)
	leftKeysV := leftV.MapKeys()

	// goon.Dump(dest)
	// goon.Dump(source)

	for _, leftKeyV := range leftKeysV {
		leftValV := leftV.MapIndex(leftKeyV)

		sourceValV := rightV.MapIndex(leftKeyV)
		if !sourceValV.IsValid() {
			resultV.SetMapIndex(leftKeyV, leftValV)
			continue
		}

		// fmt.Printf("=== key %s\n", leftKeyV.Interface().(string))
		// fmt.Printf("=== kind %s val %s\n", leftValV.Elem().Kind(), leftValV.Interface())
		// fmt.Printf("=== kind %s val %s\n", sourceValV.Elem().Kind(), sourceValV.Interface())

		kind := leftValV.Elem().Kind()

		if kind == reflect.Map && kind == sourceValV.Elem().Kind() {
			resultV.SetMapIndex(leftKeyV, reflect.ValueOf(MergeMaps(
				leftValV.Interface().(map[string]interface{}),
				sourceValV.Interface().(map[string]interface{}),
			)))
		} else if kind == reflect.Slice && kind == sourceValV.Elem().Kind() {
			// fmt.Printf("==== %s %s \n", kind, sourceValV.Elem().Kind())
			// goon.Dump(leftValV.Interface())
			// goon.Dump(sourceValV.Interface())
			resultV.SetMapIndex(
				leftKeyV,
				reflect.AppendSlice(leftValV.Elem(), sourceValV.Elem()))
		} else {
			resultV.SetMapIndex(leftKeyV, sourceValV)
		}
	}

	CopyOmmitedKeys(result, source)

	return result
}