package org.gcube.portlets.user.geoportaldataentry.server.json; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * This class provides methods to merge two json of any nested level into a * single json. * * copied from: https://github.com/hemantsonu20/json-merge * * @maintainer updated by Francesco Mangiacrapa at ISTI-CNR * francesco.mangiacrapa@isti.cnr.it * * Apr 21, 2023 */ public class JsonMerge { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); /** * The Enum MERGE_OPTION. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Apr 21, 2023 */ public static enum MERGE_OPTION { MERGE, REPLACE } /** * Method to merge two json objects into single json object. * *

* It merges two json of any nested level into a single json following below * logic. *

* * *

Examples

*

Example 1

*

* Source Json *

* *
	 * {@code
	 * {
	 *   "name": "json-merge-src"
	 * }
	 * }
	 * 
*

* Target Json *

* *
	 * {@code
	 * {
	 *   "name": "json-merge-target"
	 * }
	 * }
	 * 
*

* Output *

* *
	 * {@code
	 * {
	 *   "name": "json-merge-src"
	 * }
	 * }
	 * 
* *

Example 2

*

* Source Json *

* *
	 * {@code
	 * {
	 *   "level1": {
	 *     "key1": "SrcValue1"
	 *   }
	 * }
	 * }
	 * 
*

* Target Json *

* *
	 * {@code
	 * {
	 *   "level1": {
	 *     "key1": "targetValue1",
	 *     "level2": {
	 *       "key2": "value2"
	 *     }
	 *   }
	 * }
	 * }
	 * 
*

* Output *

* *
	 * {@code
	 * {
	 *   "level1": {
	 *     "key1": "SrcValue1",
	 *     "level2": {
	 *       "key2": "value2"
	 *     }
	 *   }
	 * }
	 * }
	 * 
* * @param srcJsonStr source json string * @param targetJsonStr target json string * @param option the option * @return merged json as a string */ public static String merge(String srcJsonStr, String targetJsonStr, MERGE_OPTION option) { try { if (option == null) option = MERGE_OPTION.MERGE; JsonNode srcNode = OBJECT_MAPPER.readTree(srcJsonStr); JsonNode targetNode = OBJECT_MAPPER.readTree(targetJsonStr); JsonNode result = merge(srcNode, targetNode, option); return OBJECT_MAPPER.writeValueAsString(result); } catch (IOException e) { throw new JsonMergeException("Unable to merge json", e); } } /** * Merge. * * @param srcNode the src node * @param targetNode the target node * @param option the option * @return the json node */ public static JsonNode merge(JsonNode srcNode, JsonNode targetNode, MERGE_OPTION option) { if (option == null) option = MERGE_OPTION.MERGE; // if both nodes are object node, merged object node is returned if (srcNode.isObject() && targetNode.isObject()) { return merge((ObjectNode) srcNode, (ObjectNode) targetNode, option); } // if both nodes are array node, merged array node is returned if (srcNode.isArray() && targetNode.isArray()) { return mergeArray((ArrayNode) srcNode, (ArrayNode) targetNode, option); } // special case when src node is null if (srcNode.isNull()) { return targetNode; } return srcNode; } /** * Merge. * * @param srcNode the src node * @param targetNode the target node * @param option the option * @return the object node */ public static ObjectNode merge(ObjectNode srcNode, ObjectNode targetNode, MERGE_OPTION option) { ObjectNode result = OBJECT_MAPPER.createObjectNode(); Iterator> srcItr = srcNode.fields(); while (srcItr.hasNext()) { Map.Entry entry = srcItr.next(); // check key in src json exists in target json or not at same level if (targetNode.has(entry.getKey())) { result.set(entry.getKey(), merge(entry.getValue(), targetNode.get(entry.getKey()), option)); } else { // if key in src json doesn't exist in target json, just copy the same in result result.set(entry.getKey(), entry.getValue()); } } // copy fields from target json into result which were missing in src json Iterator> targetItr = targetNode.fields(); while (targetItr.hasNext()) { Map.Entry entry = targetItr.next(); if (!result.has(entry.getKey())) { result.set(entry.getKey(), entry.getValue()); } } return result; } /** * Merge. * * @param srcNode the src node * @param targetNode the target node * @param option the option * @return the array node */ public static ArrayNode mergeArray(ArrayNode srcNode, ArrayNode targetNode, MERGE_OPTION option) { ArrayNode result = OBJECT_MAPPER.createArrayNode(); switch (option) { case REPLACE: //Replacing source json value as result return result.addAll(srcNode); //return result.addAll(srcNode).addAll(targetNode); default: return mergeSet(srcNode, targetNode); } } /** * Added by Francesco Mangiacrapa Merge set. * * @param srcNode the src node * @param targetNode the target node * @return the array node */ public static ArrayNode mergeSet(ArrayNode srcNode, ArrayNode targetNode) { ArrayNode result = OBJECT_MAPPER.createArrayNode(); HashSet set = new HashSet<>(); set = toHashSet(set, srcNode); set = toHashSet(set, targetNode); Iterator itr = set.iterator(); while (itr != null && itr.hasNext()) { JsonNode arrayValue = itr.next(); result.add(arrayValue); } return result; } /** * To hash set. * * @param set the set * @param srcNode the src node * @return the hash set */ public static HashSet toHashSet(HashSet set, ArrayNode srcNode) { if (srcNode != null) { Iterator itr = srcNode.elements(); while (itr != null && itr.hasNext()) { JsonNode arrayValue = itr.next(); set.add(arrayValue); } } return set; } }