forked from lilith44/gox
-
Notifications
You must be signed in to change notification settings - Fork 0
/
flatten.go
130 lines (106 loc) · 3.46 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
package gox
import (
"encoding/json"
"errors"
"regexp"
"strconv"
)
// The style of keys. If there is an input with two
// nested keys "f" and "g", with "f" at the root,
// { "f": { "g": ... } }
// the output will be the concatenation
// f{Middle}{Before}g{After}...
// Any struct element may be blank.
// If you use Middle, you will probably leave Before & After blank, and vice-versa.
// See examples in flatten_test.go and the "Default styles" here.
type SeparatorStyle struct {
Before string // Prepend to key
Middle string // Add between keys
After string // Append to key
}
// Default styles
var (
// Separate nested key components with dots, e.g. "a.b.1.c.d"
DotStyle = SeparatorStyle{Middle: "."}
// Separate with path-like slashes, e.g. a/b/1/c/d
PathStyle = SeparatorStyle{Middle: "/"}
// Separate ala Rails, e.g. "a[b][c][1][d]"
RailsStyle = SeparatorStyle{Before: "[", After: "]"}
// Separate with underscores, e.g. "a_b_1_c_d"
UnderscoreStyle = SeparatorStyle{Middle: "_"}
)
// 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[string]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
}
// JSON nested input must be a map
var NotValidJsonInputError = errors.New("Not a valid input, must be a map")
var isJsonMap = regexp.MustCompile(`^\s*\{`)
// FlattenString generates a flat JSON map from a nested one. 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 FlattenString(nestedStr, prefix string, style SeparatorStyle) (string, error) {
if !isJsonMap.MatchString(nestedStr) {
return "", NotValidJsonInputError
}
var nested map[string]interface{}
err := json.Unmarshal([]byte(nestedStr), &nested)
if err != nil {
return "", err
}
flatmap, err := Flatten(nested, prefix, style)
if err != nil {
return "", err
}
flatb, err := json.Marshal(&flatmap)
if err != nil {
return "", err
}
return string(flatb), 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[string]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[string]interface{}:
for k, v := range nested.(map[string]interface{}) {
newKey := enKey(top, prefix, k, 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 {
key += style.Before + style.Middle + subKey + style.After
}
return key
}