Mainflux.mainflux/vendor/github.com/apapsch/go-jsonmerge/v2/merge.go

168 lines
4.0 KiB
Go

package jsonmerge
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
)
// Merger describes result of merge operation and provides
// configuration.
type Merger 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{}
// CopyNonexistent enables setting fields into the result
// which only exist in the patch.
CopyNonexistent bool
}
func (m *Merger) 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)
m.Errors = append(m.Errors, err)
return value
}
return m.mergeObjects(value, patchValue, path)
}
if _, ok := value.([]interface{}); ok && patchValueIsObject {
return m.mergeObjects(value, patchValue, path)
}
if !reflect.DeepEqual(value, patchValue) {
m.Replaced[pathStr] = patchValue
}
return patchValue
}
func (m *Merger) 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] = m.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] = m.mergeValue(path, patchObject, k, v)
}
if m.CopyNonexistent {
for k, v := range patchObject {
if _, ok := dataObject[k]; !ok {
ret[k] = v
}
}
}
return ret
}
}
return data
}
// Merge merges patch document to data document
//
// Returning merged document. Result of merge operation can be
// obtained from the Merger. Result information is discarded before
// merging.
func (m *Merger) Merge(data, patch interface{}) interface{} {
m.Replaced = make(map[string]interface{})
m.Errors = make([]error, 0)
return m.mergeObjects(data, patch, nil)
}
// MergeBytesIndent merges patch document buffer to data document buffer
//
// Use prefix and indent for set indentation like in json.MarshalIndent
//
// Returning merged document buffer and error if any.
func (m *Merger) MergeBytesIndent(dataBuff, patchBuff []byte, prefix, indent string) (mergedBuff []byte, 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 = m.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 (m *Merger) MergeBytes(dataBuff, patchBuff []byte) (mergedBuff []byte, 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 = m.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)
}