-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathflatten.go
156 lines (132 loc) · 4.93 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
// This Package Flattens Json with the specified key separators up to a specified depth
package gojsonflatten
import (
"encoding/json"
"errors"
"regexp"
"strconv"
)
// SeparatorStyle defines the style of keys when flattening nested structures.
// It provides options for adding prefixes, middle separators, and suffixes.
type SeparatorStyle struct {
Before string // Prepend to key
Middle string // Add between keys
After string // Append to key
}
// Default SeparatorStyles
var (
DotStyle = SeparatorStyle{Middle: "."}
PathStyle = SeparatorStyle{Middle: "/"}
RailsStyle = SeparatorStyle{Before: "[", After: "]"}
UnderscoreStyle = SeparatorStyle{Middle: "_"}
)
var (
ErrNotValidInput = errors.New("not a valid input: map or slice")
ErrNotValidJsonInput = errors.New("not a valid input, must be a map")
isJsonMap = regexp.MustCompile(`^\s*\{`)
)
// Flatten generates a flat map from a nested map with a specified depth.
func Flatten(nested map[string]interface{}, prefix string, style SeparatorStyle, depth int) (map[string]interface{}, error) {
return flattenInternal(nested, prefix, style, depth, false)
}
// FlattenString generates a flat JSON map from a nested JSON string with a specified depth.
func FlattenString(nestedString, prefix string, style SeparatorStyle, depth int) (string, error) {
return flattenStringInternal(nestedString, prefix, style, depth, false)
}
// FlattenNoArray generates a flat map from a nested map with a specified depth, preserving arrays as strings.
func FlattenNoArray(nested map[string]interface{}, prefix string, style SeparatorStyle, depth int) (map[string]interface{}, error) {
return flattenInternal(nested, prefix, style, depth, true)
}
// FlattenStringNoArray generates a flat JSON map from a nested JSON string with a specified depth, preserving arrays as strings.
func FlattenStringNoArray(nestedString, prefix string, style SeparatorStyle, depth int) (string, error) {
return flattenStringInternal(nestedString, prefix, style, depth, true)
}
// flattenInternal generates a flat map from a nested map with a specified depth, optionally preserving arrays as strings.
func flattenInternal(nested map[string]interface{}, prefix string, style SeparatorStyle, depth int, preserveArray bool) (map[string]interface{}, error) {
if depth == 0 {
return nested, nil
} else if depth > 0 {
depth++
}
flatmap := make(map[string]interface{})
err := flatten(true, flatmap, nested, prefix, style, depth, preserveArray)
if err != nil {
return nil, err
}
return flatmap, nil
}
// flattenStringInternal generates a flat JSON map from a nested JSON string with a specified depth, optionally preserving arrays as strings.
func flattenStringInternal(nestedString, prefix string, style SeparatorStyle, depth int, preserveArray bool) (string, error) {
if !isJsonMap.MatchString(nestedString) {
return "", ErrNotValidJsonInput
}
var nested map[string]interface{}
err := json.Unmarshal([]byte(nestedString), &nested)
if err != nil {
return "", err
}
flatmap, err := flattenInternal(nested, prefix, style, depth, preserveArray)
if err != nil {
return "", err
}
flatBytes, err := json.Marshal(&flatmap)
if err != nil {
return "", err
}
return string(flatBytes), nil
}
// flatten recursively processes nested structures and flattens them.
func flatten(top bool, flatMap map[string]interface{}, nested interface{}, prefix string, style SeparatorStyle, depth int, keepArrays bool) error {
if depth == 0 {
// If the desired depth is reached, add the prefix and nested value to the flat map.
flatMap[prefix] = nested
return nil
}
// Assign function is used to process and assign values to the flat map.
assign := func(newKey string, v interface{}) error {
switch v.(type) {
case map[string]interface{}, []interface{}:
// If the value is a nested map or slice, continue flattening recursively.
if err := flatten(false, flatMap, v, newKey, style, depth-1, keepArrays); err != nil {
return err
}
default:
// For scalar values, directly add them to the flat map.
flatMap[newKey] = v
}
return nil
}
switch nested := nested.(type) {
case map[string]interface{}:
for k, v := range nested {
newKey := enkey(top, prefix, k, style)
// Process and assign the key-value pair.
assign(newKey, v)
}
case []interface{}:
if !keepArrays {
for i, v := range nested {
newKey := enkey(top, prefix, strconv.Itoa(i), style)
// Process and assign the index-value pair.
assign(newKey, v)
}
} else {
flatMap[prefix] = nested
}
default:
return ErrNotValidInput
}
return nil
}
// enkey combines the prefix, subKey, and SeparatorStyle to form a new key.
func enkey(top bool, prefix, subKey string, style SeparatorStyle) string {
key := prefix
if top {
// If it's the top level, directly add the subKey.
key += subKey
} else {
// For nested levels, use the specified SeparatorStyle.
key += style.Before + style.Middle + subKey + style.After
}
return key
}