Skip to content

Commit cd2a819

Browse files
committed
Implement #790: external JsonNode comparator
1 parent 595c895 commit cd2a819

File tree

9 files changed

+141
-18
lines changed

9 files changed

+141
-18
lines changed

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Project: jackson-databind
3838
#781: Support handling of `@JsonProperty.required` for Creator methods
3939
#787: Add `ObjectMapper setFilterProvider(FilterProvider)` to allow chaining
4040
(suggested by rgoldberg@githin)
41+
#790: Add `JsonNode.equals(Comparator<JsonNode>, JsonNode)` to support
42+
configurable/external equality comparison
4143
- Remove old cglib compatibility tests; cause problems in Eclipse
4244

4345
2.5.4 (not yet released)

src/main/java/com/fasterxml/jackson/databind/JsonNode.java

+28
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,34 @@ public JsonNode withArray(String propertyName) {
897897
+getClass().getName()+"), can not call withArray() on it");
898898
}
899899

900+
/*
901+
/**********************************************************
902+
/* Public API, comparison
903+
/**********************************************************
904+
*/
905+
906+
/**
907+
* Entry method for invoking customizable comparison, using passed-in
908+
* {@link Comparator} object. Nodes will handle traversal of structured
909+
* types (arrays, objects), but defer to comparator for scalar value
910+
* comparisons. If a "natural" {@link Comparator} is passed -- one that
911+
* simply calls <code>equals()</code> on one of arguments, passing the other
912+
* -- implementation is the same as directly calling <code>equals()<code>
913+
* on node.
914+
*<p>
915+
* Default implementation simply delegates to passed in <code>comparator</code>,
916+
* with <code>this</code> as the first argument, and <code>other</code> as
917+
* the second argument.
918+
*
919+
* @param comparator Object called to compare two scalar {@link JsonNode}
920+
* instances, and return either 0 (are equals) or non-zero (not equal)
921+
*
922+
* @since 2.6
923+
*/
924+
public boolean equals(Comparator<JsonNode> comparator, JsonNode other) {
925+
return comparator.compare(this, other) == 0;
926+
}
927+
900928
/*
901929
/**********************************************************
902930
/* Overridden standard methods

src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,8 @@ public VisibilityChecker<?> getVisibilityChecker() {
11371137

11381138
/**
11391139
* @deprecated Since 2.6 use {@link #setVisibility(VisibilityChecker)} instead.
1140-
*/
1140+
*/
1141+
@Deprecated
11411142
public void setVisibilityChecker(VisibilityChecker<?> vc) {
11421143
setVisibility(vc);
11431144
}

src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java

+37-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.core.*;
44
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.JsonSerializable;
56
import com.fasterxml.jackson.databind.SerializerProvider;
67
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
78
import com.fasterxml.jackson.databind.util.RawValue;
@@ -10,6 +11,7 @@
1011
import java.math.BigDecimal;
1112
import java.util.ArrayList;
1213
import java.util.Collection;
14+
import java.util.Comparator;
1315
import java.util.Iterator;
1416
import java.util.List;
1517

@@ -98,30 +100,55 @@ public JsonNode path(int index) {
98100
}
99101
return MissingNode.getInstance();
100102
}
101-
103+
104+
@Override
105+
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
106+
{
107+
if (!(o instanceof ArrayNode)) {
108+
return false;
109+
}
110+
ArrayNode other = (ArrayNode) o;
111+
final int len = _children.size();
112+
if (other.size() != len) {
113+
return false;
114+
}
115+
List<JsonNode> l1 = _children;
116+
List<JsonNode> l2 = other._children;
117+
for (int i = 0; i < len; ++i) {
118+
if (comparator.compare(l1.get(i), l2.get(i)) != 0) {
119+
return false;
120+
}
121+
}
122+
return true;
123+
}
124+
102125
/*
103126
/**********************************************************
104127
/* Public API, serialization
105128
/**********************************************************
106129
*/
107130

