Skip to content

Commit f93fd41

Browse files
committed
Last clean up, ready for merge back to 2.13
1 parent 9b4bf2f commit f93fd41

File tree

3 files changed

+114
-68
lines changed

3 files changed

+114
-68
lines changed

src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java

Lines changed: 63 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
/**
1414
* Deserializer that can build instances of {@link JsonNode} from any
1515
* JSON content, using appropriate {@link JsonNode} type.
16+
*<p>
17+
* Rewritten in Jackson 2.13 to avoid recursion and allow handling of
18+
* very deeply nested structures.
1619
*/
1720
@SuppressWarnings("serial")
1821
public class JsonNodeDeserializer
@@ -48,7 +51,7 @@ public static JsonDeserializer<? extends JsonNode> getDeserializer(Class<?> node
4851

4952
/*
5053
/**********************************************************************
51-
/* Actual deserializer implementations
54+
/* Actual deserialization method implementations
5255
/**********************************************************************
5356
*/
5457

@@ -69,16 +72,16 @@ public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IO
6972
final JsonNodeFactory nodeF = ctxt.getNodeFactory();
7073
switch (p.currentTokenId()) {
7174
case JsonTokenId.ID_START_OBJECT:
72-
return deserializeContainerNonRecursive(p, ctxt, nodeF, stack, nodeF.objectNode());
75+
return _deserializeContainerNoRecursion(p, ctxt, nodeF, stack, nodeF.objectNode());
7376
case JsonTokenId.ID_END_OBJECT:
7477
return nodeF.objectNode();
7578
case JsonTokenId.ID_START_ARRAY:
76-
return deserializeContainerNonRecursive(p, ctxt, nodeF, stack, nodeF.arrayNode());
79+
return _deserializeContainerNoRecursion(p, ctxt, nodeF, stack, nodeF.arrayNode());
7780
case JsonTokenId.ID_FIELD_NAME:
78-
return deserializeObjectAtName(p, ctxt, nodeF, stack);
81+
return _deserializeObjectAtName(p, ctxt, nodeF, stack);
7982
default:
8083
}
81-
return deserializeAnyScalar(p, ctxt);
84+
return _deserializeAnyScalar(p, ctxt);
8285
}
8386

8487
/*
@@ -87,6 +90,9 @@ public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IO
8790
/**********************************************************************
8891
*/
8992

93+
/**
94+
* Implementation used when declared type is specifically {@link ObjectNode}.
95+
*/
9096
final static class ObjectDeserializer
9197
extends BaseNodeDeserializer<ObjectNode>
9298
{
@@ -104,11 +110,11 @@ public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt) throws
104110
final JsonNodeFactory nodeF = ctxt.getNodeFactory();
105111
if (p.isExpectedStartObjectToken()) {
106112
final ObjectNode root = nodeF.objectNode();
107-
deserializeContainerNonRecursive(p, ctxt, nodeF, new ContainerStack(), root);
113+
_deserializeContainerNoRecursion(p, ctxt, nodeF, new ContainerStack(), root);
108114
return root;
109115
}
110116
if (p.hasToken(JsonToken.FIELD_NAME)) {
111-
return deserializeObjectAtName(p, ctxt, nodeF, new ContainerStack());
117+
return _deserializeObjectAtName(p, ctxt, nodeF, new ContainerStack());
112118
}
113119
// 23-Sep-2015, tatu: Ugh. We may also be given END_OBJECT (similar to FIELD_NAME),
114120
// if caller has advanced to the first token of Object, but for empty Object
@@ -135,6 +141,9 @@ public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt,
135141
}
136142
}
137143

