Skip to content

Commit

Permalink
Fix #1406
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Oct 28, 2016
1 parent bb05a88 commit b8b0e72
Show file tree
Hide file tree
Showing 13 changed files with 608 additions and 575 deletions.
4 changes: 4 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -577,3 +577,7 @@ Jan Lolling (jlolling@github)
Michael R Fairhurst (MichaelRFairhurst@github)
* Reported #1035: `@JsonAnySetter` assumes key of `String`, does not consider declared type.
(2.9.0)

Fabrizio Cucci (fabriziocucci@github)
* Reported #1406: `ObjectMapper.readTree()` methods do not return `null` on end-of-input
(2.9.0)
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Project: jackson-databind
#1371: Add `MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES` to allow
disabling use of `@CreatorProperties` as explicit `@JsonCreator` equivalent
#1399: Add support for `@JsonSetter(merge=OptBoolean.TRUE`) to allow "deep update"
#1406: `ObjectMapper.readTree()` methods do not return `null` on end-of-input
(reported by Fabrizio C)

2.8.5 (not yet released)

Expand Down
79 changes: 51 additions & 28 deletions src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2352,11 +2352,9 @@ public <T> MappingIterator<T> readValues(JsonParser p, TypeReference<?> valueTyp
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
public JsonNode readTree(InputStream in)
throws IOException, JsonProcessingException
public JsonNode readTree(InputStream in) throws IOException
{
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(in), JSON_NODE_TYPE);
return (n == null) ? NullNode.instance : n;
return _readTreeAndClose(_jsonFactory.createParser(in));
}

/**
Expand All @@ -2382,11 +2380,8 @@ public JsonNode readTree(InputStream in)
* as a non-null {@link JsonNode} (one that returns <code>true</code>
* for {@link JsonNode#isNull()}
*/
public JsonNode readTree(Reader r)
throws IOException, JsonProcessingException
{
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(r), JSON_NODE_TYPE);
return (n == null) ? NullNode.instance : n;
public JsonNode readTree(Reader r) throws IOException {
return _readTreeAndClose(_jsonFactory.createParser(r));
}

/**
Expand All @@ -2412,11 +2407,8 @@ public JsonNode readTree(Reader r)
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
public JsonNode readTree(String content)
throws IOException, JsonProcessingException
{
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(content), JSON_NODE_TYPE);
return (n == null) ? NullNode.instance : n;
public JsonNode readTree(String content) throws IOException {
return _readTreeAndClose(_jsonFactory.createParser(content));
}

/**
Expand All @@ -2435,11 +2427,8 @@ public JsonNode readTree(String content)
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
public JsonNode readTree(byte[] content)
throws IOException, JsonProcessingException
{
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(content), JSON_NODE_TYPE);
return (n == null) ? NullNode.instance : n;
public JsonNode readTree(byte[] content) throws IOException {
return _readTreeAndClose(_jsonFactory.createParser(content));
}

/**
Expand All @@ -2465,8 +2454,7 @@ public JsonNode readTree(byte[] content)
public JsonNode readTree(File file)
throws IOException, JsonProcessingException
{
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(file), JSON_NODE_TYPE);
return (n == null) ? NullNode.instance : n;
return _readTreeAndClose(_jsonFactory.createParser(file));
}

/**
Expand All @@ -2489,11 +2477,8 @@ public JsonNode readTree(File file)
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
public JsonNode readTree(URL source)
throws IOException, JsonProcessingException
{
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(source), JSON_NODE_TYPE);
return (n == null) ? NullNode.instance : n;
public JsonNode readTree(URL source) throws IOException {
return _readTreeAndClose(_jsonFactory.createParser(source));
}

/*
Expand Down Expand Up @@ -3837,12 +3822,50 @@ protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
}
ctxt.checkUnresolvedObjectId();
}
// Need to consume the token too
p.clearCurrentToken();
return result;
}
}

/**
* Similar to {@link #_readMapAndClose} but specialized for <code>JsonNode</code>
* reading.
*
* @since 2.9
*/
protected JsonNode _readTreeAndClose(JsonParser p0) throws IOException
{
try (JsonParser p = p0) {
final JavaType valueType = JSON_NODE_TYPE;

// 27-Oct-2016, tatu: Need to inline `_initForReading()` due to
// special requirements by tree reading (no fail on eof)

_deserializationConfig.initialize(p); // since 2.5
JsonToken t = p.getCurrentToken();
if (t == null) {
t = p.nextToken();
if (t == null) { // [databind#1406]: expose end-of-input as `null`
return null;
}
}
if (t == JsonToken.VALUE_NULL) {
return _deserializationConfig.getNodeFactory().nullNode();
}
DeserializationConfig cfg = getDeserializationConfig();
DeserializationContext ctxt = createDeserializationContext(p, cfg);
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
Object result;
if (cfg.useRootWrapping()) {
result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
} else {
result = deser.deserialize(p, ctxt);
}
// No ObjectIds so can ignore
// ctxt.checkUnresolvedObjectId();
return (JsonNode) result;
}
}

