forked from getsops/sops
/
flatten.go
187 lines (176 loc) · 4.94 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package stores
import (
"fmt"
"strconv"
"strings"
)
const mapSeparator = "__map_"
const listSeparator = "__list_"
// flattenAndMerge flattens the provided value and merges into the
// into map using prefix
func flattenAndMerge(into map[string]interface{}, prefix string, value interface{}) {
flattenedValue := flattenValue(value)
if flattenedValue, ok := flattenedValue.(map[string]interface{}); ok {
for flatK, flatV := range flattenedValue {
into[prefix+flatK] = flatV
}
} else {
into[prefix] = value
}
}
func flattenValue(value interface{}) interface{} {
var output interface{}
switch value := value.(type) {
case map[string]interface{}:
newMap := make(map[string]interface{})
for k, v := range value {
flattenAndMerge(newMap, mapSeparator+k, v)
}
output = newMap
case []interface{}:
newMap := make(map[string]interface{})
for i, v := range value {
flattenAndMerge(newMap, listSeparator+fmt.Sprintf("%d", i), v)
}
output = newMap
default:
output = value
}
return output
}
// Flatten flattens a map with potentially nested maps into a flat
// map. Only string keys are allowed on both the top-level map and
// child maps.
func Flatten(in map[string]interface{}) map[string]interface{} {
newMap := make(map[string]interface{})
for k, v := range in {
if flat, ok := flattenValue(v).(map[string]interface{}); ok {
for flatK, flatV := range flat {
newMap[k+flatK] = flatV
}
} else {
newMap[k] = v
}
}
return newMap
}
type token interface{}
type mapToken struct {
key string
}
type listToken struct {
position int
}
// tokenize converts a path generated by Flatten to be used as a key
// in the flattened map, and converts it to a slice of tokens
func tokenize(path string) []token {
const (
StateNormal = 0
StateMap = iota
StateList = iota
)
var tokens []token
state := StateNormal
lastTokenEnd := 0
i := 0
finishPrevToken := func() {
var t token
switch state {
case StateNormal:
t = mapToken{path[lastTokenEnd:i]}
case StateMap:
t = mapToken{path[lastTokenEnd+len(mapSeparator) : i]}
case StateList:
pos, _ := strconv.Atoi(path[lastTokenEnd+len(listSeparator) : i])
t = listToken{pos}
}
lastTokenEnd = i
tokens = append(tokens, t)
}
for i < len(path) {
if strings.HasPrefix(path[i:], mapSeparator) {
finishPrevToken()
state = StateMap
i += len(mapSeparator)
} else if strings.HasPrefix(path[i:], listSeparator) {
finishPrevToken()
state = StateList
i += len(listSeparator)
} else {
i++
}
}
finishPrevToken()
return tokens
}
// unflatten takes the currentNode, currentToken, nextToken and value
// and populates currentNode such that currentToken can be considered
// processed. It inspects nextToken to decide what type to allocate
// and assign under currentNode.
func unflatten(currentNode interface{}, currentToken, nextToken token, value interface{}) interface{} {
switch currentToken := currentToken.(type) {
case mapToken:
currentNode := currentNode.(map[string]interface{})
switch nextToken := nextToken.(type) {
case mapToken:
if _, ok := currentNode[currentToken.key]; !ok {
currentNode[currentToken.key] = make(map[string]interface{})
}
next := currentNode[currentToken.key].(map[string]interface{})
return next
case listToken:
if _, ok := currentNode[currentToken.key]; !ok {
currentNode[currentToken.key] = make([]interface{}, nextToken.position+1)
}
next := currentNode[currentToken.key].([]interface{})
if nextToken.position >= len(next) {
// Grow the slice and reassign it
newNext := make([]interface{}, nextToken.position+1)
copy(newNext, next)
next = newNext
currentNode[currentToken.key] = next
}
return next
default:
currentNode[currentToken.key] = value
}
case listToken:
currentNode := currentNode.([]interface{})
switch nextToken := nextToken.(type) {
case mapToken:
if currentNode[currentToken.position] == nil {
currentNode[currentToken.position] = make(map[string]interface{})
}
next := currentNode[currentToken.position].(map[string]interface{})
return next
case listToken:
if currentNode[currentToken.position] == nil {
currentNode[currentToken.position] = make([]interface{}, nextToken.position+1)
}
next := currentNode[currentToken.position].([]interface{})
if nextToken.position >= len(next) {
// Grow the slice and reassign it
newNext := make([]interface{}, nextToken.position+1)
copy(newNext, next)
next = newNext
currentNode[currentToken.position] = next
}
return next
default:
currentNode[currentToken.position] = value
}
}
return nil
}
// Unflatten unflattens a map flattened by Flatten
func Unflatten(in map[string]interface{}) map[string]interface{} {
newMap := make(map[string]interface{})
for k, v := range in {
var current interface{} = newMap
tokens := append(tokenize(k), nil)
for i := 0; i < len(tokens)-1; i++ {
current = unflatten(current, tokens[i], tokens[i+1], v)
}
}
return newMap
}