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