144+
/**
145+
* Implementation used when declared type is specifically {@link ArrayNode}.
146+
*/
138147
final static class ArrayDeserializer
139148
extends BaseNodeDeserializer<ArrayNode>
140149
{
@@ -152,7 +161,7 @@ public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt) throws I
152161
if (p.isExpectedStartArrayToken()) {
153162
final JsonNodeFactory nodeF = ctxt.getNodeFactory();
154163
final ArrayNode arrayNode = nodeF.arrayNode();
155-
deserializeContainerNonRecursive(p, ctxt, nodeF,
164+
_deserializeContainerNoRecursion(p, ctxt, nodeF,
156165
new ContainerStack(), arrayNode);
157166
return arrayNode;
158167
}
@@ -167,7 +176,7 @@ public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt,
167176
ArrayNode arrayNode) throws IOException
168177
{
169178
if (p.isExpectedStartArrayToken()) {
170-
deserializeContainerNonRecursive(p, ctxt, ctxt.getNodeFactory(),
179+
_deserializeContainerNoRecursion(p, ctxt, ctxt.getNodeFactory(),
171180
new ContainerStack(), arrayNode);
172181
return arrayNode;
173182
}
@@ -177,8 +186,10 @@ public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt,
177186
}
178187

179188
/**
180-
* Base class for all actual {@link JsonNode} deserializer
181-
* implementations
189+
* Base class for all actual {@link JsonNode} deserializer implementations.
190+
*<p>
191+
* Starting with Jackson 2.13 uses iteration instead of recursion: this allows
192+
* handling of very deeply nested input structures.
182193
*/
183194
@SuppressWarnings("serial")
184195
abstract class BaseNodeDeserializer<T extends JsonNode>
@@ -275,10 +286,8 @@ protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt,
275286
/**
276287
* Alternate deserialization method used when parser already points to first
277288
* FIELD_NAME and not START_OBJECT.
278-
*
279-
* @since 2.9
280289
*/
281-
protected final ObjectNode deserializeObjectAtName(JsonParser p, DeserializationContext ctxt,
290+
protected final ObjectNode _deserializeObjectAtName(JsonParser p, DeserializationContext ctxt,
282291
final JsonNodeFactory nodeFactory, final ContainerStack stack) throws IOException
283292
{
284293
final ObjectNode node = nodeFactory.objectNode();
@@ -291,15 +300,15 @@ protected final ObjectNode deserializeObjectAtName(JsonParser p, Deserialization
291300
}
292301
switch (t.id()) {
293302
case JsonTokenId.ID_START_OBJECT:
294-
value = deserializeContainerNonRecursive(p, ctxt, nodeFactory,
303+
value = _deserializeContainerNoRecursion(p, ctxt, nodeFactory,
295304
stack, nodeFactory.objectNode());
296305
break;
297306
case JsonTokenId.ID_START_ARRAY:
298-
value = deserializeContainerNonRecursive(p, ctxt, nodeFactory,
307+
value = _deserializeContainerNoRecursion(p, ctxt, nodeFactory,
299308
stack, nodeFactory.arrayNode());
300309
break;
301310
default:
302-
value = deserializeAnyScalar(p, ctxt);
311+
value = _deserializeAnyScalar(p, ctxt);
303312
}
304313
JsonNode old = node.replace(key, value);
305314
if (old != null) {
@@ -352,7 +361,7 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
352361
if (t == JsonToken.START_ARRAY) {
353362
// 28-Mar-2021, tatu: We'll only append entries so not very different
354363
// from "regular" deserializeArray...
355-
deserializeContainerNonRecursive(p, ctxt, nodeFactory,
364+
_deserializeContainerNoRecursion(p, ctxt, nodeFactory,
356365
stack, (ArrayNode) old);
357366
continue;
358367
}
@@ -364,11 +373,11 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
364373
JsonNode value;
365374
switch (t.id()) {
366375
case JsonTokenId.ID_START_OBJECT:
367-
value = deserializeContainerNonRecursive(p, ctxt, nodeFactory,
376+
value = _deserializeContainerNoRecursion(p, ctxt, nodeFactory,
368377
stack, nodeFactory.objectNode());
369378
break;
370379
case JsonTokenId.ID_START_ARRAY:
371-
value = deserializeContainerNonRecursive(p, ctxt, nodeFactory,
380+
value = _deserializeContainerNoRecursion(p, ctxt, nodeFactory,
372381
stack, nodeFactory.arrayNode());
373382
break;
374383
case JsonTokenId.ID_STRING:
@@ -387,7 +396,7 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
387396
value = nodeFactory.nullNode();
388397
break;
389398
default:
390-
value = deserializeRareScalar(p, ctxt);
399+
value = _deserializeRareScalar(p, ctxt);
391400
}
392401
// 15-Feb-2021, tatu: I don't think this should have been called
393402
// on update case (was until 2.12.2) and was simply result of
@@ -405,20 +414,20 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
405414

406415
// Non-recursive alternative, used beyond certain nesting level
407416
// @since 2.13.0
408-
protected final ContainerNode<?> deserializeContainerNonRecursive(JsonParser p, DeserializationContext ctxt,
409-
JsonNodeFactory nodeFactory, ContainerStack stack, ContainerNode<?> root)
417+
protected final ContainerNode<?> _deserializeContainerNoRecursion(JsonParser p, DeserializationContext ctxt,
418+
JsonNodeFactory nodeFactory, ContainerStack stack, final ContainerNode<?> root)
410419
throws IOException
411420
{
412421
ContainerNode<?> curr = root;
413422
final int intCoercionFeats = ctxt.getDeserializationFeatures() & F_MASK_INT_COERCIONS;
414423

415424
outer_loop:
416-
while (true) {
417-
// if (curr.isObject()) {
425+
do {
418426
if (curr instanceof ObjectNode) {
419-
final ObjectNode currObject = (ObjectNode) curr;
427+
ObjectNode currObject = (ObjectNode) curr;
420428
String propName = p.nextFieldName();
421429

430+
objectLoop:
422431
for (; propName != null; propName = p.nextFieldName()) {
423432
JsonNode value;
424433
JsonToken t = p.nextToken();
@@ -435,9 +444,10 @@ protected final ContainerNode<?> deserializeContainerNonRecursive(JsonParser p,
435444
propName, currObject, old, newOb);
436445
}
437446
stack.push(curr);
438-
curr = newOb;
447+
curr = currObject = newOb;
448+
// We can actually take a short-cut with nested Objects...
449+
continue objectLoop;
439450
}
440-
continue outer_loop;
441451
case JsonTokenId.ID_START_ARRAY:
442452
{
443453
ArrayNode newOb = nodeFactory.arrayNode();
@@ -469,16 +479,17 @@ protected final ContainerNode<?> deserializeContainerNonRecursive(JsonParser p,
469479
value = nodeFactory.nullNode();
470480
break;
471481
default:
472-
value = deserializeRareScalar(p, ctxt);
482+
value = _deserializeRareScalar(p, ctxt);
473483
}
474484
JsonNode old = currObject.replace(propName, value);
475485
if (old != null) {
476486
_handleDuplicateField(p, ctxt, nodeFactory,
477487
propName, currObject, old, value);
478488
}
479489
}
480-
// reached not-property-name, should be END_OBJECT
490+
// reached not-property-name, should be END_OBJECT (verify?)
481491
} else {
492+
// Otherwise we must have an array
482493
final ArrayNode currArray = (ArrayNode) curr;
483494

484495
arrayLoop:
@@ -519,22 +530,21 @@ protected final ContainerNode<?> deserializeContainerNonRecursive(JsonParser p,
519530
currArray.add(nodeFactory.nullNode());
520531
continue arrayLoop;
521532
default:
522-
currArray.add(deserializeRareScalar(p, ctxt));
533+
currArray.add(_deserializeRareScalar(p, ctxt));
523534
continue arrayLoop;
524535
}
525536
}
526537
// Reached end of array (or input), so...
527538
}
528-
if (stack.isEmpty()) {
529-
return root;
530-
}
531-
curr = stack.pop();
532-
}
533-
}
534539

540+
// Either way, Object or Array ended, return up nesting level:
541+
curr = stack.popOrNull();
542+
} while (curr != null);
543+
return root;
544+
}
535545

536546
// Was called "deserializeAny()" in 2.12 and prior
537-
protected final JsonNode deserializeAnyScalar(JsonParser p, DeserializationContext ctxt)
547+
protected final JsonNode _deserializeAnyScalar(JsonParser p, DeserializationContext ctxt)
538548
throws IOException
539549
{
540550
final JsonNodeFactory nodeF = ctxt.getNodeFactory();
@@ -562,7 +572,7 @@ protected final JsonNode deserializeAnyScalar(JsonParser p, DeserializationConte
562572
return (JsonNode) ctxt.handleUnexpectedToken(handledType(), p);
563573
}
564574

565-
protected final JsonNode deserializeRareScalar(JsonParser p, DeserializationContext ctxt)
575+
protected final JsonNode _deserializeRareScalar(JsonParser p, DeserializationContext ctxt)
566576
throws IOException
567577
{
568578
// 28-Mar-2021, tatu: Only things that caller does not check
@@ -686,29 +696,33 @@ protected final JsonNode _fromEmbedded(JsonParser p, DeserializationContext ctxt
686696
final static class ContainerStack
687697
{
688698
private ContainerNode[] _stack;
689-
private int _top;
699+
private int _top, _end;
690700

691701
public ContainerStack() { }
692702

693-
public boolean isEmpty() { return _top == 0; }
694-
695703
// Not used yet but useful for limits (fail at [some high depth])
696704
public int size() { return _top; }
697705

698-
public void push(ContainerNode node) {
706+
public void push(ContainerNode node)
707+
{
708+
if (_top < _end) {
709+
_stack[_top++] = node;
710+
return;
711+
}
699712
if (_stack == null) {
700-
_stack = new ContainerNode[10];
701-
} else if (_stack.length == _top) {
713+
_end = 10;
714+
_stack = new ContainerNode[_end];
715+
} else {
702716
// grow by 50%, for most part
703-
final int newSize = _top + Math.min(512, Math.max(10, _top>>1));
704-
_stack = Arrays.copyOf(_stack, newSize);
717+
_end += Math.min(4000, Math.max(20, _end>>1));
718+
_stack = Arrays.copyOf(_stack, _end);
705719
}
706720
_stack[_top++] = node;
707721
}
708722

709-
public ContainerNode pop() {
723+
public ContainerNode popOrNull() {
710724
if (_top == 0) {
711-
throw new IllegalStateException("ContainerStack empty");
725+
return null;
712726
}
713727
// note: could clean up stack but due to usage pattern, should not make
714728
// any difference -- all nodes joined during and after construction and
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.fasterxml.jackson.databind.deser.dos;
2+
3+
import com.fasterxml.jackson.databind.*;
4+
5+
// [databind#2816], wrt JsonNode
6+
public class DeepJsonNodeDeser2816Test extends BaseMapTest
7+
{
8+
// 28-Mar-2021, tatu: Used to fail at 5000 for tree/object,
9+
// 8000 for tree/array, before work on iterative JsonNode deserializer
10+
// ... currently gets a bit slow at 1M but passes
11+
// private final static int TOO_DEEP_NESTING = 1_000_000;
12+
private final static int TOO_DEEP_NESTING = 9999;
13+
14+
private final ObjectMapper MAPPER = newJsonMapper();
15+
16+
public void testTreeWithArray() throws Exception
17+
{
18+
final String doc = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ");
19+
JsonNode n = MAPPER.readTree(doc);
20+
assertTrue(n.isArray());
21+
}
22+
23+
public void testTreeWithObject() throws Exception
24+
{
25+
final String doc = "{"+_nestedDoc(TOO_DEEP_NESTING, "\"x\":{", "} ") + "}";
26+
JsonNode n = MAPPER.readTree(doc);
27+
assertTrue(n.isObject());
28+
}
29+
30+
private String _nestedDoc(int nesting, String open, String close) {
31+
StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
32+
for (int i = 0; i < nesting; ++i) {
33+
sb.append(open);
34+
if ((i & 31) == 0) {
35+
sb.append("\n");
36+
}
37+
}
38+
for (int i = 0; i < nesting; ++i) {
39+
sb.append(close);
40+
if ((i & 31) == 0) {
41+
sb.append("\n");
42+
}
43+
}
44+
return sb.toString();
45+
}
46+
}

src/test/java/com/fasterxml/jackson/failing/DeepNestingDeser2816Test.java renamed to src/test/java/com/fasterxml/jackson/failing/DeepNestingUntypedDeser2816Test.java

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
import com.fasterxml.jackson.databind.*;
77

8-
// [databind#2816]
9-
public class DeepNestingDeser2816Test extends BaseMapTest
8+
// [databind#2816] wrt "untyped" (Maps, Lists)
9+
public class DeepNestingUntypedDeser2816Test extends BaseMapTest
1010
{
11-
// 2000 passes for all; 3000 fails for untyped, 5000 for tree/object,
12-
// 8000 for tree/array too
13-
private final static int TOO_DEEP_NESTING = 8000;
11+
// 28-Mar-2021, tatu: Currently 3000 fails for untyped/Object,
12+
// 4000 for untyped/Array
13+
private final static int TOO_DEEP_NESTING = 4000;
1414

1515
private final ObjectMapper MAPPER = newJsonMapper();
1616

@@ -28,20 +28,6 @@ public void testUntypedWithObject() throws Exception
2828
assertTrue(ob instanceof Map<?,?>);
2929
}
3030

31-
public void testTreeWithArray() throws Exception
32-
{
33-
final String doc = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ");
34-
JsonNode n = MAPPER.readTree(doc);
35-
assertTrue(n.isArray());
36-
}
37-
38-
public void testTreeWithObject() throws Exception
39-
{
40-
final String doc = "{"+_nestedDoc(TOO_DEEP_NESTING, "\"x\":{", "} ") + "}";
41-
JsonNode n = MAPPER.readTree(doc);
42-
assertTrue(n.isObject());
43-
}
44-
4531
private String _nestedDoc(int nesting, String open, String close) {
4632
StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
4733
for (int i = 0; i < nesting; ++i) {

0 commit comments

Comments
 (0)