108131
@Override
109-
public void serialize(JsonGenerator jg, SerializerProvider provider) throws IOException, JsonProcessingException
132+
public void serialize(JsonGenerator f, SerializerProvider provider) throws IOException
110133
{
111-
final List<JsonNode> c = _children;
112-
final int size = c.size();
113-
jg.writeStartArray(size);
134+
final List<JsonNode> c = _children;
135+
final int size = c.size();
136+
f.writeStartArray(size);
114137
for (int i = 0; i < size; ++i) { // we'll typically have array list
115-
// Can we trust that all nodes will always extend BaseJsonNode? Or if not,
116-
// at least implement JsonSerializable? Let's start with former, change if must
117-
((BaseJsonNode) c.get(i)).serialize(jg, provider);
138+
// For now, assuming it's either BaseJsonNode, JsonSerializable
139+
JsonNode n = c.get(i);
140+
if (n instanceof BaseJsonNode) {
141+
((BaseJsonNode) n).serialize(f, provider);
142+
} else {
143+
((JsonSerializable) n).serialize(f, provider);
144+
}
118145
}
119-
jg.writeEndArray();
146+
f.writeEndArray();
120147
}
121148

122149
@Override
123150
public void serializeWithType(JsonGenerator jg, SerializerProvider provider, TypeSerializer typeSer)
124-
throws IOException, JsonProcessingException
151+
throws IOException
125152
{
126153
typeSer.writeTypePrefixForArray(this, jg);
127154
for (JsonNode n : _children) {

src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.ArrayList;
1111
import java.util.Arrays;
1212
import java.util.Collection;
13+
import java.util.Comparator;
1314
import java.util.Iterator;
1415
import java.util.LinkedHashMap;
1516
import java.util.List;
@@ -163,7 +164,31 @@ public ArrayNode withArray(String propertyName)
163164
_children.put(propertyName, result);
164165
return result;
165166
}
166-
167+
168+
@Override
169+
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
170+
{
171+
if (!(o instanceof ObjectNode)) {
172+
return false;
173+
}
174+
ObjectNode other = (ObjectNode) o;
175+
Map<String, JsonNode> m1 = _children;
176+
Map<String, JsonNode> m2 = other._children;
177+
178+
final int len = m1.size();
179+
if (m2.size() != len) {
180+
return false;
181+
}
182+
183+
for (Map.Entry<String, JsonNode> entry : m1.entrySet()) {
184+
JsonNode v2 = m2.get(entry.getKey());
185+
if ((v2 == null) || comparator.compare(entry.getValue(), v2) != 0) {
186+
return false;
187+
}
188+
}
189+
return true;
190+
}
191+
167192
/*
168193
/**********************************************************
169194
/* Public API, finding value nodes

src/test/java/com/fasterxml/jackson/databind/filter/TestJsonFilter.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public void testSimpleInclusionFilter() throws Exception
7676

7777
// [JACKSON-504]: also verify it works via mapper
7878
ObjectMapper mapper = new ObjectMapper();
79-
mapper.setFilters(prov);
79+
mapper.setFilterProvider(prov);
8080
assertEquals("{\"a\":\"a\"}", mapper.writeValueAsString(new Bean()));
8181
}
8282

@@ -101,7 +101,7 @@ public void testMissingFilter() throws Exception
101101
// but when changing behavior, should work difference
102102
SimpleFilterProvider fp = new SimpleFilterProvider().setFailOnUnknownId(false);
103103
ObjectMapper mapper = new ObjectMapper();
104-
mapper.setFilters(fp);
104+
mapper.setFilterProvider(fp);
105105
String json = mapper.writeValueAsString(new Bean());
106106
assertEquals("{\"a\":\"a\",\"b\":\"b\"}", json);
107107
}

src/test/java/com/fasterxml/jackson/databind/introspect/TestAutoDetect.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public void testPrivateCtor() throws Exception
3535
// note: clumsy code, but needed for Eclipse/JDK1.5 compilation (not for 1.6)
3636
VisibilityChecker<?> vc = m.getVisibilityChecker();
3737
vc = vc.withCreatorVisibility(JsonAutoDetect.Visibility.PUBLIC_ONLY);
38-
m.setVisibilityChecker(vc);
38+
m.setVisibility(vc);
3939
try {
4040
m.readValue("\"abc\"", PrivateBean.class);
4141
fail("Expected exception for missing constructor");

src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java

+40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.fasterxml.jackson.databind.node;
22

3+
import java.util.Comparator;
4+
35
import com.fasterxml.jackson.core.*;
46
import com.fasterxml.jackson.core.io.SerializedString;
57
import com.fasterxml.jackson.databind.*;
@@ -120,4 +122,42 @@ public void testRawValue() throws Exception
120122

121123
assertEquals("{\"a\":[1, 2, 3]}", MAPPER.writeValueAsString(root));
122124
}
125+
126+
// [databind#790]
127+
public void testCustomComparators() throws Exception
128+
{
129+
ObjectNode root1 = MAPPER.createObjectNode();
130+
root1.put("value", 5);
131+
ObjectNode root2 = MAPPER.createObjectNode();
132+
root2.put("value", 5.0);
133+
134+
// default equals(): not strictly equal
135+
assertFalse(root1.equals(root2));
136+
assertFalse(root2.equals(root1));
137+
assertTrue(root1.equals(root1));
138+
assertTrue(root2.equals(root2));
139+
140+
// but. Custom comparator can make all the difference
141+
Comparator<JsonNode> cmp = new Comparator<JsonNode>() {
142+
143+
@Override
144+
public int compare(JsonNode o1, JsonNode o2) {
145+
if (o1.equals(o2)) {
146+
return 0;
147+
}
148+
if ((o1 instanceof NumericNode) && (o2 instanceof NumericNode)) {
149+
double d1 = ((NumericNode) o1).asDouble();
150+
double d2 = ((NumericNode) o2).asDouble();
151+
if (d1 == d2) { // strictly equals because it's integral value
152+
return 1;
153+
}
154+
}
155+
return 0;
156+
}
157+
};
158+
assertTrue(root1.equals(cmp, root2));
159+
assertTrue(root2.equals(cmp, root1));
160+
assertTrue(root1.equals(cmp, root1));
161+
assertTrue(root2.equals(cmp, root2));
162+
}
123163
}

src/test/java/com/fasterxml/jackson/databind/ser/TestAutoDetect.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public void testPrivateUsingGlobals() throws Exception
7474
ObjectMapper m = new ObjectMapper();
7575
VisibilityChecker<?> vc = m.getVisibilityChecker();
7676
vc = vc.withFieldVisibility(JsonAutoDetect.Visibility.ANY);
77-
m.setVisibilityChecker(vc);
77+
m.setVisibility(vc);
7878

7979
Map<String,Object> result = writeAndMap(m, new FieldBean());
8080
assertEquals(3, result.size());
@@ -85,7 +85,7 @@ public void testPrivateUsingGlobals() throws Exception
8585
m = new ObjectMapper();
8686
vc = m.getVisibilityChecker();
8787
vc = vc.withGetterVisibility(JsonAutoDetect.Visibility.ANY);
88-
m.setVisibilityChecker(vc);
88+
m.setVisibility(vc);
8989
result = writeAndMap(m, new MethodBean());
9090
assertEquals(3, result.size());
9191
assertEquals("a", result.get("a"));
@@ -99,7 +99,7 @@ public void testBasicSetup() throws Exception
9999
ObjectMapper m = new ObjectMapper();
100100
VisibilityChecker<?> vc = m.getVisibilityChecker();
101101
vc = vc.with(JsonAutoDetect.Visibility.ANY);
102-
m.setVisibilityChecker(vc);
102+
m.setVisibility(vc);
103103

104104
Map<String,Object> result = writeAndMap(m, new FieldBean());
105105
assertEquals(3, result.size());

0 commit comments

Comments
 (0)