/**
* Method called to ensure that given parser is ready for reading
* content for data binding.
Expand Down
64 changes: 38 additions & 26 deletions src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.databind.type.TypeFactory;
Expand All @@ -32,6 +31,12 @@
* Instances are initially constructed by {@link ObjectMapper} and can be
* reused, shared, cached; both because of thread-safety and because
* instances are relatively light-weight.
*<p>
* NOTE: this class is NOT meant as sub-classable (with Jackson 2.8 and
* above) by users. It is left as non-final mostly to allow frameworks
* that require bytecode generation for proxying and similar use cases,
* but there is no expecation that functionality should be extended
* by sub-classing.
*/
public class ObjectReader
extends ObjectCodec
Expand Down Expand Up @@ -333,8 +338,7 @@ protected <T> MappingIterator<T> _newIterator(JsonParser p, DeserializationConte

/*
/**********************************************************
/* Methods sub-classes may choose to override, if customized
/* initialization is needed.
/* Methods for initializing parser instance to use
/**********************************************************
*/

Expand Down Expand Up @@ -1160,7 +1164,7 @@ public <T extends TreeNode> T readTree(JsonParser p) throws IOException {
}

@Override
public void writeTree(JsonGenerator jgen, TreeNode rootNode) {
public void writeTree(JsonGenerator g, TreeNode rootNode) {
throw new UnsupportedOperationException();
}

Expand Down Expand Up @@ -1323,8 +1327,7 @@ public <T> T readValue(DataInput src) throws IOException
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
public JsonNode readTree(InputStream in)
throws IOException, JsonProcessingException
public JsonNode readTree(InputStream in) throws IOException
{
if (_dataFormatReaders != null) {
return _detectBindAndCloseAsTree(in);
Expand All @@ -1341,8 +1344,7 @@ public JsonNode readTree(InputStream in)
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
public JsonNode readTree(Reader r)
throws IOException, JsonProcessingException
public JsonNode readTree(Reader r) throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(r);
Expand All @@ -1359,8 +1361,7 @@ public JsonNode readTree(Reader r)
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
public JsonNode readTree(String json)
throws IOException, JsonProcessingException
public JsonNode readTree(String json) throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(json);
Expand Down Expand Up @@ -1635,32 +1636,43 @@ protected Object _bindAndClose(JsonParser p0) throws IOException
}
}

protected JsonNode _bindAndCloseAsTree(JsonParser p0) throws IOException {
protected final JsonNode _bindAndCloseAsTree(JsonParser p0) throws IOException {
try (JsonParser p = p0) {
return _bindAsTree(p);
}
}

protected JsonNode _bindAsTree(JsonParser p) throws IOException
protected final JsonNode _bindAsTree(JsonParser p) throws IOException
{
JsonNode result;
// 27-Oct-2016, tatu: Need to inline `_initForReading()` due to
// special requirements by tree reading (no fail on eof)

_config.initialize(p);
if (_schema != null) {
p.setSchema(_schema);
}

JsonToken t = p.getCurrentToken();
if (t == null) {
t = p.nextToken();
if (t == null) { // [databind#1406]: expose end-of-input as `null`
return null;
}
}
DeserializationContext ctxt = createDeserializationContext(p);
JsonToken t = _initForReading(ctxt, p);
if (t == JsonToken.VALUE_NULL || t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = NullNode.instance;
if (t == JsonToken.VALUE_NULL) {
return ctxt.getNodeFactory().nullNode();
}
JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
Object result;
if (_unwrapRoot) {
result = _unwrapAndDeserialize(p, ctxt, JSON_NODE_TYPE, deser);
} else {
JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
if (_unwrapRoot) {
result = (JsonNode) _unwrapAndDeserialize(p, ctxt, JSON_NODE_TYPE, deser);
} else {
result = (JsonNode) deser.deserialize(p, ctxt);
}
result = deser.deserialize(p, ctxt);
}
// Need to consume the token too
p.clearCurrentToken();
return result;
return (JsonNode) result;
}

/**
* @since 2.1
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/**
* Additional tests for {@link ArrayNode} container class.
*/
public class TestArrayNode
public class ArrayNodeTest
extends BaseMapTest
{
public void testBasics() throws IOException
Expand Down Expand Up @@ -94,6 +94,20 @@ public void testBasics() throws IOException
jg.close();
}

public void testArrayViaMapper() throws Exception
{
final String JSON = "[[[-0.027512,51.503221],[-0.008497,51.503221],[-0.008497,51.509744],[-0.027512,51.509744]]]";

JsonNode n = objectMapper().readTree(JSON);
assertNotNull(n);
assertTrue(n.isArray());
ArrayNode an = (ArrayNode) n;
assertEquals(1, an.size());
ArrayNode an2 = (ArrayNode) n.get(0);
assertTrue(an2.isArray());
assertEquals(4, an2.size());
}

public void testAdds()
{
ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
Expand Down Expand Up @@ -167,4 +181,74 @@ public void testParser() throws Exception
assertEquals(JsonParser.NumberType.INT, p.getNumberType());
p.close();
}

public void testArrayNodeEquality()
{
ArrayNode n1 = new ArrayNode(null);
ArrayNode n2 = new ArrayNode(null);

assertTrue(n1.equals(n2));
assertTrue(n2.equals(n1));

n1.add(TextNode.valueOf("Test"));

assertFalse(n1.equals(n2));
assertFalse(n2.equals(n1));

n2.add(TextNode.valueOf("Test"));

assertTrue(n1.equals(n2));
assertTrue(n2.equals(n1));
}

public void testSimpleArray() throws Exception
{
ArrayNode result = objectMapper().createArrayNode();

assertTrue(result.isArray());
assertType(result, ArrayNode.class);

assertFalse(result.isObject());
assertFalse(result.isNumber());
assertFalse(result.isNull());
assertFalse(result.isTextual());

// and let's add stuff...
result.add(false);
result.insertNull(0);

// should be equal to itself no matter what
assertEquals(result, result);
assertFalse(result.equals(null)); // but not to null

// plus see that we can access stuff
assertEquals(NullNode.instance, result.path(0));
assertEquals(NullNode.instance, result.get(0));
assertEquals(BooleanNode.FALSE, result.path(1));
assertEquals(BooleanNode.FALSE, result.get(1));
assertEquals(2, result.size());

assertNull(result.get(-1));
assertNull(result.get(2));
JsonNode missing = result.path(2);
assertTrue(missing.isMissingNode());
assertTrue(result.path(-100).isMissingNode());

// then construct and compare
ArrayNode array2 = objectMapper().createArrayNode();
array2.addNull();
array2.add(false);
assertEquals(result, array2);

// plus remove entries
JsonNode rm1 = array2.remove(0);
assertEquals(NullNode.instance, rm1);
assertEquals(1, array2.size());
assertEquals(BooleanNode.FALSE, array2.get(0));
assertFalse(result.equals(array2));

JsonNode rm2 = array2.remove(0);
assertEquals(BooleanNode.FALSE, rm2);
assertEquals(0, array2.size());
}
}
Loading

0 comments on commit b8b0e72

Please sign in to comment.