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 }
func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType reflect.Type) (bool, error) { for key, leftValue := range typedLeft { if key != directiveMarker { if rightValue, ok := typedRight[key]; ok { fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(structType, key) if err != nil { return true, err } if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, fieldType, fieldPatchStrategy, fieldPatchMergeKey); hasConflicts { return true, err } } } } return false, nil }
// Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map. func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) { newS := map[string]interface{}{} for k, v := range s { if strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) { typedV, ok := v.([]interface{}) if !ok { return nil, errBadPatchFormatForPrimitiveList } v = uniqifyAndSortScalars(typedV) } else 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 }
func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType reflect.Type) (bool, error) { isForListOfPrimitives := false if leftDirective, ok := typedLeft[directiveMarker]; ok { if rightDirective, ok := typedRight[directiveMarker]; ok { if leftDirective == MergePrimitivesListDirective && rightDirective == rightDirective { isForListOfPrimitives = true } } } for key, leftValue := range typedLeft { if key != directiveMarker { if rightValue, ok := typedRight[key]; ok { var fieldType reflect.Type var fieldPatchStrategy, fieldPatchMergeKey string var err error if isForListOfPrimitives { fieldType = reflect.TypeOf(leftValue) fieldPatchStrategy = "" fieldPatchMergeKey = "" } else { fieldType, fieldPatchStrategy, fieldPatchMergeKey, err = forkedjson.LookupPatchMetadata(structType, key) if err != nil { return true, err } } if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, fieldType, fieldPatchStrategy, fieldPatchMergeKey); hasConflicts { return true, err } } } } return false, 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 } if v == MergePrimitivesListDirective { // delete the directiveMarker's key-value pair to avoid delta map and delete map // overlaping with each other when calculating a ThreeWayDiff for list of Primitives. // Otherwise, the overlaping will cause it calling LookupPatchMetadata() which will // return an error since the metadata shows it's a slice but it is actually a map. delete(original, directiveMarker) } else { 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) // check if we are trying to merge a slice with a map for list of primitives isMergeSliceOfPrimitivesWithAPatchMap := originalType != nil && patchType != nil && originalType.Kind() == reflect.Slice && patchType.Kind() == reflect.Map if originalType == patchType || isMergeSliceOfPrimitivesWithAPatchMap { // 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{}) var err error original[k], err = mergeSlice(typedOriginal, patchV, 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, smPatchVersion StrategicMergePatchVersion) (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, smPatchVersion) 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, smPatchVersion) if err != nil { return nil, err } if patchValue == nil { continue } switch typedPatchValue := patchValue.(type) { case []interface{}: if len(typedPatchValue) > 0 { patch[key] = typedPatchValue } case map[string]interface{}: if len(typedPatchValue) > 0 { patch[key] = typedPatchValue } default: return nil, fmt.Errorf("invalid type of patch: %v", reflect.TypeOf(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 }
// 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 } // The patch has a patch directive 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 { addList, deletionList, err := diffLists(originalValueTyped, modifiedValueTyped, fieldType.Elem(), fieldPatchMergeKey, ignoreChangesAndAdditions, ignoreDeletions) if err != nil { return nil, err } if len(addList) > 0 { patch[key] = addList } // generate a parallel list for deletion if len(deletionList) > 0 { parallelDeletionListKey := fmt.Sprintf("%s/%s", deleteFromPrimitiveListDirectivePrefix, key) patch[parallelDeletionListKey] = deletionList } 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 }