13
13
/**
14
14
* Deserializer that can build instances of {@link JsonNode} from any
15
15
* 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.
16
19
*/
17
20
@ SuppressWarnings ("serial" )
18
21
public class JsonNodeDeserializer
@@ -48,7 +51,7 @@ public static JsonDeserializer<? extends JsonNode> getDeserializer(Class<?> node
48
51
49
52
/*
50
53
/**********************************************************************
51
- /* Actual deserializer implementations
54
+ /* Actual deserialization method implementations
52
55
/**********************************************************************
53
56
*/
54
57
@@ -69,16 +72,16 @@ public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IO
69
72
final JsonNodeFactory nodeF = ctxt .getNodeFactory ();
70
73
switch (p .currentTokenId ()) {
71
74
case JsonTokenId .ID_START_OBJECT :
72
- return deserializeContainerNonRecursive (p , ctxt , nodeF , stack , nodeF .objectNode ());
75
+ return _deserializeContainerNoRecursion (p , ctxt , nodeF , stack , nodeF .objectNode ());
73
76
case JsonTokenId .ID_END_OBJECT :
74
77
return nodeF .objectNode ();
75
78
case JsonTokenId .ID_START_ARRAY :
76
- return deserializeContainerNonRecursive (p , ctxt , nodeF , stack , nodeF .arrayNode ());
79
+ return _deserializeContainerNoRecursion (p , ctxt , nodeF , stack , nodeF .arrayNode ());
77
80
case JsonTokenId .ID_FIELD_NAME :
78
- return deserializeObjectAtName (p , ctxt , nodeF , stack );
81
+ return _deserializeObjectAtName (p , ctxt , nodeF , stack );
79
82
default :
80
83
}
81
- return deserializeAnyScalar (p , ctxt );
84
+ return _deserializeAnyScalar (p , ctxt );
82
85
}
83
86
84
87
/*
@@ -87,6 +90,9 @@ public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IO
87
90
/**********************************************************************
88
91
*/
89
92
93
+ /**
94
+ * Implementation used when declared type is specifically {@link ObjectNode}.
95
+ */
90
96
final static class ObjectDeserializer
91
97
extends BaseNodeDeserializer <ObjectNode >
92
98
{
@@ -104,11 +110,11 @@ public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt) throws
104
110
final JsonNodeFactory nodeF = ctxt .getNodeFactory ();
105
111
if (p .isExpectedStartObjectToken ()) {
106
112
final ObjectNode root = nodeF .objectNode ();
107
- deserializeContainerNonRecursive (p , ctxt , nodeF , new ContainerStack (), root );
113
+ _deserializeContainerNoRecursion (p , ctxt , nodeF , new ContainerStack (), root );
108
114
return root ;
109
115
}
110
116
if (p .hasToken (JsonToken .FIELD_NAME )) {
111
- return deserializeObjectAtName (p , ctxt , nodeF , new ContainerStack ());
117
+ return _deserializeObjectAtName (p , ctxt , nodeF , new ContainerStack ());
112
118
}
113
119
// 23-Sep-2015, tatu: Ugh. We may also be given END_OBJECT (similar to FIELD_NAME),
114
120
// 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,
135
141
}
136
142
}
137
143
144
+ /**
145
+ * Implementation used when declared type is specifically {@link ArrayNode}.
146
+ */
138
147
final static class ArrayDeserializer
139
148
extends BaseNodeDeserializer <ArrayNode >
140
149
{
@@ -152,7 +161,7 @@ public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt) throws I
152
161
if (p .isExpectedStartArrayToken ()) {
153
162
final JsonNodeFactory nodeF = ctxt .getNodeFactory ();
154
163
final ArrayNode arrayNode = nodeF .arrayNode ();
155
- deserializeContainerNonRecursive (p , ctxt , nodeF ,
164
+ _deserializeContainerNoRecursion (p , ctxt , nodeF ,
156
165
new ContainerStack (), arrayNode );
157
166
return arrayNode ;
158
167
}
@@ -167,7 +176,7 @@ public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt,
167
176
ArrayNode arrayNode ) throws IOException
168
177
{
169
178
if (p .isExpectedStartArrayToken ()) {
170
- deserializeContainerNonRecursive (p , ctxt , ctxt .getNodeFactory (),
179
+ _deserializeContainerNoRecursion (p , ctxt , ctxt .getNodeFactory (),
171
180
new ContainerStack (), arrayNode );
172
181
return arrayNode ;
173
182
}
@@ -177,8 +186,10 @@ public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt,
177
186
}
178
187
179
188
/**
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.
182
193
*/
183
194
@ SuppressWarnings ("serial" )
184
195
abstract class BaseNodeDeserializer <T extends JsonNode >
@@ -275,10 +286,8 @@ protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt,
275
286
/**
276
287
* Alternate deserialization method used when parser already points to first
277
288
* FIELD_NAME and not START_OBJECT.
278
- *
279
- * @since 2.9
280
289
*/
281
- protected final ObjectNode deserializeObjectAtName (JsonParser p , DeserializationContext ctxt ,
290
+ protected final ObjectNode _deserializeObjectAtName (JsonParser p , DeserializationContext ctxt ,
282
291
final JsonNodeFactory nodeFactory , final ContainerStack stack ) throws IOException
283
292
{
284
293
final ObjectNode node = nodeFactory .objectNode ();
@@ -291,15 +300,15 @@ protected final ObjectNode deserializeObjectAtName(JsonParser p, Deserialization
291
300
}
292
301
switch (t .id ()) {
293
302
case JsonTokenId .ID_START_OBJECT :
294
- value = deserializeContainerNonRecursive (p , ctxt , nodeFactory ,
303
+ value = _deserializeContainerNoRecursion (p , ctxt , nodeFactory ,
295
304
stack , nodeFactory .objectNode ());
296
305
break ;
297
306
case JsonTokenId .ID_START_ARRAY :
298
- value = deserializeContainerNonRecursive (p , ctxt , nodeFactory ,
307
+ value = _deserializeContainerNoRecursion (p , ctxt , nodeFactory ,
299
308
stack , nodeFactory .arrayNode ());
300
309
break ;
301
310
default :
302
- value = deserializeAnyScalar (p , ctxt );
311
+ value = _deserializeAnyScalar (p , ctxt );
303
312
}
304
313
JsonNode old = node .replace (key , value );
305
314
if (old != null ) {
@@ -352,7 +361,7 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
352
361
if (t == JsonToken .START_ARRAY ) {
353
362
// 28-Mar-2021, tatu: We'll only append entries so not very different
354
363
// from "regular" deserializeArray...
355
- deserializeContainerNonRecursive (p , ctxt , nodeFactory ,
364
+ _deserializeContainerNoRecursion (p , ctxt , nodeFactory ,
356
365
stack , (ArrayNode ) old );
357
366
continue ;
358
367
}
@@ -364,11 +373,11 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
364
373
JsonNode value ;
365
374
switch (t .id ()) {
366
375
case JsonTokenId .ID_START_OBJECT :
367
- value = deserializeContainerNonRecursive (p , ctxt , nodeFactory ,
376
+ value = _deserializeContainerNoRecursion (p , ctxt , nodeFactory ,
368
377
stack , nodeFactory .objectNode ());
369
378
break ;
370
379
case JsonTokenId .ID_START_ARRAY :
371
- value = deserializeContainerNonRecursive (p , ctxt , nodeFactory ,
380
+ value = _deserializeContainerNoRecursion (p , ctxt , nodeFactory ,
372
381
stack , nodeFactory .arrayNode ());
373
382
break ;
374
383
case JsonTokenId .ID_STRING :
@@ -387,7 +396,7 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
387
396
value = nodeFactory .nullNode ();
388
397
break ;
389
398
default :
390
- value = deserializeRareScalar (p , ctxt );
399
+ value = _deserializeRareScalar (p , ctxt );
391
400
}
392
401
// 15-Feb-2021, tatu: I don't think this should have been called
393
402
// 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,
405
414
406
415
// Non-recursive alternative, used beyond certain nesting level
407
416
// @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 )
410
419
throws IOException
411
420
{
412
421
ContainerNode <?> curr = root ;
413
422
final int intCoercionFeats = ctxt .getDeserializationFeatures () & F_MASK_INT_COERCIONS ;
414
423
415
424
outer_loop :
416
- while (true ) {
417
- // if (curr.isObject()) {
425
+ do {
418
426
if (curr instanceof ObjectNode ) {
419
- final ObjectNode currObject = (ObjectNode ) curr ;
427
+ ObjectNode currObject = (ObjectNode ) curr ;
420
428
String propName = p .nextFieldName ();
421
429
430
+ objectLoop :
422
431
for (; propName != null ; propName = p .nextFieldName ()) {
423
432
JsonNode value ;
424
433
JsonToken t = p .nextToken ();
@@ -435,9 +444,10 @@ protected final ContainerNode<?> deserializeContainerNonRecursive(JsonParser p,
435
444
propName , currObject , old , newOb );
436
445
}
437
446
stack .push (curr );
438
- curr = newOb ;
447
+ curr = currObject = newOb ;
448
+ // We can actually take a short-cut with nested Objects...
449
+ continue objectLoop ;
439
450
}
440
- continue outer_loop ;
441
451
case JsonTokenId .ID_START_ARRAY :
442
452
{
443
453
ArrayNode newOb = nodeFactory .arrayNode ();
@@ -469,16 +479,17 @@ protected final ContainerNode<?> deserializeContainerNonRecursive(JsonParser p,
469
479
value = nodeFactory .nullNode ();
470
480
break ;
471
481
default :
472
- value = deserializeRareScalar (p , ctxt );
482
+ value = _deserializeRareScalar (p , ctxt );
473
483
}
474
484
JsonNode old = currObject .replace (propName , value );
475
485
if (old != null ) {
476
486
_handleDuplicateField (p , ctxt , nodeFactory ,
477
487
propName , currObject , old , value );
478
488
}
479
489
}
480
- // reached not-property-name, should be END_OBJECT
490
+ // reached not-property-name, should be END_OBJECT (verify?)
481
491
} else {
492
+ // Otherwise we must have an array
482
493
final ArrayNode currArray = (ArrayNode ) curr ;
483
494
484
495
arrayLoop :
@@ -519,22 +530,21 @@ protected final ContainerNode<?> deserializeContainerNonRecursive(JsonParser p,
519
530
currArray .add (nodeFactory .nullNode ());
520
531
continue arrayLoop ;
521
532
default :
522
- currArray .add (deserializeRareScalar (p , ctxt ));
533
+ currArray .add (_deserializeRareScalar (p , ctxt ));
523
534
continue arrayLoop ;
524
535
}
525
536
}
526
537
// Reached end of array (or input), so...
527
538
}
528
- if (stack .isEmpty ()) {
529
- return root ;
530
- }
531
- curr = stack .pop ();
532
- }
533
- }
534
539
540
+ // Either way, Object or Array ended, return up nesting level:
541
+ curr = stack .popOrNull ();
542
+ } while (curr != null );
543
+ return root ;
544
+ }
535
545
536
546
// 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 )
538
548
throws IOException
539
549
{
540
550
final JsonNodeFactory nodeF = ctxt .getNodeFactory ();
@@ -562,7 +572,7 @@ protected final JsonNode deserializeAnyScalar(JsonParser p, DeserializationConte
562
572
return (JsonNode ) ctxt .handleUnexpectedToken (handledType (), p );
563
573
}
564
574
565
- protected final JsonNode deserializeRareScalar (JsonParser p , DeserializationContext ctxt )
575
+ protected final JsonNode _deserializeRareScalar (JsonParser p , DeserializationContext ctxt )
566
576
throws IOException
567
577
{
568
578
// 28-Mar-2021, tatu: Only things that caller does not check
@@ -686,29 +696,33 @@ protected final JsonNode _fromEmbedded(JsonParser p, DeserializationContext ctxt
686
696
final static class ContainerStack
687
697
{
688
698
private ContainerNode [] _stack ;
689
- private int _top ;
699
+ private int _top , _end ;
690
700
691
701
public ContainerStack () { }
692
702
693
- public boolean isEmpty () { return _top == 0 ; }
694
-
695
703
// Not used yet but useful for limits (fail at [some high depth])
696
704
public int size () { return _top ; }
697
705
698
- public void push (ContainerNode node ) {
706
+ public void push (ContainerNode node )
707
+ {
708
+ if (_top < _end ) {
709
+ _stack [_top ++] = node ;
710
+ return ;
711
+ }
699
712
if (_stack == null ) {
700
- _stack = new ContainerNode [10 ];
701
- } else if (_stack .length == _top ) {
713
+ _end = 10 ;
714
+ _stack = new ContainerNode [_end ];
715
+ } else {
702
716
// 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 );
705
719
}
706
720
_stack [_top ++] = node ;
707
721
}
708
722
709
- public ContainerNode pop () {
723
+ public ContainerNode popOrNull () {
710
724
if (_top == 0 ) {
711
- throw new IllegalStateException ( "ContainerStack empty" ) ;
725
+ return null ;
712
726
}
713
727
// note: could clean up stack but due to usage pattern, should not make
714
728
// any difference -- all nodes joined during and after construction and
0 commit comments