Esempio n. 1
0
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
}
Esempio n. 2
0
// 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
}