-
Notifications
You must be signed in to change notification settings - Fork 9
/
flatten.go
133 lines (116 loc) · 3 KB
/
flatten.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Flatten makes flat, one-dimensional maps from arbitrarily nested ones.
//
// Map keys turn into compound
// names, like `a.b.1.c` (dotted style) or `a[b][1][c]` (Rails style). It takes input as either JSON strings or
// Go structures. It (only) knows how to traverse JSON types: maps, slices and scalars.
//
// You can flatten JSON strings.
//
// nested := `{
// "one": {
// "two": [
// "2a",
// "2b"
// ]
// },
// "side": "value"
// }`
//
// flat, err := FlattenString(nested, "", DotStyle)
//
// // output: `{ "one.two.0": "2a", "one.two.1": "2b", "side": "value" }`
//
// Or Go maps directly.
//
// t := map[string]interface{}{
// "a": "b",
// "c": map[string]interface{}{
// "d": "e",
// "f": "g",
// },
// "z": 1.4567,
// }
//
// flat, err := Flatten(nested, "", RailsStyle)
//
// // output:
// // map[string]interface{}{
// // "a": "b",
// // "c[d]": "e",
// // "c[f]": "g",
// // "z": 1.4567,
// // }
//
package main
import (
"errors"
"strconv"
)
// The presentation style of keys.
type SeparatorStyle int
const (
_ SeparatorStyle = iota
// Separate nested key components with dots, e.g. "a.b.1.c.d"
DotStyle
// Separate ala Rails, e.g. "a[b][c][1][d]"
RailsStyle
// Separate with underscore, e.g. "a_b_1_c_d"
UnderscoreStyle
)
// Nested input must be a map or slice
var NotValidInputError = errors.New("Not a valid input: map or slice")
// Flatten generates a flat map from a nested one. The original may include values of type map, slice and scalar,
// but not struct. Keys in the flat map will be a compound of descending map keys and slice iterations.
// The presentation of keys is set by style. A prefix is joined to each key.
func Flatten(nested map[interface{}]interface{}, prefix string, style SeparatorStyle) (map[string]interface{}, error) {
flatmap := make(map[string]interface{})
err := flatten(true, flatmap, nested, prefix, style)
if err != nil {
return nil, err
}
return flatmap, nil
}
func flatten(top bool, flatMap map[string]interface{}, nested interface{}, prefix string, style SeparatorStyle) error {
assign := func(newKey string, v interface{}) error {
switch v.(type) {
case map[interface{}]interface{}, []interface{}:
if err := flatten(false, flatMap, v, newKey, style); err != nil {
return err
}
default:
flatMap[newKey] = v
}
return nil
}
switch nested.(type) {
case map[interface{}]interface{}:
for k, v := range nested.(map[interface{}]interface{}) {
newKey := enkey(top, prefix, k.(string), style)
assign(newKey, v)
}
case []interface{}:
for i, v := range nested.([]interface{}) {
newKey := enkey(top, prefix, strconv.Itoa(i), style)
assign(newKey, v)
}
default:
return NotValidInputError
}
return nil
}
func enkey(top bool, prefix, subkey string, style SeparatorStyle) string {
key := prefix
if top {
key += subkey
} else {
switch style {
case DotStyle:
key += "." + subkey
case RailsStyle:
key += "[" + subkey + "]"
case UnderscoreStyle:
key += "_" + subkey
}
}
return key
}