Skip to content

Commit fcc6cad

Browse files
committed
JsonDiff: add .asJson(), .asJsonPatch() methods
1 parent b0c691f commit fcc6cad

File tree

1 file changed

+112
-0
lines changed

1 file changed

+112
-0
lines changed

src/main/java/com/github/fge/jsonpatch/diff2/JsonDiff.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,144 @@
2222
import com.fasterxml.jackson.core.type.TypeReference;
2323
import com.fasterxml.jackson.databind.JsonNode;
2424
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import com.fasterxml.jackson.databind.node.ArrayNode;
2526
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
27+
import com.fasterxml.jackson.databind.node.ObjectNode;
2628
import com.github.fge.jackson.JacksonUtils;
2729
import com.github.fge.jackson.JsonNumEquals;
2830
import com.github.fge.jackson.NodeType;
2931
import com.github.fge.jackson.jsonpointer.JsonPointer;
32+
import com.github.fge.jsonpatch.JsonPatch;
3033
import com.github.fge.jsonpatch.MoveOperation;
3134
import com.google.common.annotations.VisibleForTesting;
3235
import com.google.common.base.Equivalence;
3336
import com.google.common.collect.ImmutableMap;
3437
import com.google.common.collect.Maps;
38+
import com.google.common.collect.Sets;
3539

3640
import javax.annotation.ParametersAreNonnullByDefault;
3741
import java.io.IOException;
3842
import java.util.Iterator;
3943
import java.util.Map;
44+
import java.util.Set;
4045

4146
@ParametersAreNonnullByDefault
4247
public final class JsonDiff
4348
{
49+
private static final ObjectMapper MAPPER = JacksonUtils.newMapper();
50+
4451
private static final Equivalence<JsonNode> EQUIVALENCE
4552
= JsonNumEquals.getInstance();
4653

4754
private JsonDiff()
4855
{
4956
}
5057

58+
public static JsonPatch asJsonPatch(final JsonNode first,
59+
final JsonNode second)
60+
{
61+
final Map<JsonPointer, JsonNode> unchanged
62+
= getUnchangedValues(first, second);
63+
final DiffProcessor processor = new DiffProcessor(unchanged);
64+
65+
generateDiffs(processor, JsonPointer.empty(), first, second);
66+
return processor.getPatch();
67+
}
68+
69+
public static JsonNode asJson(final JsonNode first, final JsonNode second)
70+
{
71+
final String s;
72+
try {
73+
s = MAPPER.writeValueAsString(asJsonPatch(first, second));
74+
return MAPPER.readTree(s);
75+
} catch (IOException e) {
76+
throw new RuntimeException("cannot generate JSON diff", e);
77+
}
78+
}
79+
80+
private static void generateDiffs(final DiffProcessor processor,
81+
final JsonPointer pointer, final JsonNode first, final JsonNode second)
82+
{
83+
if (EQUIVALENCE.equivalent(first, second))
84+
return;
85+
86+
final NodeType firstType = NodeType.getNodeType(first);
87+
final NodeType secondType = NodeType.getNodeType(second);
88+
89+
/*
90+
* Node types differ: generate a replacement operation.
91+
*/
92+
if (firstType != secondType) {
93+
processor.valueReplaced(pointer, first, second);
94+
return;
95+
}
96+
97+
/*
98+
* If we reach this point, it means that both nodes are the same type,
99+
* but are not equivalent.
100+
*
101+
* If this is not a container, generate a replace operation.
102+
*/
103+
if (!first.isContainerNode()) {
104+
processor.valueReplaced(pointer, first, second);
105+
return;
106+
}
107+
108+
/*
109+
* If we reach this point, both nodes are either objects or arrays;
110+
* delegate.
111+
*/
112+
if (firstType == NodeType.OBJECT)
113+
generateObjectDiffs(processor, pointer, (ObjectNode) first,
114+
(ObjectNode) second);
115+
else // array
116+
generateArrayDiffs(processor, pointer, (ArrayNode) first,
117+
(ArrayNode) second);
118+
}
119+
120+
private static void generateObjectDiffs(final DiffProcessor processor,
121+
final JsonPointer pointer, final ObjectNode first,
122+
final ObjectNode second)
123+
{
124+
final Set<String> firstFields = Sets.newHashSet(first.fieldNames());
125+
final Set<String> secondFields = Sets.newHashSet(second.fieldNames());
126+
127+
for (final String field: Sets.difference(firstFields, secondFields))
128+
processor.valueRemoved(pointer.append(field), first.get(field));
129+
130+
for (final String field: Sets.difference(secondFields, firstFields))
131+
processor.valueAdded(pointer.append(field), second.get(field));
132+
133+
for (final String field: Sets.intersection(firstFields, secondFields))
134+
generateDiffs(processor, pointer.append(field), first.get(field),
135+
second.get(field));
136+
}
137+
138+
private static void generateArrayDiffs(final DiffProcessor processor,
139+
final JsonPointer pointer, final ArrayNode first,
140+
final ArrayNode second)
141+
{
142+
final int firstSize = first.size();
143+
final int secondSize = second.size();
144+
final int size = Math.min(firstSize, secondSize);
145+
146+
for (int index = 0; index < size; index++)
147+
generateDiffs(processor, pointer.append(index), first.get(index),
148+
second.get(index));
149+
150+
/*
151+
* Source array is larger; in this case, elements are removed from the
152+
* target; the index of removal is always the original arrays's length.
153+
*/
154+
for (int index = size; index < firstSize; index++)
155+
processor.valueRemoved(pointer.append(size), first.get(index));
156+
157+
// Deal with the destination array being larger...
158+
for (int index = size; index < secondSize; index++)
159+
processor.valueAdded(pointer.append("-"), second.get(index));
160+
}
161+
162+
51163
@VisibleForTesting
52164
static Map<JsonPointer, JsonNode> getUnchangedValues(final JsonNode first,
53165
final JsonNode second)

0 commit comments

Comments
 (0)