diff --git a/maps/maps.go b/maps/maps.go index bdb9d62b..55271c51 100644 --- a/maps/maps.go +++ b/maps/maps.go @@ -11,6 +11,12 @@ import ( "github.com/mitchellh/copystructure" ) +const ( + escapeChar = "~" + tildeEscape = escapeChar + "0" + delimEscape = escapeChar + "1" +) + // Flatten takes a map[string]interface{} and traverses it and flattens // nested children into keys delimited by delim. // @@ -33,6 +39,21 @@ func Flatten(m map[string]interface{}, keys []string, delim string) (map[string] return out, keyMap } +func escape(keyPairs []string, delim string) []string { + if delim == "" { + return keyPairs + } + var result []string + for _, kp := range keyPairs { + // first pass, escape all escape characters + out := strings.ReplaceAll(kp, escapeChar, tildeEscape) + // second pass, escape the delimiter + out = strings.Replace(out, delim, delimEscape, -1) + result = append(result, out) + } + return result +} + func flatten(m map[string]interface{}, keys []string, delim string, out map[string]interface{}, keyMap map[string][]string) { for key, val := range m { // Copy the incoming key paths into a fresh list @@ -45,7 +66,7 @@ func flatten(m map[string]interface{}, keys []string, delim string, out map[stri case map[string]interface{}: // Empty map. if len(cur) == 0 { - newKey := strings.Join(kp, delim) + newKey := strings.Join(escape(kp, delim), delim) out[newKey] = val keyMap[newKey] = kp continue @@ -54,13 +75,25 @@ func flatten(m map[string]interface{}, keys []string, delim string, out map[stri // It's a nested map. Flatten it recursively. flatten(cur, kp, delim, out, keyMap) default: - newKey := strings.Join(kp, delim) + newKey := strings.Join(escape(kp, delim), delim) out[newKey] = val keyMap[newKey] = kp } } } +func unescape(keyPairs []string, delim string) []string { + var result []string + for _, kp := range keyPairs { + // first pass, unescape the delimiter + out := strings.Replace(kp, delimEscape, delim, -1) + // second pass, unescape all escape characters + out = strings.Replace(out, tildeEscape, escapeChar, -1) + result = append(result, out) + } + return result +} + // 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}}}` @@ -79,7 +112,7 @@ func Unflatten(m map[string]interface{}, delim string) map[string]interface{} { ) if delim != "" { - keys = strings.Split(k, delim) + keys = unescape(strings.Split(k, delim), delim) } else { keys = []string{k} } diff --git a/providers/nats/nats_test.go b/providers/nats/nats_test.go index 76882319..5fd637e7 100644 --- a/providers/nats/nats_test.go +++ b/providers/nats/nats_test.go @@ -28,6 +28,9 @@ func TestNats(t *testing.T) { kv, err := js.CreateKeyValue(&nats.KeyValueConfig{ Bucket: "test", }) + if err != nil { + t.Fatal(err) + } _, err = kv.Put("some.test.color", []byte("blue")) if err != nil { t.Fatal(err) @@ -46,8 +49,8 @@ func TestNats(t *testing.T) { t.Fatal(err) } - assert.Equal(t, k.Keys(), []string{"some.test.color"}) - assert.Equal(t, k.Get("some.test.color"), "blue") + assert.Equal(t, []string{"some.test.color"}, k.Keys()) + assert.Equal(t, "blue", k.Get("some.test.color")) err = provider.Watch(func(event interface{}, err error) { if err != nil { diff --git a/tests/maps_test.go b/tests/maps_test.go index 9b52b3c2..e4317440 100644 --- a/tests/maps_test.go +++ b/tests/maps_test.go @@ -103,16 +103,16 @@ var testMap3 = map[string]interface{}{ func TestFlatten(t *testing.T) { f, k := maps.Flatten(testMap, nil, delim) assert.Equal(t, map[string]interface{}{ - "parent.child.key": 123, - "parent.child.key.with.dot": 456, - "top": 789, - "empty": map[string]interface{}{}, + "parent.child.key": 123, + "parent.child.key~1with~1dot": 456, + "top": 789, + "empty": map[string]interface{}{}, }, f) assert.Equal(t, map[string][]string{ - "parent.child.key": {"parent", "child", "key"}, - "parent.child.key.with.dot": {"parent", "child", "key.with.dot"}, - "top": {"top"}, - "empty": {"empty"}, + "parent.child.key": {"parent", "child", "key"}, + "parent.child.key~1with~1dot": {"parent", "child", "key.with.dot"}, + "top": {"top"}, + "empty": {"empty"}, }, k) } @@ -125,11 +125,11 @@ func BenchmarkFlatten(b *testing.B) { func TestUnflatten(t *testing.T) { m, _ := maps.Flatten(testMap, nil, delim) um := maps.Unflatten(m, delim) - assert.NotEqual(t, um, testMap) + assert.Equal(t, testMap, um) m, _ = maps.Flatten(testMap2, nil, delim) um = maps.Unflatten(m, delim) - assert.Equal(t, um, testMap2) + assert.Equal(t, testMap2, um) } func TestIntfaceKeysToStrings(t *testing.T) {