Skip to content

Commit

Permalink
Implement #790: external JsonNode comparator
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed May 13, 2015
1 parent 595c895 commit cd2a819
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 18 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION
Expand Up @@ -38,6 +38,8 @@ Project: jackson-databind
#781: Support handling of `@JsonProperty.required` for Creator methods #781: Support handling of `@JsonProperty.required` for Creator methods
#787: Add `ObjectMapper setFilterProvider(FilterProvider)` to allow chaining #787: Add `ObjectMapper setFilterProvider(FilterProvider)` to allow chaining
(suggested by rgoldberg@githin) (suggested by rgoldberg@githin)
#790: Add `JsonNode.equals(Comparator<JsonNode>, JsonNode)` to support
configurable/external equality comparison
- Remove old cglib compatibility tests; cause problems in Eclipse - Remove old cglib compatibility tests; cause problems in Eclipse


2.5.4 (not yet released) 2.5.4 (not yet released)
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/JsonNode.java
Expand Up @@ -897,6 +897,34 @@ public JsonNode withArray(String propertyName) {
+getClass().getName()+"), can not call withArray() on it"); +getClass().getName()+"), can not call withArray() on it");
} }


/*
/**********************************************************
/* Public API, comparison
/**********************************************************
*/

/**
* Entry method for invoking customizable comparison, using passed-in
* {@link Comparator} object. Nodes will handle traversal of structured
* types (arrays, objects), but defer to comparator for scalar value
* comparisons. If a "natural" {@link Comparator} is passed -- one that
* simply calls <code>equals()</code> on one of arguments, passing the other
* -- implementation is the same as directly calling <code>equals()<code>
* on node.
*<p>
* Default implementation simply delegates to passed in <code>comparator</code>,
* with <code>this</code> as the first argument, and <code>other</code> as
* the second argument.
*
* @param comparator Object called to compare two scalar {@link JsonNode}
* instances, and return either 0 (are equals) or non-zero (not equal)
*
* @since 2.6
*/
public boolean equals(Comparator<JsonNode> comparator, JsonNode other) {
return comparator.compare(this, other) == 0;
}

/* /*
/********************************************************** /**********************************************************
/* Overridden standard methods /* Overridden standard methods
Expand Down
Expand Up @@ -1137,7 +1137,8 @@ public VisibilityChecker<?> getVisibilityChecker() {


/** /**
* @deprecated Since 2.6 use {@link #setVisibility(VisibilityChecker)} instead. * @deprecated Since 2.6 use {@link #setVisibility(VisibilityChecker)} instead.
*/ */
@Deprecated
public void setVisibilityChecker(VisibilityChecker<?> vc) { public void setVisibilityChecker(VisibilityChecker<?> vc) {
setVisibility(vc); setVisibility(vc);
} }
Expand Down
47 changes: 37 additions & 10 deletions src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java
Expand Up @@ -2,6 +2,7 @@


import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.util.RawValue; import com.fasterxml.jackson.databind.util.RawValue;
Expand All @@ -10,6 +11,7 @@
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;


Expand Down Expand Up @@ -98,30 +100,55 @@ public JsonNode path(int index) {
} }
return MissingNode.getInstance(); return MissingNode.getInstance();
} }


@Override
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
{
if (!(o instanceof ArrayNode)) {
return false;
}
ArrayNode other = (ArrayNode) o;
final int len = _children.size();
if (other.size() != len) {
return false;
}
List<JsonNode> l1 = _children;
List<JsonNode> l2 = other._children;
for (int i = 0; i < len; ++i) {
if (comparator.compare(l1.get(i), l2.get(i)) != 0) {
return false;
}
}
return true;
}

/* /*
/********************************************************** /**********************************************************
/* Public API, serialization /* Public API, serialization
/********************************************************** /**********************************************************
*/ */


