All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.fge.jsonpatch.JsonDiff Maven / Gradle / Ivy

There is a newer version: 1.9
Show newest version
package com.github.fge.jsonpatch;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.JacksonUtils;
import com.github.fge.jackson.JsonNumEquals;
import com.github.fge.jackson.NodeType;
import com.github.fge.jackson.jsonpointer.JsonPointer;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import java.util.List;
import java.util.Set;

/**
 * "Reverse" JSON Patch implementation
 *
 * 

This class only has one method, {@link #asJson(JsonNode, JsonNode)}, which * takes two JSON values as arguments and returns a patch as a {@link JsonNode}. * This generated patch can then be used in {@link * JsonPatch#fromJson(JsonNode)}.

* *

Numeric equivalence is respected. When dealing with container values (ie, * objects or arrays), operations are always generated in the following order: *

* *
    *
  • additions,
  • *
  • removals,
  • *
  • replacements.
  • *
* *

Note that due to the way {@link JsonNode} is implemented, this class is * inherently not thread safe (since {@code JsonNode} is mutable). It is * therefore the responsibility of the caller to ensure that the calling context * is safe (by ensuring, for instance, that only the diff operation has * references to the values to be diff'ed).

* *

Note also that currently, no effort is made to "factorize" operations. * That is, generating the patch for the following two nodes:

* *
    *
  • {@code { "a": "b" }} (first),
  • *
  • {@code { "c": "b" }} (second)
  • *
* *

will generate:

* *
 *     [
 *         { "op": "add": "path": "/c", "value": "b" },
 *         { "op": "remove", "path": "/a" }
 *     ]
 * 
* *

even though a shorter version would be:

* *
 *     [ { "op": "move": "from": "/a", "path": "/c" } ]
 * 
*/ public final class JsonDiff { private static final JsonNodeFactory FACTORY = JacksonUtils.nodeFactory(); private JsonDiff() { } /** * Generate a patch for transforming the first node into the second node * * @param first the node to be patched * @param second the expected result after applying the patch * @return the patch as a {@link JsonNode} */ public static JsonNode asJson(final JsonNode first, final JsonNode second) { final ArrayNode ret = FACTORY.arrayNode(); final List ops = Lists.newArrayList(); genDiff(ops, JsonPointer.empty(), first, second); ret.addAll(ops); return ret; } private static void genDiff(final List ops, final JsonPointer ptr, final JsonNode first, final JsonNode second) { if (JsonNumEquals.getInstance().equivalent(first, second)) return; final NodeType firstType = NodeType.getNodeType(first); final NodeType secondType = NodeType.getNodeType(second); /* * If types are different, this is a replace operation. * * If types are the same BUT nodes are not containers (ie, objects or * arrays), then this is also a replace operation. */ if (firstType != secondType || !first.isContainerNode()) { final ObjectNode op = createOp("replace", ptr); op.put("value", second.deepCopy()); ops.add(op); return; } /* * Otherwise, recurse into object members/array elements. */ if (firstType == NodeType.OBJECT) genObjectDiff(ops, ptr, first, second); else genArrayDiff(ops, ptr, first, second); } private static void genObjectDiff(final List ops, final JsonPointer ptr, final JsonNode first, final JsonNode second) { final Set firstKeys = Sets.newHashSet(first.fieldNames()); final Set secondKeys = Sets.newHashSet(second.fieldNames()); ObjectNode op; /* * Deal with added members */ final Set added = Sets.difference(secondKeys, firstKeys); for (final String fieldName: added) { op = createOp("add", ptr.append(fieldName)); op.put("value", second.get(fieldName).deepCopy()); ops.add(op); } /* * Deal with removed members */ final Set removed = Sets.difference(firstKeys, secondKeys); for (final String fieldName: removed) ops.add(createOp("remove", ptr.append(fieldName))); /* * Deal with modified members */ final Set inCommon = Sets.intersection(firstKeys, secondKeys); for (final String fieldName: inCommon) genDiff(ops, ptr.append(fieldName), first.get(fieldName), second.get(fieldName)); } private static void genArrayDiff(final List ops, final JsonPointer ptr, final JsonNode first, final JsonNode second) { final int firstSize = first.size(); final int secondSize = second.size(); ObjectNode op; /* * Deal with added elements */ for (int index = firstSize; index < secondSize; index++) { op = createOp("add", ptr.append("-")); op.put("value", second.get(index).deepCopy()); ops.add(op); } /* * Deal with removed elements */ for (int index = firstSize - 1; index >= secondSize; index--) ops.add(createOp("remove", ptr.append(index))); /* * Deal with modified elements */ final int size = Math.min(firstSize, secondSize); for (int index = 0; index < size; index++) genDiff(ops, ptr.append(index), first.get(index), second.get(index)); } private static ObjectNode createOp(final String name, final JsonPointer ptr) { final ObjectNode ret = FACTORY.objectNode(); ret.put("op", name); ret.put("path", ptr.toString()); return ret; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy