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) }