func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) { newS := map[string]interface{}{} for k, v := range s { 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 == "merge" { var err error v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey) 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 the map contains "$patch: replace", don't merge it, just use the // patch map directly. Later on, can add a non-recursive replace that only // affects the map that the $patch is in. if v, ok := patch[specialKey]; ok { if v == "replace" { delete(patch, specialKey) return patch, nil } return nil, fmt.Errorf("unknown patch type found: %s", v) } // nil is an accepted value for original to simplify logic in other places. // If original is nil, create a map so if patch requires us to modify the // map, it'll work. 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. // First find the fieldPatchStrategy and fieldPatchMergeKey. fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k) if err != nil { return nil, err } originalType := reflect.TypeOf(original[k]) patchType := reflect.TypeOf(patchV) if originalType == patchType { if originalType.Kind() == reflect.Map && fieldPatchStrategy != "replace" { 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 == "merge" { 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 }