1 package com.fasterxml.jackson.databind.deser.std;
2 
3 import java.io.IOException;
4 import java.util.*;
5 
6 import com.fasterxml.jackson.core.*;
7 
8 import com.fasterxml.jackson.databind.*;
9 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
10 import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
11 import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
12 import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
13 import com.fasterxml.jackson.databind.type.LogicalType;
14 import com.fasterxml.jackson.databind.type.TypeFactory;
15 import com.fasterxml.jackson.databind.util.ClassUtil;
16 import com.fasterxml.jackson.databind.util.ObjectBuffer;
17 
18 /**
19  * Deserializer implementation that is used if it is necessary to bind content of
20  * "unknown" type; something declared as basic {@link java.lang.Object}
21  * (either explicitly, or due to type erasure).
22  * If so, "natural" mapping is used to convert JSON values to their natural
23  * Java object matches: JSON arrays to Java {@link java.util.List}s (or, if configured,
24  * Object[]), JSON objects to {@link java.util.Map}s, numbers to
25  * {@link java.lang.Number}s, booleans to {@link java.lang.Boolean}s and
26  * strings to {@link java.lang.String} (and nulls to nulls).
27  */
28 @JacksonStdImpl
29 public class UntypedObjectDeserializer
30     extends StdDeserializer<Object>
31     implements ResolvableDeserializer, ContextualDeserializer
32 {
33     private static final long serialVersionUID = 1L;
34 
35     protected final static Object[] NO_OBJECTS = new Object[0];
36 
37     /*
38     /**********************************************************
39     /* Possible custom deserializer overrides we need to use
40     /**********************************************************
41      */
42 
43     protected JsonDeserializer<Object> _mapDeserializer;
44 
45     protected JsonDeserializer<Object> _listDeserializer;
46 
47     protected JsonDeserializer<Object> _stringDeserializer;
48 
49     protected JsonDeserializer<Object> _numberDeserializer;
50 
51     /**
52      * If {@link java.util.List} has been mapped to non-default implementation,
53      * we'll store type here
54      *
55      * @since 2.6
56      */
57     protected JavaType _listType;
58 
59     /**
60      * If {@link java.util.Map} has been mapped to non-default implementation,
61      * we'll store type here
62      *
63      * @since 2.6
64      */
65     protected JavaType _mapType;
66 
67     /**
68      * @since 2.9
69      */
70     protected final boolean _nonMerging;
71 
72     /**
73      * @deprecated Since 2.6 use variant takes type arguments
74      */
75     @Deprecated
UntypedObjectDeserializer()76     public UntypedObjectDeserializer() {
77         this(null, null);
78     }
79 
UntypedObjectDeserializer(JavaType listType, JavaType mapType)80     public UntypedObjectDeserializer(JavaType listType, JavaType mapType) {
81         super(Object.class);
82         _listType = listType;
83         _mapType = mapType;
84         _nonMerging = false;
85     }
86 
87     @SuppressWarnings("unchecked")
UntypedObjectDeserializer(UntypedObjectDeserializer base, JsonDeserializer<?> mapDeser, JsonDeserializer<?> listDeser, JsonDeserializer<?> stringDeser, JsonDeserializer<?> numberDeser)88     public UntypedObjectDeserializer(UntypedObjectDeserializer base,
89             JsonDeserializer<?> mapDeser, JsonDeserializer<?> listDeser,
90             JsonDeserializer<?> stringDeser, JsonDeserializer<?> numberDeser)
91     {
92         super(Object.class);
93         _mapDeserializer = (JsonDeserializer<Object>) mapDeser;
94         _listDeserializer = (JsonDeserializer<Object>) listDeser;
95         _stringDeserializer = (JsonDeserializer<Object>) stringDeser;
96         _numberDeserializer = (JsonDeserializer<Object>) numberDeser;
97         _listType = base._listType;
98         _mapType = base._mapType;
99         _nonMerging = base._nonMerging;
100     }
101 
102     /**
103      * @since 2.9
104      */
UntypedObjectDeserializer(UntypedObjectDeserializer base, boolean nonMerging)105     protected UntypedObjectDeserializer(UntypedObjectDeserializer base,
106             boolean nonMerging)
107     {
108         super(Object.class);
109         _mapDeserializer = base._mapDeserializer;
110         _listDeserializer = base._listDeserializer;
111         _stringDeserializer = base._stringDeserializer;
112         _numberDeserializer = base._numberDeserializer;
113         _listType = base._listType;
114         _mapType = base._mapType;
115         _nonMerging = nonMerging;
116     }
117 
118     /*
119     /**********************************************************
120     /* Initialization
121     /**********************************************************
122      */
123 
124     /**
125      * We need to implement this method to properly find things to delegate
126      * to: it cannot be done earlier since delegated deserializers almost
127      * certainly require access to this instance (at least "List" and "Map" ones)
128      */
129     @SuppressWarnings("unchecked")
130     @Override
resolve(DeserializationContext ctxt)131     public void resolve(DeserializationContext ctxt) throws JsonMappingException
132     {
133         JavaType obType = ctxt.constructType(Object.class);
134         JavaType stringType = ctxt.constructType(String.class);
135         TypeFactory tf = ctxt.getTypeFactory();
136 
137         /* 26-Nov-2014, tatu: This is highly unusual, as in general contextualization
138          *    should always be called separately, from within "createContextual()".
139          *    But this is a very singular deserializer since it operates on `Object`
140          *    (and often for `?` type parameter), and as a result, easily and commonly
141          *    results in cycles, being value deserializer for various Maps and Collections.
142          *    Because of this, we must somehow break the cycles. This is done here by
143          *    forcing pseudo-contextualization with null property.
144          */
145 
146         // So: first find possible custom instances
147         if (_listType == null) {
148             _listDeserializer = _clearIfStdImpl(_findCustomDeser(ctxt, tf.constructCollectionType(List.class, obType)));
149         } else {
150             // NOTE: if non-default List type, always consider to be non-standard deser
151             _listDeserializer = _findCustomDeser(ctxt, _listType);
152         }
153         if (_mapType == null) {
154             _mapDeserializer = _clearIfStdImpl(_findCustomDeser(ctxt, tf.constructMapType(Map.class, stringType, obType)));
155         } else {
156             // NOTE: if non-default Map type, always consider to be non-standard deser
157             _mapDeserializer = _findCustomDeser(ctxt, _mapType);
158         }
159         _stringDeserializer = _clearIfStdImpl(_findCustomDeser(ctxt, stringType));
160         _numberDeserializer = _clearIfStdImpl(_findCustomDeser(ctxt, tf.constructType(Number.class)));
161 
162         // and then do bogus contextualization, in case custom ones need to resolve dependencies of
163         // their own
164         JavaType unknown = TypeFactory.unknownType();
165         _mapDeserializer = (JsonDeserializer<Object>) ctxt.handleSecondaryContextualization(_mapDeserializer, null, unknown);
166         _listDeserializer = (JsonDeserializer<Object>) ctxt.handleSecondaryContextualization(_listDeserializer, null, unknown);
167         _stringDeserializer = (JsonDeserializer<Object>) ctxt.handleSecondaryContextualization(_stringDeserializer, null, unknown);
168         _numberDeserializer = (JsonDeserializer<Object>) ctxt.handleSecondaryContextualization(_numberDeserializer, null, unknown);
169     }
170 
_findCustomDeser(DeserializationContext ctxt, JavaType type)171     protected JsonDeserializer<Object> _findCustomDeser(DeserializationContext ctxt, JavaType type)
172         throws JsonMappingException
173     {
174         // Since we are calling from `resolve`, we should NOT try to contextualize yet;
175         // contextualization will only occur at a later point
176         return ctxt.findNonContextualValueDeserializer(type);
177     }
178 
_clearIfStdImpl(JsonDeserializer<Object> deser)179     protected JsonDeserializer<Object> _clearIfStdImpl(JsonDeserializer<Object> deser) {
180         return ClassUtil.isJacksonStdImpl(deser) ? null : deser;
181     }
182 
183     /**
184      * We only use contextualization for optimizing the case where no customization
185      * occurred; if so, can slip in a more streamlined version.
186      */
187     @Override
createContextual(DeserializationContext ctxt, BeanProperty property)188     public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
189             BeanProperty property) throws JsonMappingException
190     {
191         // 14-Jun-2017, tatu: [databind#1625]: may want to block merging, for root value
192         boolean preventMerge = (property == null)
193                 && Boolean.FALSE.equals(ctxt.getConfig().getDefaultMergeable(Object.class));
194         // 20-Apr-2014, tatu: If nothing custom, let's use "vanilla" instance,
195         //     simpler and can avoid some of delegation
196         if ((_stringDeserializer == null) && (_numberDeserializer == null)
197                 && (_mapDeserializer == null) && (_listDeserializer == null)
198                 &&  getClass() == UntypedObjectDeserializer.class) {
199             return Vanilla.instance(preventMerge);
200         }
201 
202         if (preventMerge != _nonMerging) {
203             return new UntypedObjectDeserializer(this, preventMerge);
204         }
205 
206         return this;
207     }
208 
209     /*
210     /**********************************************************
211     /* Deserializer API
212     /**********************************************************
213      */
214 
215     /* 07-Nov-2014, tatu: When investigating [databind#604], realized that it makes
216      *   sense to also mark this is cachable, since lookup not exactly free, and
217      *   since it's not uncommon to "read anything"
218      */
219     @Override
isCachable()220     public boolean isCachable() {
221         /* 26-Mar-2015, tatu: With respect to [databind#735], there are concerns over
222          *   cachability. It seems like we SHOULD be safe here; but just in case there
223          *   are problems with false sharing, this may need to be revisited.
224          */
225         return true;
226     }
227 
228     @Override // since 2.12
logicalType()229     public LogicalType logicalType() {
230         return LogicalType.Untyped;
231     }
232 
233     @Override // since 2.9
supportsUpdate(DeserializationConfig config)234     public Boolean supportsUpdate(DeserializationConfig config) {
235         // 21-Apr-2017, tatu: Bit tricky... some values, yes. So let's say "dunno"
236         return null;
237     }
238 
239     @Override
deserialize(JsonParser p, DeserializationContext ctxt)240     public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
241     {
242         switch (p.currentTokenId()) {
243         case JsonTokenId.ID_START_OBJECT:
244         case JsonTokenId.ID_FIELD_NAME:
245             // 28-Oct-2015, tatu: [databind#989] We may also be given END_OBJECT (similar to FIELD_NAME),
246             //    if caller has advanced to the first token of Object, but for empty Object
247         case JsonTokenId.ID_END_OBJECT:
248             if (_mapDeserializer != null) {
249                 return _mapDeserializer.deserialize(p, ctxt);
250             }
251             return mapObject(p, ctxt);
252         case JsonTokenId.ID_START_ARRAY:
253             if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
254                 return mapArrayToArray(p, ctxt);
255             }
256             if (_listDeserializer != null) {
257                 return _listDeserializer.deserialize(p, ctxt);
258             }
259             return mapArray(p, ctxt);
260         case JsonTokenId.ID_EMBEDDED_OBJECT:
261             return p.getEmbeddedObject();
262         case JsonTokenId.ID_STRING:
263             if (_stringDeserializer != null) {
264                 return _stringDeserializer.deserialize(p, ctxt);
265             }
266             return p.getText();
267 
268         case JsonTokenId.ID_NUMBER_INT:
269             if (_numberDeserializer != null) {
270                 return _numberDeserializer.deserialize(p, ctxt);
271             }
272             /* Caller may want to get all integral values returned as {@link java.math.BigInteger},
273              * or {@link java.lang.Long} for consistency
274              */
275             if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
276                 return _coerceIntegral(p, ctxt);
277             }
278             return p.getNumberValue(); // should be optimal, whatever it is
279 
280         case JsonTokenId.ID_NUMBER_FLOAT:
281             if (_numberDeserializer != null) {
282                 return _numberDeserializer.deserialize(p, ctxt);
283             }
284             // Need to allow overriding the behavior regarding which type to use
285             if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
286                 return p.getDecimalValue();
287             }
288             // as per [databind#1453] should not assume Double but:
289             return p.getNumberValue();
290 
291         case JsonTokenId.ID_TRUE:
292             return Boolean.TRUE;
293         case JsonTokenId.ID_FALSE:
294             return Boolean.FALSE;
295 
296         case JsonTokenId.ID_NULL: // 08-Nov-2016, tatu: yes, occurs
297             return null;
298 
299 //        case JsonTokenId.ID_END_ARRAY: // invalid
300         default:
301         }
302         return ctxt.handleUnexpectedToken(Object.class, p);
303     }
304 
305     @Override
deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)306     public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
307             TypeDeserializer typeDeserializer) throws IOException
308     {
309         switch (p.currentTokenId()) {
310         // First: does it look like we had type id wrapping of some kind?
311         case JsonTokenId.ID_START_ARRAY:
312         case JsonTokenId.ID_START_OBJECT:
313         case JsonTokenId.ID_FIELD_NAME:
314             // Output can be as JSON Object, Array or scalar: no way to know at this point:
315             return typeDeserializer.deserializeTypedFromAny(p, ctxt);
316 
317         case JsonTokenId.ID_EMBEDDED_OBJECT:
318             return p.getEmbeddedObject();
319 
320         // Otherwise we probably got a "native" type (ones that map
321         // naturally and thus do not need or use type ids)
322         case JsonTokenId.ID_STRING:
323             if (_stringDeserializer != null) {
324                 return _stringDeserializer.deserialize(p, ctxt);
325             }
326             return p.getText();
327 
328         case JsonTokenId.ID_NUMBER_INT:
329             if (_numberDeserializer != null) {
330                 return _numberDeserializer.deserialize(p, ctxt);
331             }
332             // May need coercion to "bigger" types:
333             if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
334                 return _coerceIntegral(p, ctxt);
335             }
336             return p.getNumberValue(); // should be optimal, whatever it is
337 
338         case JsonTokenId.ID_NUMBER_FLOAT:
339             if (_numberDeserializer != null) {
340                 return _numberDeserializer.deserialize(p, ctxt);
341             }
342             if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
343                 return p.getDecimalValue();
344             }
345             return p.getNumberValue();
346 
347         case JsonTokenId.ID_TRUE:
348             return Boolean.TRUE;
349         case JsonTokenId.ID_FALSE:
350             return Boolean.FALSE;
351 
352         case JsonTokenId.ID_NULL: // should not get this far really but...
353             return null;
354         default:
355         }
356         return ctxt.handleUnexpectedToken(Object.class, p);
357     }
358 
359     @SuppressWarnings("unchecked")
360     @Override // since 2.9 (to support deep merge)
deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue)361     public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue)
362         throws IOException
363     {
364         if (_nonMerging) {
365             return deserialize(p, ctxt);
366         }
367 
368         switch (p.currentTokenId()) {
369         case JsonTokenId.ID_START_OBJECT:
370         case JsonTokenId.ID_FIELD_NAME:
371             // We may also be given END_OBJECT (similar to FIELD_NAME),
372             // if caller has advanced to the first token of Object, but for empty Object
373         case JsonTokenId.ID_END_OBJECT:
374             if (_mapDeserializer != null) {
375                 return _mapDeserializer.deserialize(p, ctxt, intoValue);
376             }
377             if (intoValue instanceof Map<?,?>) {
378                 return mapObject(p, ctxt, (Map<Object,Object>) intoValue);
379             }
380             return mapObject(p, ctxt);
381         case JsonTokenId.ID_START_ARRAY:
382             if (_listDeserializer != null) {
383                 return _listDeserializer.deserialize(p, ctxt, intoValue);
384             }
385             if (intoValue instanceof Collection<?>) {
386                 return mapArray(p, ctxt, (Collection<Object>) intoValue);
387             }
388             if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
389                 return mapArrayToArray(p, ctxt);
390             }
391             return mapArray(p, ctxt);
392         case JsonTokenId.ID_EMBEDDED_OBJECT:
393             return p.getEmbeddedObject();
394         case JsonTokenId.ID_STRING:
395             if (_stringDeserializer != null) {
396                 return _stringDeserializer.deserialize(p, ctxt, intoValue);
397             }
398             return p.getText();
399 
400         case JsonTokenId.ID_NUMBER_INT:
401             if (_numberDeserializer != null) {
402                 return _numberDeserializer.deserialize(p, ctxt, intoValue);
403             }
404             if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
405                 return _coerceIntegral(p, ctxt);
406             }
407             return p.getNumberValue();
408 
409         case JsonTokenId.ID_NUMBER_FLOAT:
410             if (_numberDeserializer != null) {
411                 return _numberDeserializer.deserialize(p, ctxt, intoValue);
412             }
413             if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
414                 return p.getDecimalValue();
415             }
416             return p.getNumberValue();
417         case JsonTokenId.ID_TRUE:
418             return Boolean.TRUE;
419         case JsonTokenId.ID_FALSE:
420             return Boolean.FALSE;
421 
422         case JsonTokenId.ID_NULL:
423             // 21-Apr-2017, tatu: May need to consider "skip nulls" at some point but...
424             return null;
425         default:
426         }
427         // easiest to just delegate to "dumb" version for the rest?
428         return deserialize(p, ctxt);
429     }
430 
431     /*
432     /**********************************************************
433     /* Internal methods
434     /**********************************************************
435      */
436 
437     /**
438      * Method called to map a JSON Array into a Java value.
439      */
mapArray(JsonParser p, DeserializationContext ctxt)440     protected Object mapArray(JsonParser p, DeserializationContext ctxt) throws IOException
441     {
442         // Minor optimization to handle small lists (default size for ArrayList is 10)
443         if (p.nextToken()  == JsonToken.END_ARRAY) {
444             return new ArrayList<Object>(2);
445         }
446         Object value = deserialize(p, ctxt);
447         if (p.nextToken()  == JsonToken.END_ARRAY) {
448             ArrayList<Object> l = new ArrayList<Object>(2);
449             l.add(value);
450             return l;
451         }
452         Object value2 = deserialize(p, ctxt);
453         if (p.nextToken()  == JsonToken.END_ARRAY) {
454             ArrayList<Object> l = new ArrayList<Object>(2);
455             l.add(value);
456             l.add(value2);
457             return l;
458         }
459         ObjectBuffer buffer = ctxt.leaseObjectBuffer();
460         Object[] values = buffer.resetAndStart();
461         int ptr = 0;
462         values[ptr++] = value;
463         values[ptr++] = value2;
464         int totalSize = ptr;
465         do {
466             value = deserialize(p, ctxt);
467             ++totalSize;
468             if (ptr >= values.length) {
469                 values = buffer.appendCompletedChunk(values);
470                 ptr = 0;
471             }
472             values[ptr++] = value;
473         } while (p.nextToken() != JsonToken.END_ARRAY);
474         // let's create full array then
475         ArrayList<Object> result = new ArrayList<Object>(totalSize);
476         buffer.completeAndClearBuffer(values, ptr, result);
477         return result;
478     }
479 
mapArray(JsonParser p, DeserializationContext ctxt, Collection<Object> result)480     protected Object mapArray(JsonParser p, DeserializationContext ctxt,
481             Collection<Object> result) throws IOException
482     {
483         // we start by pointing to START_ARRAY. Also, no real merging; array/Collection
484         // just appends always
485         while (p.nextToken() != JsonToken.END_ARRAY) {
486             result.add(deserialize(p, ctxt));
487         }
488         return result;
489     }
490 
491     /**
492      * Method called to map a JSON Object into a Java value.
493      */
mapObject(JsonParser p, DeserializationContext ctxt)494     protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOException
495     {
496         String key1;
497 
498         JsonToken t = p.currentToken();
499 
500         if (t == JsonToken.START_OBJECT) {
501             key1 = p.nextFieldName();
502         } else if (t == JsonToken.FIELD_NAME) {
503             key1 = p.currentName();
504         } else {
505             if (t != JsonToken.END_OBJECT) {
506                 return ctxt.handleUnexpectedToken(handledType(), p);
507             }
508             key1 = null;
509         }
510         if (key1 == null) {
511             // empty map might work; but caller may want to modify... so better just give small modifiable
512             return new LinkedHashMap<>(2);
513         }
514         // minor optimization; let's handle 1 and 2 entry cases separately
515         // 24-Mar-2015, tatu: Ideally, could use one of 'nextXxx()' methods, but for
516         //   that we'd need new method(s) in JsonDeserializer. So not quite yet.
517         p.nextToken();
518         Object value1 = deserialize(p, ctxt);
519         String key2 = p.nextFieldName();
520         if (key2 == null) { // has to be END_OBJECT, then
521             // single entry; but we want modifiable
522             LinkedHashMap<String, Object> result = new LinkedHashMap<>(2);
523             result.put(key1, value1);
524             return result;
525         }
526         p.nextToken();
527         Object value2 = deserialize(p, ctxt);
528 
529         String key = p.nextFieldName();
530         if (key == null) {
531             LinkedHashMap<String, Object> result = new LinkedHashMap<>(4);
532             result.put(key1, value1);
533             if (result.put(key2, value2) != null) {
534                 // 22-May-2020, tatu: [databind#2733] may need extra handling
535                 return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
536             }
537             return result;
538         }
539         // And then the general case; default map size is 16
540         LinkedHashMap<String, Object> result = new LinkedHashMap<>();
541         result.put(key1, value1);
542         if (result.put(key2, value2) != null) {
543             // 22-May-2020, tatu: [databind#2733] may need extra handling
544             return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
545         }
546 
547         do {
548             p.nextToken();
549             final Object newValue = deserialize(p, ctxt);
550             final Object oldValue = result.put(key, newValue);
551             if (oldValue != null) {
552                 return _mapObjectWithDups(p, ctxt, result, key, oldValue, newValue,
553                         p.nextFieldName());
554             }
555         } while ((key = p.nextFieldName()) != null);
556         return result;
557     }
558 
559     // @since 2.12 (wrt [databind#2733]
_mapObjectWithDups(JsonParser p, DeserializationContext ctxt, final Map<String, Object> result, String key, Object oldValue, Object newValue, String nextKey)560     protected Object _mapObjectWithDups(JsonParser p, DeserializationContext ctxt,
561             final Map<String, Object> result, String key,
562             Object oldValue, Object newValue, String nextKey) throws IOException
563     {
564         final boolean squashDups = ctxt.isEnabled(StreamReadCapability.DUPLICATE_PROPERTIES);
565 
566         if (squashDups) {
567             _squashDups(result, key, oldValue, newValue);
568         }
569 
570         while (nextKey != null) {
571             p.nextToken();
572             newValue = deserialize(p, ctxt);
573             oldValue = result.put(nextKey, newValue);
574             if ((oldValue != null) && squashDups) {
575                 _squashDups(result, key, oldValue, newValue);
576             }
577             nextKey = p.nextFieldName();
578         }
579 
580         return result;
581     }
582 
583     @SuppressWarnings("unchecked")
_squashDups(final Map<String, Object> result, String key, Object oldValue, Object newValue)584     private void _squashDups(final Map<String, Object> result, String key,
585             Object oldValue, Object newValue)
586     {
587         if (oldValue instanceof List<?>) {
588             ((List<Object>) oldValue).add(newValue);
589             result.put(key, oldValue);
590         } else {
591             ArrayList<Object> l = new ArrayList<>();
592             l.add(oldValue);
593             l.add(newValue);
594             result.put(key, l);
595         }
596     }
597 
598     /**
599      * Method called to map a JSON Array into a Java Object array (Object[]).
600      */
mapArrayToArray(JsonParser p, DeserializationContext ctxt)601     protected Object[] mapArrayToArray(JsonParser p, DeserializationContext ctxt) throws IOException
602     {
603         // Minor optimization to handle small lists (default size for ArrayList is 10)
604         if (p.nextToken()  == JsonToken.END_ARRAY) {
605             return NO_OBJECTS;
606         }
607         ObjectBuffer buffer = ctxt.leaseObjectBuffer();
608         Object[] values = buffer.resetAndStart();
609         int ptr = 0;
610         do {
611             Object value = deserialize(p, ctxt);
612             if (ptr >= values.length) {
613                 values = buffer.appendCompletedChunk(values);
614                 ptr = 0;
615             }
616             values[ptr++] = value;
617         } while (p.nextToken() != JsonToken.END_ARRAY);
618         return buffer.completeAndClearBuffer(values, ptr);
619     }
620 
mapObject(JsonParser p, DeserializationContext ctxt, Map<Object,Object> m)621     protected Object mapObject(JsonParser p, DeserializationContext ctxt,
622             Map<Object,Object> m) throws IOException
623     {
624         JsonToken t = p.currentToken();
625         if (t == JsonToken.START_OBJECT) {
626             t = p.nextToken();
627         }
628         if (t == JsonToken.END_OBJECT) {
629             return m;
630         }
631         // NOTE: we are guaranteed to point to FIELD_NAME
632         String key = p.currentName();
633         do {
634             p.nextToken();
635             // and possibly recursive merge here
636             Object old = m.get(key);
637             Object newV;
638 
639             if (old != null) {
640                 newV = deserialize(p, ctxt, old);
641             } else {
642                 newV = deserialize(p, ctxt);
643             }
644             if (newV != old) {
645                 m.put(key, newV);
646             }
647         } while ((key = p.nextFieldName()) != null);
648         return m;
649     }
650 
651     /*
652     /**********************************************************
653     /* Separate "vanilla" implementation for common case of
654     /* no custom deserializer overrides
655     /**********************************************************
656      */
657 
658     /**
659      * Streamlined version of {@link UntypedObjectDeserializer} that has fewer checks and
660      * is only used when no custom deserializer overrides are applied.
661      */
662     @JacksonStdImpl
663     public static class Vanilla
664         extends StdDeserializer<Object>
665     {
666         private static final long serialVersionUID = 1L;
667 
668         public final static Vanilla std = new Vanilla();
669 
670         /**
671          * @since 2.9
672          */
673         protected final boolean _nonMerging;
674 
Vanilla()675         public Vanilla() { this(false); }
676 
Vanilla(boolean nonMerging)677         protected Vanilla(boolean nonMerging) {
678             super(Object.class);
679             _nonMerging = nonMerging;
680         }
681 
instance(boolean nonMerging)682         public static Vanilla instance(boolean nonMerging) {
683             if (nonMerging) {
684                 return new Vanilla(true);
685             }
686             return std;
687         }
688 
689         @Override // since 2.12
logicalType()690         public LogicalType logicalType() {
691             return LogicalType.Untyped;
692         }
693 
694         @Override // since 2.9
supportsUpdate(DeserializationConfig config)695         public Boolean supportsUpdate(DeserializationConfig config) {
696             // 21-Apr-2017, tatu: Bit tricky... some values, yes. So let's say "dunno"
697             // 14-Jun-2017, tatu: Well, if merging blocked, can say no, as well.
698             return _nonMerging ? Boolean.FALSE : null;
699         }
700 
701         @Override
deserialize(JsonParser p, DeserializationContext ctxt)702         public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
703         {
704             switch (p.currentTokenId()) {
705             case JsonTokenId.ID_START_OBJECT:
706                 {
707                     JsonToken t = p.nextToken();
708                     if (t == JsonToken.END_OBJECT) {
709                         return new LinkedHashMap<String,Object>(2);
710                     }
711                 }
712             case JsonTokenId.ID_FIELD_NAME:
713                 return mapObject(p, ctxt);
714             case JsonTokenId.ID_START_ARRAY:
715                 {
716                     JsonToken t = p.nextToken();
717                     if (t == JsonToken.END_ARRAY) { // and empty one too
718                         if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
719                             return NO_OBJECTS;
720                         }
721                         return new ArrayList<Object>(2);
722                     }
723                 }
724                 if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
725                     return mapArrayToArray(p, ctxt);
726                 }
727                 return mapArray(p, ctxt);
728             case JsonTokenId.ID_EMBEDDED_OBJECT:
729                 return p.getEmbeddedObject();
730             case JsonTokenId.ID_STRING:
731                 return p.getText();
732 
733             case JsonTokenId.ID_NUMBER_INT:
734                 if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
735                     return _coerceIntegral(p, ctxt);
736                 }
737                 return p.getNumberValue(); // should be optimal, whatever it is
738 
739             case JsonTokenId.ID_NUMBER_FLOAT:
740                 if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
741                     return p.getDecimalValue();
742                 }
743                 return p.getNumberValue();
744 
745             case JsonTokenId.ID_TRUE:
746                 return Boolean.TRUE;
747             case JsonTokenId.ID_FALSE:
748                 return Boolean.FALSE;
749 
750             case JsonTokenId.ID_END_OBJECT:
751                 // 28-Oct-2015, tatu: [databind#989] We may also be given END_OBJECT (similar to FIELD_NAME),
752                 //    if caller has advanced to the first token of Object, but for empty Object
753                 return new LinkedHashMap<String,Object>(2);
754 
755             case JsonTokenId.ID_NULL: // 08-Nov-2016, tatu: yes, occurs
756                 return null;
757 
758             //case JsonTokenId.ID_END_ARRAY: // invalid
759             default:
760             }
761             return ctxt.handleUnexpectedToken(Object.class, p);
762         }
763 
764         @Override
deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)765         public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException
766         {
767             switch (p.currentTokenId()) {
768             case JsonTokenId.ID_START_ARRAY:
769             case JsonTokenId.ID_START_OBJECT:
770             case JsonTokenId.ID_FIELD_NAME:
771                 return typeDeserializer.deserializeTypedFromAny(p, ctxt);
772 
773             case JsonTokenId.ID_STRING:
774                 return p.getText();
775 
776             case JsonTokenId.ID_NUMBER_INT:
777                 if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
778                     return p.getBigIntegerValue();
779                 }
780                 return p.getNumberValue();
781 
782             case JsonTokenId.ID_NUMBER_FLOAT:
783                 if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
784                     return p.getDecimalValue();
785                 }
786                 return p.getNumberValue();
787 
788             case JsonTokenId.ID_TRUE:
789                 return Boolean.TRUE;
790             case JsonTokenId.ID_FALSE:
791                 return Boolean.FALSE;
792             case JsonTokenId.ID_EMBEDDED_OBJECT:
793                 return p.getEmbeddedObject();
794 
795             case JsonTokenId.ID_NULL: // should not get this far really but...
796                 return null;
797             default:
798             }
799             return ctxt.handleUnexpectedToken(Object.class, p);
800         }
801 
802         @SuppressWarnings("unchecked")
803         @Override // since 2.9 (to support deep merge)
deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue)804         public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue)
805             throws IOException
806         {
807             if (_nonMerging) {
808                 return deserialize(p, ctxt);
809             }
810 
811             switch (p.currentTokenId()) {
812             case JsonTokenId.ID_END_OBJECT:
813             case JsonTokenId.ID_END_ARRAY:
814                 return intoValue;
815             case JsonTokenId.ID_START_OBJECT:
816                 {
817                     JsonToken t = p.nextToken(); // to get to FIELD_NAME or END_OBJECT
818                     if (t == JsonToken.END_OBJECT) {
819                         return intoValue;
820                     }
821                 }
822             case JsonTokenId.ID_FIELD_NAME:
823                 if (intoValue instanceof Map<?,?>) {
824                     Map<Object,Object> m = (Map<Object,Object>) intoValue;
825                     // NOTE: we are guaranteed to point to FIELD_NAME
826                     String key = p.currentName();
827                     do {
828                         p.nextToken();
829                         // and possibly recursive merge here
830                         Object old = m.get(key);
831                         Object newV;
832                         if (old != null) {
833                             newV = deserialize(p, ctxt, old);
834                         } else {
835                             newV = deserialize(p, ctxt);
836                         }
837                         if (newV != old) {
838                             m.put(key, newV);
839                         }
840                     } while ((key = p.nextFieldName()) != null);
841                     return intoValue;
842                 }
843                 break;
844             case JsonTokenId.ID_START_ARRAY:
845                 {
846                     JsonToken t = p.nextToken(); // to get to FIELD_NAME or END_OBJECT
847                     if (t == JsonToken.END_ARRAY) {
848                         return intoValue;
849                     }
850                 }
851 
852                 if (intoValue instanceof Collection<?>) {
853                     Collection<Object> c = (Collection<Object>) intoValue;
854                     // NOTE: merge for arrays/Collections means append, can't merge contents
855                     do {
856                         c.add(deserialize(p, ctxt));
857                     } while (p.nextToken() != JsonToken.END_ARRAY);
858                     return intoValue;
859                 }
860                 // 21-Apr-2017, tatu: Should we try to support merging of Object[] values too?
861                 //    ... maybe future improvement
862                 break;
863             }
864             // Easiest handling for the rest, delegate. Only (?) question: how about nulls?
865             return deserialize(p, ctxt);
866         }
867 
mapArray(JsonParser p, DeserializationContext ctxt)868         protected Object mapArray(JsonParser p, DeserializationContext ctxt) throws IOException
869         {
870             Object value = deserialize(p, ctxt);
871             if (p.nextToken()  == JsonToken.END_ARRAY) {
872                 ArrayList<Object> l = new ArrayList<Object>(2);
873                 l.add(value);
874                 return l;
875             }
876             Object value2 = deserialize(p, ctxt);
877             if (p.nextToken()  == JsonToken.END_ARRAY) {
878                 ArrayList<Object> l = new ArrayList<Object>(2);
879                 l.add(value);
880                 l.add(value2);
881                 return l;
882             }
883             ObjectBuffer buffer = ctxt.leaseObjectBuffer();
884             Object[] values = buffer.resetAndStart();
885             int ptr = 0;
886             values[ptr++] = value;
887             values[ptr++] = value2;
888             int totalSize = ptr;
889             do {
890                 value = deserialize(p, ctxt);
891                 ++totalSize;
892                 if (ptr >= values.length) {
893                     values = buffer.appendCompletedChunk(values);
894                     ptr = 0;
895                 }
896                 values[ptr++] = value;
897             } while (p.nextToken() != JsonToken.END_ARRAY);
898             // let's create full array then
899             ArrayList<Object> result = new ArrayList<Object>(totalSize);
900             buffer.completeAndClearBuffer(values, ptr, result);
901             return result;
902         }
903 
904         /**
905          * Method called to map a JSON Array into a Java Object array (Object[]).
906          */
mapArrayToArray(JsonParser p, DeserializationContext ctxt)907         protected Object[] mapArrayToArray(JsonParser p, DeserializationContext ctxt) throws IOException {
908             ObjectBuffer buffer = ctxt.leaseObjectBuffer();
909             Object[] values = buffer.resetAndStart();
910             int ptr = 0;
911             do {
912                 Object value = deserialize(p, ctxt);
913                 if (ptr >= values.length) {
914                     values = buffer.appendCompletedChunk(values);
915                     ptr = 0;
916                 }
917                 values[ptr++] = value;
918             } while (p.nextToken() != JsonToken.END_ARRAY);
919             return buffer.completeAndClearBuffer(values, ptr);
920         }
921 
922         /**
923          * Method called to map a JSON Object into a Java value.
924          */
mapObject(JsonParser p, DeserializationContext ctxt)925         protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOException
926         {
927             // will point to FIELD_NAME at this point, guaranteed
928             String key1 = p.getText();
929             p.nextToken();
930             Object value1 = deserialize(p, ctxt);
931 
932             String key2 = p.nextFieldName();
933             if (key2 == null) { // single entry; but we want modifiable
934                 LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(2);
935                 result.put(key1, value1);
936                 return result;
937             }
938             p.nextToken();
939             Object value2 = deserialize(p, ctxt);
940 
941             String key = p.nextFieldName();
942             if (key == null) {
943                 LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4);
944                 result.put(key1, value1);
945                 if (result.put(key2, value2) != null) {
946                     // 22-May-2020, tatu: [databind#2733] may need extra handling
947                     return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
948                 }
949                 return result;
950             }
951             // And then the general case; default map size is 16
952             LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
953             result.put(key1, value1);
954             if (result.put(key2, value2) != null) {
955                 // 22-May-2020, tatu: [databind#2733] may need extra handling
956                 return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
957             }
958 
959             do {
960                 p.nextToken();
961                 final Object newValue = deserialize(p, ctxt);
962                 final Object oldValue = result.put(key, newValue);
963                 if (oldValue != null) {
964                     return _mapObjectWithDups(p, ctxt, result, key, oldValue, newValue,
965                             p.nextFieldName());
966                 }
967             } while ((key = p.nextFieldName()) != null);
968             return result;
969         }
970 
971         // NOTE: copied from above (alas, no easy way to share/reuse)
972         // @since 2.12 (wrt [databind#2733]
_mapObjectWithDups(JsonParser p, DeserializationContext ctxt, final Map<String, Object> result, String key, Object oldValue, Object newValue, String nextKey)973         protected Object _mapObjectWithDups(JsonParser p, DeserializationContext ctxt,
974                 final Map<String, Object> result, String key,
975                 Object oldValue, Object newValue, String nextKey) throws IOException
976         {
977             final boolean squashDups = ctxt.isEnabled(StreamReadCapability.DUPLICATE_PROPERTIES);
978 
979             if (squashDups) {
980                 _squashDups(result, key, oldValue, newValue);
981             }
982 
983             while (nextKey != null) {
984                 p.nextToken();
985                 newValue = deserialize(p, ctxt);
986                 oldValue = result.put(nextKey, newValue);
987                 if ((oldValue != null) && squashDups) {
988                     _squashDups(result, key, oldValue, newValue);
989                 }
990                 nextKey = p.nextFieldName();
991             }
992 
993             return result;
994         }
995 
996         // NOTE: copied from above (alas, no easy way to share/reuse)
997         @SuppressWarnings("unchecked")
_squashDups(final Map<String, Object> result, String key, Object oldValue, Object newValue)998         private void _squashDups(final Map<String, Object> result, String key,
999                 Object oldValue, Object newValue)
1000         {
1001             if (oldValue instanceof List<?>) {
1002                 ((List<Object>) oldValue).add(newValue);
1003                 result.put(key, oldValue);
1004             } else {
1005                 ArrayList<Object> l = new ArrayList<>();
1006                 l.add(oldValue);
1007                 l.add(newValue);
1008                 result.put(key, l);
1009             }
1010         }
1011 
1012     }
1013 }
1014