public static JsonObject mergeJsonObjects(JsonObject source, JsonObject patch,
            FieldMetaDataProvider fieldDataProvider) throws HyscaleException {

        if (patch == null && source == null) {
            return null;
        }
        if (patch == null) {
            return source;
        }

        if (source == null) {
            return patch;
        }

        Map<String, JsonValue> effectiveMap = new HashMap<>();

        source.entrySet().stream().forEach(each -> effectiveMap.put(each.getKey(), each.getValue()));

        for (Entry<String, JsonValue> entrySet : patch.entrySet()) {
            String key = entrySet.getKey();
            JsonValue value = entrySet.getValue();
            if (value.getValueType() == ValueType.NULL) {
                // Empty/Null value field are ignored
                continue;
            }

            // if absent in source or different type than patch, put patch value
            JsonValue sourceJsonValue = source.get(key);
            if (sourceJsonValue == null || value.getValueType() != sourceJsonValue.getValueType()) {
                effectiveMap.put(key, value);
                continue;
            }

            if (replacementValueTypes.contains(value.getValueType())) {
                // if primitive, String or Boolean replace value in map
                effectiveMap.put(key, value);
            } else if (value.getValueType() == ValueType.ARRAY) {

                JsonArray patchArray = value.asJsonArray();
                JsonValue sourceJsonVal = effectiveMap.get(key);
                JsonArray effectiveArray = handleArrayMerge(sourceJsonVal, patchArray, key, fieldDataProvider);
                effectiveMap.put(key, effectiveArray);
            } else {
                effectiveMap.put(key,
                        mergeJsonObjects(sourceJsonValue.asJsonObject(), value.asJsonObject(), fieldDataProvider));
            }
        }
        JsonObjectBuilder objBuilder = Json.createObjectBuilder();
        effectiveMap.entrySet().stream().forEach(each -> objBuilder.add(each.getKey(), each.getValue()));
        return objBuilder.build();
    }
Creates new source map and recursively updates it with patch values
Returns the map as JsonObject

<p>
Implementation:
Updates patch values into source, for each value in patch
1. If null or empty, ignore (doesn't delete value in source)
2. If it is replacement value type, replace value in source
3. If arrays type
 3.a. If replacement value type elements, merge the values without duplication
 3.b. If merge strategy is replace, replace all items in the list
 3.c. Else merge based on key provided, throws exception if key is not available
4. For Others(JSONObject):
 4.a. If value in source is absent or replacement type, replace with patch value(Model change)
 4.b. Else Recursively call with JsonObject of source and patch
</p>

@param source
@param patch
@param fieldDataProvider - provides keys while merging array entities
@return merged JsonObject
@throws HyscaleException
commons/src/main/java/io/hyscale/commons/framework/patch/StrategicPatch.java