-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4134c6f
commit 9faa6a0
Showing
8 changed files
with
677 additions
and
639 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package flatmap | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
) | ||
|
||
/* | ||
Do takes a nested map and flattens it into a single level map. The flattening | ||
roughly follows the [JSONPath] standard. Please see the test function to | ||
understand how the flattened output looks like. Here is an example that may | ||
fall out of date, so be careful: | ||
If this is the nested input: | ||
map[string]any{ | ||
"a": "foo", | ||
"b": []any{ | ||
map[string]any{ | ||
"c": "bar", | ||
"d": []any{ | ||
map[string]any{ | ||
"e": 2, | ||
}, | ||
true, | ||
}, | ||
}, | ||
map[string]any{ | ||
"c": "baz", | ||
"d": []any{ | ||
map[string]any{ | ||
"e": 3, | ||
}, | ||
false, | ||
}, | ||
}, | ||
}, | ||
} | ||
You can expect this flattened output: | ||
map[string]any{ | ||
".a": "foo", | ||
".b[0].c": "bar", | ||
".b[0].d[0].e": 2, | ||
".b[0].d[1]": true, | ||
".b[1].c": "baz", | ||
".b[1].d[0].e": 3, | ||
".b[1].d[1]": false, | ||
} | ||
[JSONPath]: https://goessner.net/articles/JsonPath/ | ||
*/ | ||
func Do(nested map[string]any) map[string]any { | ||
flattened := map[string]any{} | ||
for childKey, childValue := range nested { | ||
setChildren(flattened, childKey, childValue) | ||
} | ||
|
||
return flattened | ||
} | ||
|
||
// setChildren is a helper function for flatten. It is invoked recursively on a | ||
// child value. If the child is not a map or a slice, then the value is simply | ||
// set on the flattened map. If the child is a map or a slice, then the | ||
// function is invoked recursively on the child's values, until a | ||
// non-map-non-slice value is hit. | ||
func setChildren(flattened map[string]any, parentKey string, parentValue any) { | ||
newKey := fmt.Sprintf(".%s", parentKey) | ||
if reflect.TypeOf(parentValue) == nil { | ||
flattened[newKey] = parentValue | ||
return | ||
} | ||
|
||
if reflect.TypeOf(parentValue).Kind() == reflect.Map { | ||
children := parentValue.(map[string]any) | ||
for childKey, childValue := range children { | ||
newKey = fmt.Sprintf("%s.%s", parentKey, childKey) | ||
setChildren(flattened, newKey, childValue) | ||
} | ||
return | ||
} | ||
|
||
if reflect.TypeOf(parentValue).Kind() == reflect.Slice { | ||
children := parentValue.([]any) | ||
if len(children) == 0 { | ||
flattened[newKey] = children | ||
return | ||
} | ||
|
||
for childIndex, childValue := range children { | ||
newKey = fmt.Sprintf("%s[%v]", parentKey, childIndex) | ||
setChildren(flattened, newKey, childValue) | ||
} | ||
return | ||
} | ||
|
||
flattened[newKey] = parentValue | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package flatmap_test | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/nextmv-io/sdk/flatmap" | ||
) | ||
|
||
func Test_Do(t *testing.T) { | ||
type args struct { | ||
nested map[string]any | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want map[string]any | ||
}{ | ||
{ | ||
name: "flat", | ||
args: args{ | ||
nested: map[string]any{ | ||
"a": "foo", | ||
"b": 2, | ||
"c": true, | ||
}, | ||
}, | ||
want: map[string]any{ | ||
".a": "foo", | ||
".b": 2, | ||
".c": true, | ||
}, | ||
}, | ||
{ | ||
name: "flat with nil", | ||
args: args{ | ||
nested: map[string]any{ | ||
"a": "foo", | ||
"b": nil, | ||
"c": true, | ||
}, | ||
}, | ||
want: map[string]any{ | ||
".a": "foo", | ||
".b": nil, | ||
".c": true, | ||
}, | ||
}, | ||
{ | ||
name: "slice", | ||
args: args{ | ||
nested: map[string]any{ | ||
"a": "foo", | ||
"b": []any{ | ||
"bar", | ||
2, | ||
}, | ||
}, | ||
}, | ||
want: map[string]any{ | ||
".a": "foo", | ||
".b[0]": "bar", | ||
".b[1]": 2, | ||
}, | ||
}, | ||
{ | ||
name: "nested map", | ||
args: args{ | ||
nested: map[string]any{ | ||
"a": "foo", | ||
"b": map[string]any{ | ||
"c": "bar", | ||
"d": 2, | ||
}, | ||
}, | ||
}, | ||
want: map[string]any{ | ||
".a": "foo", | ||
".b.c": "bar", | ||
".b.d": 2, | ||
}, | ||
}, | ||
{ | ||
name: "slice with nested maps", | ||
args: args{ | ||
nested: map[string]any{ | ||
"a": "foo", | ||
"b": []any{ | ||
map[string]any{ | ||
"c": "bar", | ||
"d": 2, | ||
}, | ||
map[string]any{ | ||
"c": "baz", | ||
"d": 3, | ||
}, | ||
}, | ||
}, | ||
}, | ||
want: map[string]any{ | ||
".a": "foo", | ||
".b[0].c": "bar", | ||
".b[0].d": 2, | ||
".b[1].c": "baz", | ||
".b[1].d": 3, | ||
}, | ||
}, | ||
{ | ||
name: "slice with nested maps with nested slice", | ||
args: args{ | ||
nested: map[string]any{ | ||
"a": "foo", | ||
"b": []any{ | ||
map[string]any{ | ||
"c": "bar", | ||
"d": []any{ | ||
2, | ||
true, | ||
}, | ||
}, | ||
map[string]any{ | ||
"c": "baz", | ||
"d": []any{ | ||
3, | ||
false, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
want: map[string]any{ | ||
".a": "foo", | ||
".b[0].c": "bar", | ||
".b[0].d[0]": 2, | ||
".b[0].d[1]": true, | ||
".b[1].c": "baz", | ||
".b[1].d[0]": 3, | ||
".b[1].d[1]": false, | ||
}, | ||
}, | ||
{ | ||
name: "slice with nested maps with nested slice with nested map", | ||
args: args{ | ||
nested: map[string]any{ | ||
"a": "foo", | ||
"b": []any{ | ||
map[string]any{ | ||
"c": "bar", | ||
"d": []any{ | ||
map[string]any{ | ||
"e": 2, | ||
}, | ||
true, | ||
}, | ||
}, | ||
map[string]any{ | ||
"c": "baz", | ||
"d": []any{ | ||
map[string]any{ | ||
"e": 3, | ||
}, | ||
false, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
want: map[string]any{ | ||
".a": "foo", | ||
".b[0].c": "bar", | ||
".b[0].d[0].e": 2, | ||
".b[0].d[1]": true, | ||
".b[1].c": "baz", | ||
".b[1].d[0].e": 3, | ||
".b[1].d[1]": false, | ||
}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := flatmap.Do(tt.args.nested); !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("flatten() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* | ||
Package flatmap contains functions to flatten and unflatten maps: | ||
- [Do] flattens a nested map into a flat map. | ||
- [Undo] unflattens a flat map into a nested map. | ||
The flattening roughly follows the [JSONPath] standard. | ||
[JSONPath]: https://goessner.net/articles/JsonPath/ | ||
*/ | ||
package flatmap |
Oops, something went wrong.