-
Notifications
You must be signed in to change notification settings - Fork 8
/
merge.go
157 lines (125 loc) · 3.71 KB
/
merge.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
package jsonmerge
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
)
// Info describes result of merge operation
type Info struct {
// Errors is slice of non-critical errors of merge operations
Errors []error
// Replaced is describe replacements
// Key is path in document like
// "prop1.prop2.prop3" for object properties or
// "arr1.1.prop" for arrays
// Value is value of replacemet
Replaced map[string]interface{}
}
func (info *Info) mergeValue(path []string, patch map[string]interface{}, key string, value interface{}) interface{} {
patchValue, patchHasValue := patch[key]
if !patchHasValue {
return value
}
_, patchValueIsObject := patchValue.(map[string]interface{})
path = append(path, key)
pathStr := strings.Join(path, ".")
if _, ok := value.(map[string]interface{}); ok {
if !patchValueIsObject {
err := fmt.Errorf("patch value must be object for key \"%v\"", pathStr)
info.Errors = append(info.Errors, err)
return value
}
return info.mergeObjects(value, patchValue, path)
}
if _, ok := value.([]interface{}); ok && patchValueIsObject {
return info.mergeObjects(value, patchValue, path)
}
if !reflect.DeepEqual(value, patchValue) {
info.Replaced[pathStr] = patchValue
}
return patchValue
}
func (info *Info) mergeObjects(data, patch interface{}, path []string) interface{} {
if patchObject, ok := patch.(map[string]interface{}); ok {
if dataArray, ok := data.([]interface{}); ok {
ret := make([]interface{}, len(dataArray))
for i, val := range dataArray {
ret[i] = info.mergeValue(path, patchObject, strconv.Itoa(i), val)
}
return ret
} else if dataObject, ok := data.(map[string]interface{}); ok {
ret := make(map[string]interface{})
for k, v := range dataObject {
ret[k] = info.mergeValue(path, patchObject, k, v)
}
return ret
}
}
return data
}
// Merge merges patch document to data document
//
// Returning merged document and merge info
func Merge(data, patch interface{}) (interface{}, *Info) {
info := &Info{
Replaced: make(map[string]interface{}),
}
ret := info.mergeObjects(data, patch, nil)
return ret, info
}
// MergeBytesIndent merges patch document buffer to data document buffer
//
// Use prefix and indent for set indentation like in json.MarshalIndent
//
// Returning merged document buffer, merge info and
// error if any
func MergeBytesIndent(dataBuff, patchBuff []byte, prefix, indent string) (mergedBuff []byte, info *Info, err error) {
var data, patch, merged interface{}
err = unmarshalJSON(dataBuff, &data)
if err != nil {
err = fmt.Errorf("error in data JSON: %v", err)
return
}
err = unmarshalJSON(patchBuff, &patch)
if err != nil {
err = fmt.Errorf("error in patch JSON: %v", err)
return
}
merged, info = Merge(data, patch)
mergedBuff, err = json.MarshalIndent(merged, prefix, indent)
if err != nil {
err = fmt.Errorf("error writing merged JSON: %v", err)
}
return
}
// MergeBytes merges patch document buffer to data document buffer
//
// Returning merged document buffer, merge info and
// error if any
func MergeBytes(dataBuff, patchBuff []byte) (mergedBuff []byte, info *Info, err error) {
var data, patch, merged interface{}
err = unmarshalJSON(dataBuff, &data)
if err != nil {
err = fmt.Errorf("error in data JSON: %v", err)
return
}
err = unmarshalJSON(patchBuff, &patch)
if err != nil {
err = fmt.Errorf("error in patch JSON: %v", err)
return
}
merged, info = Merge(data, patch)
mergedBuff, err = json.Marshal(merged)
if err != nil {
err = fmt.Errorf("error writing merged JSON: %v", err)
}
return
}
func unmarshalJSON(buff []byte, data interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(buff))
decoder.UseNumber()
return decoder.Decode(data)
}