225 lines
6.4 KiB
Go
225 lines
6.4 KiB
Go
// Package maps provides reusable functions for manipulating nested
|
|
// map[string]interface{} maps are common unmarshal products from
|
|
// various serializers such as json, yaml etc.
|
|
package maps
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Flatten takes a map[string]interface{} and traverses it and flattens
|
|
// nested children into keys delimited by delim.
|
|
//
|
|
// It's important to note that all nested maps should be
|
|
// map[string]interface{} and not map[interface{}]interface{}.
|
|
// Use IntfaceKeysToStrings() to convert if necessary.
|
|
//
|
|
// eg: `{ "parent": { "child": 123 }}` becomes `{ "parent.child": 123 }`
|
|
// In addition, it keeps track of and returns a map of the delimited keypaths with
|
|
// a slice of key parts, for eg: { "parent.child": ["parent", "child"] }. This
|
|
// parts list is used to remember the key path's original structure to
|
|
// unflatten later.
|
|
func Flatten(m map[string]interface{}, keys []string, delim string) (map[string]interface{}, map[string][]string) {
|
|
var (
|
|
out = make(map[string]interface{})
|
|
keyMap = make(map[string][]string)
|
|
)
|
|
for key, val := range m {
|
|
// Copy the incoming key paths into a fresh list
|
|
// and append the current key in the iteration.
|
|
kp := make([]string, 0, len(keys)+1)
|
|
kp = append(kp, keys...)
|
|
kp = append(kp, key)
|
|
|
|
switch cur := val.(type) {
|
|
case map[string]interface{}:
|
|
// Empty map.
|
|
if len(cur) == 0 {
|
|
newKey := strings.Join(kp, delim)
|
|
out[newKey] = val
|
|
keyMap[newKey] = kp
|
|
continue
|
|
}
|
|
|
|
// It's a nested map. Flatten it recursively.
|
|
next, parts := Flatten(cur, kp, delim)
|
|
|
|
// Copy the resultant key parts and the value maps.
|
|
for k, p := range parts {
|
|
keyMap[k] = p
|
|
}
|
|
for k, v := range next {
|
|
out[k] = v
|
|
}
|
|
default:
|
|
newKey := strings.Join(kp, delim)
|
|
out[newKey] = val
|
|
keyMap[newKey] = kp
|
|
}
|
|
}
|
|
return out, keyMap
|
|
}
|
|
|
|
// Unflatten takes a flattened key:value map (non-nested with delimited keys)
|
|
// and returns a nested map where the keys are split into hierarchies by the given
|
|
// delimiter. For instance, `parent.child.key: 1` to `{parent: {child: {key: 1}}}`
|
|
//
|
|
// It's important to note that all nested maps should be
|
|
// map[string]interface{} and not map[interface{}]interface{}.
|
|
// Use IntfaceKeysToStrings() to convert if necessary.
|
|
func Unflatten(m map[string]interface{}, delim string) map[string]interface{} {
|
|
out := make(map[string]interface{})
|
|
|
|
// Iterate through the flat conf map.
|
|
for k, v := range m {
|
|
var (
|
|
keys = strings.Split(k, delim)
|
|
next = out
|
|
)
|
|
|
|
// Iterate through key parts, for eg:, parent.child.key
|
|
// will be ["parent", "child", "key"]
|
|
for _, k := range keys[:len(keys)-1] {
|
|
sub, ok := next[k]
|
|
if !ok {
|
|
// If the key does not exist in the map, create it.
|
|
sub = make(map[string]interface{})
|
|
next[k] = sub
|
|
}
|
|
if n, ok := sub.(map[string]interface{}); ok {
|
|
next = n
|
|
}
|
|
}
|
|
|
|
// Assign the value.
|
|
next[keys[len(keys)-1]] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Merge recursively merges map a into b (left to right), mutating
|
|
// and expanding map b. Note that there's no copying involved, so
|
|
// map b will retain references to map a.
|
|
//
|
|
// It's important to note that all nested maps should be
|
|
// map[string]interface{} and not map[interface{}]interface{}.
|
|
// Use IntfaceKeysToStrings() to convert if necessary.
|
|
func Merge(a, b map[string]interface{}) {
|
|
for key, val := range a {
|
|
// Does the key exist in the target map?
|
|
// If no, add it and move on.
|
|
bVal, ok := b[key]
|
|
if !ok {
|
|
b[key] = val
|
|
continue
|
|
}
|
|
|
|
// If the incoming val is not a map, do a direct merge.
|
|
if _, ok := val.(map[string]interface{}); !ok {
|
|
b[key] = val
|
|
continue
|
|
}
|
|
|
|
// The source key and target keys are both maps. Merge them.
|
|
switch v := bVal.(type) {
|
|
case map[string]interface{}:
|
|
Merge(val.(map[string]interface{}), v)
|
|
default:
|
|
b[key] = val
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search recursively searches a map for a given path. The path is
|
|
// the key map slice, for eg:, parent.child.key -> [parent child key].
|
|
//
|
|
// It's important to note that all nested maps should be
|
|
// map[string]interface{} and not map[interface{}]interface{}.
|
|
// Use IntfaceKeysToStrings() to convert if necessary.
|
|
func Search(mp map[string]interface{}, path []string) interface{} {
|
|
next, ok := mp[path[0]]
|
|
if ok {
|
|
if len(path) == 1 {
|
|
return next
|
|
}
|
|
switch next.(type) {
|
|
case map[string]interface{}:
|
|
return Search(next.(map[string]interface{}), path[1:])
|
|
default:
|
|
return nil
|
|
} //
|
|
// It's important to note that all nested maps should be
|
|
// map[string]interface{} and not map[interface{}]interface{}.
|
|
// Use IntfaceKeysToStrings() to convert if necessary.
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Copy returns a copy of a conf map by doing a JSON marshal+unmarshal
|
|
// pass. Inefficient, but creates a true deep copy. There is a side
|
|
// effect, that is, all numeric types change to float64.
|
|
//
|
|
// It's important to note that all nested maps should be
|
|
// map[string]interface{} and not map[interface{}]interface{}.
|
|
// Use IntfaceKeysToStrings() to convert if necessary.
|
|
func Copy(mp map[string]interface{}) map[string]interface{} {
|
|
var out map[string]interface{}
|
|
b, _ := json.Marshal(mp)
|
|
json.Unmarshal(b, &out)
|
|
return out
|
|
}
|
|
|
|
// IntfaceKeysToStrings recursively converts map[interface{}]interface{} to
|
|
// map[string]interface{}. Some parses such as YAML unmarshal return this.
|
|
func IntfaceKeysToStrings(mp map[string]interface{}) {
|
|
for key, val := range mp {
|
|
switch cur := val.(type) {
|
|
case map[interface{}]interface{}:
|
|
x := make(map[string]interface{})
|
|
for k, v := range cur {
|
|
x[fmt.Sprintf("%v", k)] = v
|
|
}
|
|
mp[key] = x
|
|
IntfaceKeysToStrings(x)
|
|
case []interface{}:
|
|
for i, v := range cur {
|
|
switch sub := v.(type) {
|
|
case map[interface{}]interface{}:
|
|
x := make(map[string]interface{})
|
|
for k, v := range sub {
|
|
x[fmt.Sprintf("%v", k)] = v
|
|
}
|
|
cur[i] = x
|
|
IntfaceKeysToStrings(x)
|
|
case map[string]interface{}:
|
|
IntfaceKeysToStrings(sub)
|
|
}
|
|
}
|
|
case map[string]interface{}:
|
|
IntfaceKeysToStrings(cur)
|
|
}
|
|
}
|
|
}
|
|
|
|
// StringSliceToLookupMap takes a slice of strings and returns a lookup map
|
|
// with the slice values as keys with true values.
|
|
func StringSliceToLookupMap(s []string) map[string]bool {
|
|
mp := make(map[string]bool, len(s))
|
|
for _, v := range s {
|
|
mp[v] = true
|
|
}
|
|
return mp
|
|
}
|
|
|
|
// Int64SliceToLookupMap takes a slice of int64s and returns a lookup map
|
|
// with the slice values as keys with true values.
|
|
func Int64SliceToLookupMap(s []int64) map[int64]bool {
|
|
mp := make(map[int64]bool, len(s))
|
|
for _, v := range s {
|
|
mp[v] = true
|
|
}
|
|
return mp
|
|
}
|