Created
January 25, 2019 22:13
-
-
Save maplebed/28132e95a42caf557f52438e81d58fcd to your computer and use it in GitHub Desktop.
turns deep json objects into flat objects with dotted keys
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// unpackDeepEvent takes a parsed body (straight from json) and returns the | |
// unmodified original or a copy with any nested maps turned into dotted keys. | |
// (eg `"headers": {"a":5,"b":3}` turns in to `"headers.a":5,"headers.b":3`). | |
// Additionally returns a flag saying whether nested json was present, and an | |
// int indicating the depth actually unpacked. | |
func unpackDeepEvent(raw map[string]interface{}, maxDepth int) (map[string]interface{}, bool, int) { | |
unpacked := make(map[string]interface{}, len(raw)) | |
nestedKeys := make(map[string]struct{}) | |
var actualDepth int | |
for k, v := range raw { | |
switch v := v.(type) { | |
case map[string]interface{}: | |
nestedKeys[k] = struct{}{} | |
if newDepth := dotifyMap(k, unpacked, v, maxDepth); newDepth > actualDepth { | |
actualDepth = newDepth | |
} | |
default: | |
} | |
} | |
// only use unpacked and make the copy if there is nested json | |
if len(unpacked) != 0 { | |
for k, v := range raw { | |
if _, found := nestedKeys[k]; !found { | |
unpacked[k] = v | |
} | |
} | |
return unpacked, true, actualDepth | |
} | |
return raw, false, 0 | |
} | |
// dotifyMap mutates top, adding all elements of inner to it by using | |
// topkey+innerkey as the key for top. topkey should not be empty. Will only go | |
// the configured number of levels deep into a map before refusing to unpack | |
// anymore. Returns the actual depth it went before returning. | |
func dotifyMap(topkey string, top, inner map[string]interface{}, maxDepth int) int { | |
// only go max curDepth before just including the nested json as a thing (that | |
// will get stringified later) | |
if maxDepth <= 0 { | |
top[topkey] = inner | |
return 0 | |
} | |
depth := 0 | |
for k, v := range inner { | |
newkey := topkey + "." + k | |
switch v := v.(type) { | |
case map[string]interface{}: | |
if newDepth := dotifyMap(newkey, top, v, maxDepth-1); newDepth > depth { | |
depth = newDepth | |
} | |
default: | |
top[newkey] = v | |
} | |
} | |
return depth + 1 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func TestUnpackDeepEvent(t *testing.T) { | |
testObj := map[string]interface{}{ | |
"one": 1, | |
"two": "tttoooowwwwoooo", | |
"deeptop": map[string]interface{}{ | |
"subone": "one", | |
}, | |
"deeplist": map[string]interface{}{ | |
"sublist": []string{"foo", "bar"}, | |
}, | |
"deeper": map[string]interface{}{ | |
"subfield": 3.145, | |
"subdeep": map[string]interface{}{ | |
"subdeeper": "two", | |
}, | |
}, | |
"deepest": map[string]interface{}{ | |
"subdeep": map[string]interface{}{ | |
"subdeeper": map[string]interface{}{ | |
"subdeepest": "three", | |
}, | |
}, | |
}, | |
"slice": []string{"one", "two", "three"}, | |
} | |
expectedRes := map[string]interface{}{ | |
"one": 1, | |
"two": "tttoooowwwwoooo", | |
"deeptop.subone": "one", | |
"deeplist.sublist": []string{"foo", "bar"}, | |
"deeper.subfield": 3.145, | |
"deeper.subdeep.subdeeper": "two", | |
// should only unpack 3 levels deep | |
"deepest.subdeep.subdeeper": map[string]interface{}{ | |
"subdeepest": "three", | |
}, | |
"slice": []string{"one", "two", "three"}, | |
} | |
res, triggered, depth := unpackDeepEvent(testObj, 2) | |
test.Equals(t, res, expectedRes) | |
test.Equals(t, triggered, true) | |
test.Equals(t, depth, 2) | |
// if we don't restrict the depth, it should go 3 deep | |
res, triggered, depth = unpackDeepEvent(testObj, 5) | |
test.Equals(t, depth, 3) | |
// verify the testObj was not mutated | |
test.Equals(t, len(testObj["deeptop"].(map[string]interface{})), 1) | |
test.Equals(t, len(testObj), 7) | |
// verify non-nested json returns false, returned value matches input | |
noNest := map[string]interface{}{"one": 1, "two": "baz"} | |
res, triggered, depth = unpackDeepEvent(noNest, 3) | |
test.Equals(t, res, noNest) | |
test.Equals(t, triggered, false) | |
test.Equals(t, depth, 0) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment