Skip to content

Commit b8b0e72

Browse files
committed
Fix #1406
1 parent bb05a88 commit b8b0e72

13 files changed

+608
-575
lines changed

release-notes/CREDITS

+4
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,7 @@ Jan Lolling (jlolling@github)
577577
Michael R Fairhurst (MichaelRFairhurst@github)
578578
* Reported #1035: `@JsonAnySetter` assumes key of `String`, does not consider declared type.
579579
(2.9.0)
580+
581+
Fabrizio Cucci (fabriziocucci@github)
582+
* Reported #1406: `ObjectMapper.readTree()` methods do not return `null` on end-of-input
583+
(2.9.0)

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Project: jackson-databind
2626
#1371: Add `MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES` to allow
2727
disabling use of `@CreatorProperties` as explicit `@JsonCreator` equivalent
2828
#1399: Add support for `@JsonSetter(merge=OptBoolean.TRUE`) to allow "deep update"
29+
#1406: `ObjectMapper.readTree()` methods do not return `null` on end-of-input
30+
(reported by Fabrizio C)
2931

3032
2.8.5 (not yet released)
3133

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

+51-28
Original file line numberDiff line numberDiff line change
@@ -2352,11 +2352,9 @@ public <T> MappingIterator<T> readValues(JsonParser p, TypeReference<?> valueTyp
23522352
* @throws JsonParseException if underlying input contains invalid content
23532353
* of type {@link JsonParser} supports (JSON for default case)
23542354
*/
2355-
public JsonNode readTree(InputStream in)
2356-
throws IOException, JsonProcessingException
2355+
public JsonNode readTree(InputStream in) throws IOException
23572356
{
2358-
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(in), JSON_NODE_TYPE);
2359-
return (n == null) ? NullNode.instance : n;
2357+
return _readTreeAndClose(_jsonFactory.createParser(in));
23602358
}
23612359

23622360
/**
@@ -2382,11 +2380,8 @@ public JsonNode readTree(InputStream in)
23822380
* as a non-null {@link JsonNode} (one that returns <code>true</code>
23832381
* for {@link JsonNode#isNull()}
23842382
*/
2385-
public JsonNode readTree(Reader r)
2386-
throws IOException, JsonProcessingException
2387-
{
2388-
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(r), JSON_NODE_TYPE);
2389-
return (n == null) ? NullNode.instance : n;
2383+
public JsonNode readTree(Reader r) throws IOException {
2384+
return _readTreeAndClose(_jsonFactory.createParser(r));
23902385
}
23912386

23922387
/**
@@ -2412,11 +2407,8 @@ public JsonNode readTree(Reader r)
24122407
* @throws JsonParseException if underlying input contains invalid content
24132408
* of type {@link JsonParser} supports (JSON for default case)
24142409
*/
2415-
public JsonNode readTree(String content)
2416-
throws IOException, JsonProcessingException
2417-
{
2418-
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(content), JSON_NODE_TYPE);
2419-
return (n == null) ? NullNode.instance : n;
2410+
public JsonNode readTree(String content) throws IOException {
2411+
return _readTreeAndClose(_jsonFactory.createParser(content));
24202412
}
24212413

24222414
/**
@@ -2435,11 +2427,8 @@ public JsonNode readTree(String content)
24352427
* @throws JsonParseException if underlying input contains invalid content
24362428
* of type {@link JsonParser} supports (JSON for default case)
24372429
*/
2438-
public JsonNode readTree(byte[] content)
2439-
throws IOException, JsonProcessingException
2440-
{
2441-
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(content), JSON_NODE_TYPE);
2442-
return (n == null) ? NullNode.instance : n;
2430+
public JsonNode readTree(byte[] content) throws IOException {
2431+
return _readTreeAndClose(_jsonFactory.createParser(content));
24432432
}
24442433

24452434
/**
@@ -2465,8 +2454,7 @@ public JsonNode readTree(byte[] content)
24652454
public JsonNode readTree(File file)
24662455
throws IOException, JsonProcessingException
24672456
{
2468-
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(file), JSON_NODE_TYPE);
2469-
return (n == null) ? NullNode.instance : n;
2457+
return _readTreeAndClose(_jsonFactory.createParser(file));
24702458
}
24712459

24722460
/**
@@ -2489,11 +2477,8 @@ public JsonNode readTree(File file)
24892477
* @throws JsonParseException if underlying input contains invalid content
24902478
* of type {@link JsonParser} supports (JSON for default case)
24912479
*/
2492-
public JsonNode readTree(URL source)
2493-
throws IOException, JsonProcessingException
2494-
{
2495-
JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(source), JSON_NODE_TYPE);
2496-
return (n == null) ? NullNode.instance : n;
2480+
public JsonNode readTree(URL source) throws IOException {
2481+
return _readTreeAndClose(_jsonFactory.createParser(source));
24972482
}
24982483

24992484
/*
@@ -3837,12 +3822,50 @@ protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
38373822
}
38383823
ctxt.checkUnresolvedObjectId();
38393824
}
3840-
// Need to consume the token too
3841-
p.clearCurrentToken();
38423825
return result;
38433826
}
38443827
}
38453828

3829+
/**
3830+
* Similar to {@link #_readMapAndClose} but specialized for <code>JsonNode</code>
3831+
* reading.
3832+
*
3833+
* @since 2.9
3834+
*/
3835+
protected JsonNode _readTreeAndClose(JsonParser p0) throws IOException
3836+
{
3837+
try (JsonParser p = p0) {
3838+
final JavaType valueType = JSON_NODE_TYPE;
3839+
3840+
// 27-Oct-2016, tatu: Need to inline `_initForReading()` due to
3841+
// special requirements by tree reading (no fail on eof)
3842+
3843+
_deserializationConfig.initialize(p); // since 2.5
3844+
JsonToken t = p.getCurrentToken();
3845+
if (t == null) {
3846+
t = p.nextToken();
3847+
if (t == null) { // [databind#1406]: expose end-of-input as `null`
3848+
return null;
3849+
}
3850+
}
3851+
if (t == JsonToken.VALUE_NULL) {
3852+
return _deserializationConfig.getNodeFactory().nullNode();
3853+
}
3854+
DeserializationConfig cfg = getDeserializationConfig();
3855+
DeserializationContext ctxt = createDeserializationContext(p, cfg);
3856+
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
3857+
Object result;
3858+
if (cfg.useRootWrapping()) {
3859+
result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
3860+
} else {
3861+
result = deser.deserialize(p, ctxt);
3862+
}
3863+
// No ObjectIds so can ignore
3864+
// ctxt.checkUnresolvedObjectId();
3865+
return (JsonNode) result;
3866+
}
3867+
}
3868+
38463869
/**
38473870
* Method called to ensure that given parser is ready for reading
38483871
* content for data binding.

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

+38-26
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
1717
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
1818
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
19-
import com.fasterxml.jackson.databind.node.NullNode;
2019
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
2120
import com.fasterxml.jackson.databind.type.SimpleType;
2221
import com.fasterxml.jackson.databind.type.TypeFactory;
@@ -32,6 +31,12 @@
3231
* Instances are initially constructed by {@link ObjectMapper} and can be
3332
* reused, shared, cached; both because of thread-safety and because
3433
* instances are relatively light-weight.
34+
*<p>
35+
* NOTE: this class is NOT meant as sub-classable (with Jackson 2.8 and
36+
* above) by users. It is left as non-final mostly to allow frameworks
37+
* that require bytecode generation for proxying and similar use cases,
38+
* but there is no expecation that functionality should be extended
39+
* by sub-classing.
3540
*/
3641
public class ObjectReader
3742
extends ObjectCodec
@@ -333,8 +338,7 @@ protected <T> MappingIterator<T> _newIterator(JsonParser p, DeserializationConte
333338

334339
/*
335340
/**********************************************************
336-
/* Methods sub-classes may choose to override, if customized
337-
/* initialization is needed.
341+
/* Methods for initializing parser instance to use
338342
/**********************************************************
339343
*/
340344

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

11621166
@Override
1163-
public void writeTree(JsonGenerator jgen, TreeNode rootNode) {
1167+
public void writeTree(JsonGenerator g, TreeNode rootNode) {
11641168
throw new UnsupportedOperationException();
11651169
}
11661170

@@ -1323,8 +1327,7 @@ public <T> T readValue(DataInput src) throws IOException
13231327
* it will just be ignored; result is always a newly constructed
13241328
* {@link JsonNode} instance.
13251329
*/
1326-
public JsonNode readTree(InputStream in)
1327-
throws IOException, JsonProcessingException
1330+
public JsonNode readTree(InputStream in) throws IOException
13281331
{
13291332
if (_dataFormatReaders != null) {
13301333
return _detectBindAndCloseAsTree(in);
@@ -1341,8 +1344,7 @@ public JsonNode readTree(InputStream in)
13411344
* it will just be ignored; result is always a newly constructed
13421345
* {@link JsonNode} instance.
13431346
*/
1344-
public JsonNode readTree(Reader r)
1345-
throws IOException, JsonProcessingException
1347+
public JsonNode readTree(Reader r) throws IOException
13461348
{
13471349
if (_dataFormatReaders != null) {
13481350
_reportUndetectableSource(r);
@@ -1359,8 +1361,7 @@ public JsonNode readTree(Reader r)
13591361
* it will just be ignored; result is always a newly constructed
13601362
* {@link JsonNode} instance.
13611363
*/
1362-
public JsonNode readTree(String json)
1363-
throws IOException, JsonProcessingException
1364+
public JsonNode readTree(String json) throws IOException
13641365
{
13651366
if (_dataFormatReaders != null) {
13661367
_reportUndetectableSource(json);
@@ -1635,32 +1636,43 @@ protected Object _bindAndClose(JsonParser p0) throws IOException
16351636
}
16361637
}
16371638

1638-
protected JsonNode _bindAndCloseAsTree(JsonParser p0) throws IOException {
1639+
protected final JsonNode _bindAndCloseAsTree(JsonParser p0) throws IOException {
16391640
try (JsonParser p = p0) {
16401641
return _bindAsTree(p);
16411642
}
16421643
}
16431644

1644-
protected JsonNode _bindAsTree(JsonParser p) throws IOException
1645+
protected final JsonNode _bindAsTree(JsonParser p) throws IOException
16451646
{
1646-
JsonNode result;
1647+
// 27-Oct-2016, tatu: Need to inline `_initForReading()` due to
1648+
// special requirements by tree reading (no fail on eof)
1649+
1650+
_config.initialize(p);
1651+
if (_schema != null) {
1652+
p.setSchema(_schema);
1653+
}
1654+
1655+
JsonToken t = p.getCurrentToken();
1656+
if (t == null) {
1657+
t = p.nextToken();
1658+
if (t == null) { // [databind#1406]: expose end-of-input as `null`
1659+
return null;
1660+
}
1661+
}
16471662
DeserializationContext ctxt = createDeserializationContext(p);
1648-
JsonToken t = _initForReading(ctxt, p);
1649-
if (t == JsonToken.VALUE_NULL || t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
1650-
result = NullNode.instance;
1663+
if (t == JsonToken.VALUE_NULL) {
1664+
return ctxt.getNodeFactory().nullNode();
1665+
}
1666+
JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
1667+
Object result;
1668+
if (_unwrapRoot) {
1669+
result = _unwrapAndDeserialize(p, ctxt, JSON_NODE_TYPE, deser);
16511670
} else {
1652-
JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
1653-
if (_unwrapRoot) {
1654-
result = (JsonNode) _unwrapAndDeserialize(p, ctxt, JSON_NODE_TYPE, deser);
1655-
} else {
1656-
result = (JsonNode) deser.deserialize(p, ctxt);
1657-
}
1671+
result = deser.deserialize(p, ctxt);
16581672
}
1659-
// Need to consume the token too
1660-
p.clearCurrentToken();
1661-
return result;
1673+
return (JsonNode) result;
16621674
}
1663-
1675+
16641676
/**
16651677
* @since 2.1
16661678
*/

src/test/java/com/fasterxml/jackson/databind/node/TestArrayNode.java renamed to src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java

+85-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/**
1717
* Additional tests for {@link ArrayNode} container class.
1818
*/
19-
public class TestArrayNode
19+
public class ArrayNodeTest
2020
extends BaseMapTest
2121
{
2222
public void testBasics() throws IOException
@@ -94,6 +94,20 @@ public void testBasics() throws IOException
9494
jg.close();
9595
}
9696

97+
public void testArrayViaMapper() throws Exception
98+
{
99+
final String JSON = "[[[-0.027512,51.503221],[-0.008497,51.503221],[-0.008497,51.509744],[-0.027512,51.509744]]]";
100+
101+
JsonNode n = objectMapper().readTree(JSON);
102+
assertNotNull(n);
103+
assertTrue(n.isArray());
104+
ArrayNode an = (ArrayNode) n;
105+
assertEquals(1, an.size());
106+
ArrayNode an2 = (ArrayNode) n.get(0);
107+
assertTrue(an2.isArray());
108+
assertEquals(4, an2.size());
109+
}
110+
97111
public void testAdds()
98112
{
99113
ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
@@ -167,4 +181,74 @@ public void testParser() throws Exception
167181
assertEquals(JsonParser.NumberType.INT, p.getNumberType());
168182
p.close();
169183
}
184+
185+
public void testArrayNodeEquality()
186+
{
187+
ArrayNode n1 = new ArrayNode(null);
188+
ArrayNode n2 = new ArrayNode(null);
189+
190+
assertTrue(n1.equals(n2));
191+
assertTrue(n2.equals(n1));
192+
193+
n1.add(TextNode.valueOf("Test"));
194+
195+
assertFalse(n1.equals(n2));
196+
assertFalse(n2.equals(n1));
197+
198+
n2.add(TextNode.valueOf("Test"));
199+
200+
assertTrue(n1.equals(n2));
201+
assertTrue(n2.equals(n1));
202+
}
203+
204+
public void testSimpleArray() throws Exception
205+
{
206+
ArrayNode result = objectMapper().createArrayNode();
207+
208+
assertTrue(result.isArray());
209+
assertType(result, ArrayNode.class);
210+
211+
assertFalse(result.isObject());
212+
assertFalse(result.isNumber());
213+
assertFalse(result.isNull());
214+
assertFalse(result.isTextual());
215+
216+
// and let's add stuff...
217+
result.add(false);
218+
result.insertNull(0);
219+
220+
// should be equal to itself no matter what
221+
assertEquals(result, result);
222+
assertFalse(result.equals(null)); // but not to null
223+
224+
// plus see that we can access stuff
225+
assertEquals(NullNode.instance, result.path(0));
226+
assertEquals(NullNode.instance, result.get(0));
227+
assertEquals(BooleanNode.FALSE, result.path(1));
228+
assertEquals(BooleanNode.FALSE, result.get(1));
229+
assertEquals(2, result.size());
230+
231+
assertNull(result.get(-1));
232+
assertNull(result.get(2));
233+
JsonNode missing = result.path(2);
234+
assertTrue(missing.isMissingNode());
235+
assertTrue(result.path(-100).isMissingNode());
236+
237+
// then construct and compare
238+
ArrayNode array2 = objectMapper().createArrayNode();
239+
array2.addNull();
240+
array2.add(false);
241+
assertEquals(result, array2);
242+
243+
// plus remove entries
244+
JsonNode rm1 = array2.remove(0);
245+
assertEquals(NullNode.instance, rm1);
246+
assertEquals(1, array2.size());
247+
assertEquals(BooleanNode.FALSE, array2.get(0));
248+
assertFalse(result.equals(array2));
249+
250+
JsonNode rm2 = array2.remove(0);
251+
assertEquals(BooleanNode.FALSE, rm2);
252+
assertEquals(0, array2.size());
253+
}
170254
}

0 commit comments

Comments
 (0)