@Override @Override
public void serialize(JsonGenerator jg, SerializerProvider provider) throws IOException, JsonProcessingException public void serialize(JsonGenerator f, SerializerProvider provider) throws IOException
{ {
final List<JsonNode> c = _children; final List<JsonNode> c = _children;
final int size = c.size(); final int size = c.size();
jg.writeStartArray(size); f.writeStartArray(size);
for (int i = 0; i < size; ++i) { // we'll typically have array list for (int i = 0; i < size; ++i) { // we'll typically have array list
// Can we trust that all nodes will always extend BaseJsonNode? Or if not, // For now, assuming it's either BaseJsonNode, JsonSerializable
// at least implement JsonSerializable? Let's start with former, change if must JsonNode n = c.get(i);
((BaseJsonNode) c.get(i)).serialize(jg, provider); if (n instanceof BaseJsonNode) {
((BaseJsonNode) n).serialize(f, provider);
} else {
((JsonSerializable) n).serialize(f, provider);
}
} }
jg.writeEndArray(); f.writeEndArray();
} }


@Override @Override
public void serializeWithType(JsonGenerator jg, SerializerProvider provider, TypeSerializer typeSer) public void serializeWithType(JsonGenerator jg, SerializerProvider provider, TypeSerializer typeSer)
throws IOException, JsonProcessingException throws IOException
{ {
typeSer.writeTypePrefixForArray(this, jg); typeSer.writeTypePrefixForArray(this, jg);
for (JsonNode n : _children) { for (JsonNode n : _children) {
Expand Down
Expand Up @@ -10,6 +10,7 @@
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
Expand Down Expand Up @@ -163,7 +164,31 @@ public ArrayNode withArray(String propertyName)
_children.put(propertyName, result); _children.put(propertyName, result);
return result; return result;
} }


@Override
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
{
if (!(o instanceof ObjectNode)) {
return false;
}
ObjectNode other = (ObjectNode) o;
Map<String, JsonNode> m1 = _children;
Map<String, JsonNode> m2 = other._children;

final int len = m1.size();
if (m2.size() != len) {
return false;
}

for (Map.Entry<String, JsonNode> entry : m1.entrySet()) {
JsonNode v2 = m2.get(entry.getKey());
if ((v2 == null) || comparator.compare(entry.getValue(), v2) != 0) {
return false;
}
}
return true;
}

/* /*
/********************************************************** /**********************************************************
/* Public API, finding value nodes /* Public API, finding value nodes
Expand Down
Expand Up @@ -76,7 +76,7 @@ public void testSimpleInclusionFilter() throws Exception


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


Expand All @@ -101,7 +101,7 @@ public void testMissingFilter() throws Exception
// but when changing behavior, should work difference // but when changing behavior, should work difference
SimpleFilterProvider fp = new SimpleFilterProvider().setFailOnUnknownId(false); SimpleFilterProvider fp = new SimpleFilterProvider().setFailOnUnknownId(false);
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(fp); mapper.setFilterProvider(fp);
String json = mapper.writeValueAsString(new Bean()); String json = mapper.writeValueAsString(new Bean());
assertEquals("{\"a\":\"a\",\"b\":\"b\"}", json); assertEquals("{\"a\":\"a\",\"b\":\"b\"}", json);
} }
Expand Down
Expand Up @@ -35,7 +35,7 @@ public void testPrivateCtor() throws Exception
// note: clumsy code, but needed for Eclipse/JDK1.5 compilation (not for 1.6) // note: clumsy code, but needed for Eclipse/JDK1.5 compilation (not for 1.6)
VisibilityChecker<?> vc = m.getVisibilityChecker(); VisibilityChecker<?> vc = m.getVisibilityChecker();
vc = vc.withCreatorVisibility(JsonAutoDetect.Visibility.PUBLIC_ONLY); vc = vc.withCreatorVisibility(JsonAutoDetect.Visibility.PUBLIC_ONLY);
m.setVisibilityChecker(vc); m.setVisibility(vc);
try { try {
m.readValue("\"abc\"", PrivateBean.class); m.readValue("\"abc\"", PrivateBean.class);
fail("Expected exception for missing constructor"); fail("Expected exception for missing constructor");
Expand Down
@@ -1,5 +1,7 @@
package com.fasterxml.jackson.databind.node; package com.fasterxml.jackson.databind.node;


import java.util.Comparator;

import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.SerializedString; import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.*;
Expand Down Expand Up @@ -120,4 +122,42 @@ public void testRawValue() throws Exception


assertEquals("{\"a\":[1, 2, 3]}", MAPPER.writeValueAsString(root)); assertEquals("{\"a\":[1, 2, 3]}", MAPPER.writeValueAsString(root));
} }

// [databind#790]
public void testCustomComparators() throws Exception
{
ObjectNode root1 = MAPPER.createObjectNode();
root1.put("value", 5);
ObjectNode root2 = MAPPER.createObjectNode();
root2.put("value", 5.0);

// default equals(): not strictly equal
assertFalse(root1.equals(root2));
assertFalse(root2.equals(root1));
assertTrue(root1.equals(root1));
assertTrue(root2.equals(root2));

// but. Custom comparator can make all the difference
Comparator<JsonNode> cmp = new Comparator<JsonNode>() {

@Override
public int compare(JsonNode o1, JsonNode o2) {
if (o1.equals(o2)) {
return 0;
}
if ((o1 instanceof NumericNode) && (o2 instanceof NumericNode)) {
double d1 = ((NumericNode) o1).asDouble();
double d2 = ((NumericNode) o2).asDouble();
if (d1 == d2) { // strictly equals because it's integral value
return 1;
}
}
return 0;
}
};
assertTrue(root1.equals(cmp, root2));
assertTrue(root2.equals(cmp, root1));
assertTrue(root1.equals(cmp, root1));
assertTrue(root2.equals(cmp, root2));
}
} }
Expand Up @@ -74,7 +74,7 @@ public void testPrivateUsingGlobals() throws Exception
ObjectMapper m = new ObjectMapper(); ObjectMapper m = new ObjectMapper();
VisibilityChecker<?> vc = m.getVisibilityChecker(); VisibilityChecker<?> vc = m.getVisibilityChecker();
vc = vc.withFieldVisibility(JsonAutoDetect.Visibility.ANY); vc = vc.withFieldVisibility(JsonAutoDetect.Visibility.ANY);
m.setVisibilityChecker(vc); m.setVisibility(vc);


Map<String,Object> result = writeAndMap(m, new FieldBean()); Map<String,Object> result = writeAndMap(m, new FieldBean());
assertEquals(3, result.size()); assertEquals(3, result.size());
Expand All @@ -85,7 +85,7 @@ public void testPrivateUsingGlobals() throws Exception
m = new ObjectMapper(); m = new ObjectMapper();
vc = m.getVisibilityChecker(); vc = m.getVisibilityChecker();
vc = vc.withGetterVisibility(JsonAutoDetect.Visibility.ANY); vc = vc.withGetterVisibility(JsonAutoDetect.Visibility.ANY);
m.setVisibilityChecker(vc); m.setVisibility(vc);
result = writeAndMap(m, new MethodBean()); result = writeAndMap(m, new MethodBean());
assertEquals(3, result.size()); assertEquals(3, result.size());
assertEquals("a", result.get("a")); assertEquals("a", result.get("a"));
Expand All @@ -99,7 +99,7 @@ public void testBasicSetup() throws Exception
ObjectMapper m = new ObjectMapper(); ObjectMapper m = new ObjectMapper();
VisibilityChecker<?> vc = m.getVisibilityChecker(); VisibilityChecker<?> vc = m.getVisibilityChecker();
vc = vc.with(JsonAutoDetect.Visibility.ANY); vc = vc.with(JsonAutoDetect.Visibility.ANY);
m.setVisibilityChecker(vc); m.setVisibility(vc);


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

0 comments on commit cd2a819

Please sign in to comment.