func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) { newS := map[string]interface{}{} for k, v := range s { if k != directiveMarker { fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k) if err != nil { return nil, err } // If v is a map or a merge slice, recurse. if typedV, ok := v.(map[string]interface{}); ok { var err error v, err = sortMergeListsByNameMap(typedV, fieldType) if err != nil { return nil, err } } else if typedV, ok := v.([]interface{}); ok { if fieldPatchStrategy == mergeDirective { var err error v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey, true) if err != nil { return nil, err } } } } newS[k] = v } return newS, nil }
// Merge fields from a patch map into the original map. Note: This may modify // both the original map and the patch because getting a deep copy of a map in // golang is highly non-trivial. func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[string]interface{}, error) { if v, ok := patch[directiveMarker]; ok { if v == replaceDirective { // If the patch contains "$patch: replace", don't merge it, just use the // patch directly. Later on, we can add a single level replace that only // affects the map that the $patch is in. delete(patch, directiveMarker) return patch, nil } if v == deleteDirective { // If the patch contains "$patch: delete", don't merge it, just return // an empty map. return map[string]interface{}{}, nil } return nil, fmt.Errorf(errBadPatchTypeFmt, v, patch) } // nil is an accepted value for original to simplify logic in other places. // If original is nil, replace it with an empty map and then apply the patch. if original == nil { original = map[string]interface{}{} } // Start merging the patch into the original. for k, patchV := range patch { // If the value of this key is null, delete the key if it exists in the // original. Otherwise, skip it. if patchV == nil { if _, ok := original[k]; ok { delete(original, k) } continue } _, ok := original[k] if !ok { // If it's not in the original document, just take the patch value. original[k] = patchV continue } // If the data type is a pointer, resolve the element. if t.Kind() == reflect.Ptr { t = t.Elem() } // If they're both maps or lists, recurse into the value. originalType := reflect.TypeOf(original[k]) patchType := reflect.TypeOf(patchV) if originalType == patchType { // First find the fieldPatchStrategy and fieldPatchMergeKey. fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k) if err != nil { return nil, err } if originalType.Kind() == reflect.Map && fieldPatchStrategy != replaceDirective { typedOriginal := original[k].(map[string]interface{}) typedPatch := patchV.(map[string]interface{}) var err error original[k], err = mergeMap(typedOriginal, typedPatch, fieldType) if err != nil { return nil, err } continue } if originalType.Kind() == reflect.Slice && fieldPatchStrategy == mergeDirective { elemType := fieldType.Elem() typedOriginal := original[k].([]interface{}) typedPatch := patchV.([]interface{}) var err error original[k], err = mergeSlice(typedOriginal, typedPatch, elemType, fieldPatchMergeKey) if err != nil { return nil, err } continue } } // If originalType and patchType are different OR the types are both // maps or slices but we're just supposed to replace them, just take // the value from patch. original[k] = patchV } return original, nil }
// Returns a (recursive) strategic merge patch that yields modified when applied to original. func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreChangesAndAdditions, ignoreDeletions bool) (map[string]interface{}, error) { patch := map[string]interface{}{} if t.Kind() == reflect.Ptr { t = t.Elem() } for key, modifiedValue := range modified { originalValue, ok := original[key] if !ok { // Key was added, so add to patch if !ignoreChangesAndAdditions { patch[key] = modifiedValue } continue } if key == directiveMarker { originalString, ok := originalValue.(string) if !ok { return nil, fmt.Errorf("invalid value for special key: %s", directiveMarker) } modifiedString, ok := modifiedValue.(string) if !ok { return nil, fmt.Errorf("invalid value for special key: %s", directiveMarker) } if modifiedString != originalString { patch[directiveMarker] = modifiedValue } continue } if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) { // Types have changed, so add to patch if !ignoreChangesAndAdditions { patch[key] = modifiedValue } continue } // Types are the same, so compare values switch originalValueTyped := originalValue.(type) { case map[string]interface{}: modifiedValueTyped := modifiedValue.(map[string]interface{}) fieldType, _, _, err := forkedjson.LookupPatchMetadata(t, key) if err != nil { return nil, err } patchValue, err := diffMaps(originalValueTyped, modifiedValueTyped, fieldType, ignoreChangesAndAdditions, ignoreDeletions) if err != nil { return nil, err } if len(patchValue) > 0 { patch[key] = patchValue } continue case []interface{}: modifiedValueTyped := modifiedValue.([]interface{}) fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, key) if err != nil { return nil, err } if fieldPatchStrategy == mergeDirective { patchValue, err := diffLists(originalValueTyped, modifiedValueTyped, fieldType.Elem(), fieldPatchMergeKey, ignoreChangesAndAdditions, ignoreDeletions) if err != nil { return nil, err } if len(patchValue) > 0 { patch[key] = patchValue } continue } } if !ignoreChangesAndAdditions { if !reflect.DeepEqual(originalValue, modifiedValue) { // Values are different, so add to patch patch[key] = modifiedValue } } } if !ignoreDeletions { // Add nils for deleted values for key := range original { _, found := modified[key] if !found { patch[key] = nil } } } return patch, nil }