aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/fasterxml/jackson/databind/deser
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/fasterxml/jackson/databind/deser')
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java10
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java9
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java47
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java31
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java27
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java61
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java55
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java122
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java4
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java257
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java6
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java7
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java31
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java44
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java5
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/FailingDeserializer.java10
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/TypeWrappedDeserializer.java6
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/impl/UnsupportedTypeDeserializer.java39
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/ArrayBlockingQueueDeserializer.java16
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicBooleanDeserializer.java25
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/ByteBufferDeserializer.java6
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java58
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/ContainerDeserializerBase.java8
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java43
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java6
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java37
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java37
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java11
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java9
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java202
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java27
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java62
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java23
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java923
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java55
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java34
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java14
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java2
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java6
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java1285
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java46
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java35
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java20
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java19
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java10
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/ThrowableDeserializer.java2
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java6
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java10
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java157
49 files changed, 2762 insertions, 1203 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
index 57a7eba6c..578afd940 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
@@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
/**
* Deserializer only used for abstract types used as placeholders during polymorphic
@@ -187,6 +188,13 @@ handledType().getName()));
@Override
public boolean isCachable() { return true; }
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ // 30-May-2020, tatu: Not sure if our choice here matters, but let's
+ // guess "POJO" is most likely. If need be, could get more creative
+ return LogicalType.POJO;
+ }
+
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
/* 23-Oct-2016, tatu: Not exactly sure what to do with this; polymorphic
@@ -241,7 +249,7 @@ handledType().getName()));
t = p.nextToken();
}
if ((t == JsonToken.FIELD_NAME) && _objectIdReader.maySerializeAsObject()
- && _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) {
+ && _objectIdReader.isValidReferencePropertyName(p.currentName(), p)) {
return _deserializeFromObjectId(p, ctxt);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java
index c0c544566..96866488b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java
@@ -237,13 +237,8 @@ public abstract class BasicDeserializerFactory
}
}
}
-
- // Sanity check: does the chosen ValueInstantiator have incomplete creators?
- if (instantiator.getIncompleteParameter() != null) {
- final AnnotatedParameter nonAnnotatedParam = instantiator.getIncompleteParameter();
- final AnnotatedWithParams ctor = nonAnnotatedParam.getOwner();
- throw new IllegalArgumentException("Argument #"+nonAnnotatedParam.getIndex()
- +" of constructor "+ctor+" has no property name annotation; must have name when multiple-parameter constructor annotated as Creator");
+ if (instantiator != null) {
+ instantiator = instantiator.createContextual(ctxt, beanDesc);
}
return instantiator;
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
index a82b945f8..ced28f0a9 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
@@ -5,6 +5,7 @@ import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.deser.impl.*;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
import com.fasterxml.jackson.databind.util.NameTransformer;
@@ -163,7 +164,7 @@ public class BeanDeserializer
}
return deserializeFromObject(p, ctxt);
}
- return _deserializeOther(p, ctxt, p.getCurrentToken());
+ return _deserializeOther(p, ctxt, p.currentToken());
}
protected final Object _deserializeOther(JsonParser p, DeserializationContext ctxt,
@@ -398,7 +399,7 @@ public class BeanDeserializer
TokenBuffer unknown = null;
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
List<BeanReferring> referrings = null;
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
@@ -583,23 +584,29 @@ public class BeanDeserializer
}
return bean;
}
- if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- JsonToken t = p.nextToken();
- if (t == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
- return null;
- }
- final Object value = deserialize(p, ctxt);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return value;
- }
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
+ final CoercionAction act = _findCoercionFromEmptyArray(ctxt);
+ final boolean unwrap = ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ if (unwrap || (act != CoercionAction.Fail)) {
JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
- return null;
+ switch (act) {
+ case AsEmpty:
+ return getEmptyValue(ctxt);
+ case AsNull:
+ case TryConvert:
+ return getNullValue(ctxt);
+ default:
+ }
+ return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
+ }
+ if (unwrap) {
+ final Object value = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
+ }
+ return value;
}
- return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
}
return ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
@@ -721,7 +728,7 @@ public class BeanDeserializer
Object bean)
throws IOException
{
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
}
@@ -788,7 +795,7 @@ public class BeanDeserializer
TokenBuffer tokens = new TokenBuffer(p, ctxt);
tokens.writeStartObject();
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value
@@ -914,7 +921,7 @@ public class BeanDeserializer
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
final ExternalTypeHandler ext = _externalTypeIdHandler.start();
- for (JsonToken t = p.getCurrentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
+ for (JsonToken t = p.currentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
t = p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
@@ -970,7 +977,7 @@ public class BeanDeserializer
TokenBuffer tokens = new TokenBuffer(p, ctxt);
tokens.writeStartObject();
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
index ebeeb48ab..fe1008577 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
@@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.exc.IgnoredPropertyException;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.ClassKey;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.*;
/**
@@ -232,8 +233,8 @@ public abstract class BeanDeserializerBase
;
// Any transformation we may need to apply?
- JsonFormat.Value format = beanDesc.findExpectedFormat(null);
- _serializationShape = (format == null) ? null : format.getShape();
+ final JsonFormat.Value format = beanDesc.findExpectedFormat(null);
+ _serializationShape = format.getShape();
_needViewProcesing = hasViews;
_vanillaProcessing = !_nonStandardCreation
@@ -989,6 +990,19 @@ public abstract class BeanDeserializerBase
@Override
public boolean isCachable() { return true; }
+ /**
+ * Accessor for checking whether this deserializer is operating
+ * in case-insensitive manner.
+ *
+ * @return True if this deserializer should match property names without
+ * considering casing; false if case has to match exactly.
+ *
+ * @since 2.12
+ */
+ public boolean isCaseInsensitive() {
+ return _beanProperties.isCaseInsensitive();
+ }
+
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
// although with possible caveats, yes, values can be updated
@@ -1045,6 +1059,11 @@ public abstract class BeanDeserializerBase
@Override
public JavaType getValueType() { return _beanType; }
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.POJO;
+ }
+
/**
* Accessor for iterating over properties this deserializer uses; with
* the exception that properties passed via Creator methods
@@ -1189,7 +1208,7 @@ public abstract class BeanDeserializerBase
}
}
// or, Object Ids Jackson explicitly sets
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t != null) {
// Most commonly, a scalar (int id, uuid String, ...)
if (t.isScalarValue()) {
@@ -1405,7 +1424,7 @@ public abstract class BeanDeserializerBase
return bean;
}
}
- return _valueInstantiator.createFromString(ctxt, p.getText());
+ return _deserializeFromString(p, ctxt);
}
/**
@@ -1457,7 +1476,7 @@ public abstract class BeanDeserializerBase
return bean;
}
}
- boolean value = (p.getCurrentToken() == JsonToken.VALUE_TRUE);
+ boolean value = (p.currentToken() == JsonToken.VALUE_TRUE);
return _valueInstantiator.createFromBoolean(ctxt, value);
}
@@ -1510,7 +1529,7 @@ public abstract class BeanDeserializerBase
/**
* @since 2.9
*/
- private final JsonDeserializer<Object> _delegateDeserializer() {
+ protected final JsonDeserializer<Object> _delegateDeserializer() {
JsonDeserializer<Object> deser = _delegateDeserializer;
if (deser == null) {
deser = _arrayDelegateDeserializer;
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java
index b43bec3dd..702edd847 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java
@@ -2,6 +2,7 @@ package com.fasterxml.jackson.databind.deser;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap;
@@ -10,6 +11,7 @@ import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader;
import com.fasterxml.jackson.databind.deser.impl.ValueInjector;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.util.Annotations;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Builder class used for aggregating deserialization information about
@@ -349,7 +351,8 @@ public class BeanDeserializerBuilder
Collection<SettableBeanProperty> props = _properties.values();
_fixAccess(props);
BeanPropertyMap propertyMap = BeanPropertyMap.construct(_config, props,
- _collectAliases(props));
+ _collectAliases(props),
+ _findCaseInsensitivity());
propertyMap.assignIndexes();
// view processing must be enabled if:
@@ -404,7 +407,7 @@ public class BeanDeserializerBuilder
if (!expBuildMethodName.isEmpty()) {
_context.reportBadDefinition(_beanDesc.getType(),
String.format("Builder class %s does not have build method (name: '%s')",
- _beanDesc.getBeanClass().getName(),
+ ClassUtil.getTypeDescription(_beanDesc.getType()),
expBuildMethodName));
}
} else {
@@ -415,17 +418,18 @@ public class BeanDeserializerBuilder
&& !rawBuildType.isAssignableFrom(rawValueType)
&& !rawValueType.isAssignableFrom(rawBuildType)) {
_context.reportBadDefinition(_beanDesc.getType(),
- String.format("Build method '%s' has wrong return type (%s), not compatible with POJO type (%s)",
+ String.format("Build method `%s` has wrong return type (%s), not compatible with POJO type (%s)",
_buildMethod.getFullName(),
- rawBuildType.getName(),
- valueType.getRawClass().getName()));
+ ClassUtil.getClassDescription(rawBuildType),
+ ClassUtil.getTypeDescription(valueType)));
}
}
// And if so, we can try building the deserializer
Collection<SettableBeanProperty> props = _properties.values();
_fixAccess(props);
BeanPropertyMap propertyMap = BeanPropertyMap.construct(_config, props,
- _collectAliases(props));
+ _collectAliases(props),
+ _findCaseInsensitivity());
propertyMap.assignIndexes();
boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
@@ -541,4 +545,15 @@ public class BeanDeserializerBuilder
}
return mapping;
}
+
+ // @since 2.12
+ protected boolean _findCaseInsensitivity() {
+ // 07-May-2020, tatu: First find combination of per-type config overrides (higher
+ // precedence) and per-type annotations (lower):
+ JsonFormat.Value format = _beanDesc.findExpectedFormat(null);
+ // and see if any of those has explicit definition; if not, use global baseline default
+ Boolean B = format.getFeature(JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
+ return (B == null) ? _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
+ : B.booleanValue();
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
index 9c84bedd8..fcf93b1fa 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator;
+import com.fasterxml.jackson.databind.util.BeanUtil;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition;
@@ -93,7 +94,7 @@ public class BeanDeserializerFactory
throws JsonMappingException
{
final DeserializationConfig config = ctxt.getConfig();
- // We may also have custom overrides:
+ // First: we may also have custom overrides:
JsonDeserializer<?> deser = _findCustomBeanDeserializer(type, config, beanDesc);
if (deser != null) {
// [databind#2392]
@@ -104,10 +105,8 @@ public class BeanDeserializerFactory
}
return (JsonDeserializer<Object>) deser;
}
- /* One more thing to check: do we have an exception type
- * (Throwable or its sub-classes)? If so, need slightly
- * different handling.
- */
+ // One more thing to check: do we have an exception type (Throwable or its
+ // sub-classes)? If so, need slightly different handling.
if (type.isThrowable()) {
return buildThrowableDeserializer(ctxt, type, beanDesc);
}
@@ -120,9 +119,8 @@ public class BeanDeserializerFactory
// Let's make it possible to materialize abstract types.
JavaType concreteType = materializeAbstractType(ctxt, type, beanDesc);
if (concreteType != null) {
- /* important: introspect actual implementation (abstract class or
- * interface doesn't have constructors, for one)
- */
+ // important: introspect actual implementation (abstract class or
+ // interface doesn't have constructors, for one)
beanDesc = config.introspect(concreteType);
return buildBeanDeserializer(ctxt, concreteType, beanDesc);
}
@@ -139,17 +137,31 @@ public class BeanDeserializerFactory
}
// For checks like [databind#1599]
_validateSubType(ctxt, type, beanDesc);
+
+ // 05-May-2020, tatu: [databind#2683] Let's actually pre-emptively catch
+ // certain types (for now, java.time.*) to give better error messages
+ deser = _findUnsupportedTypeDeserializer(ctxt, type, beanDesc);
+ if (deser != null) {
+ return (JsonDeserializer<Object>)deser;
+ }
+
// Use generic bean introspection to build deserializer
return buildBeanDeserializer(ctxt, type, beanDesc);
}
@Override
- public JsonDeserializer<Object> createBuilderBasedDeserializer(DeserializationContext ctxt,
- JavaType valueType, BeanDescription beanDesc, Class<?> builderClass)
- throws JsonMappingException
+ public JsonDeserializer<Object> createBuilderBasedDeserializer(
+ DeserializationContext ctxt, JavaType valueType, BeanDescription beanDesc,
+ Class<?> builderClass)
+ throws JsonMappingException
{
// First: need a BeanDescription for builder class
- JavaType builderType = ctxt.constructType(builderClass);
+ JavaType builderType;
+ if (ctxt.isEnabled(MapperFeature.INFER_BUILDER_TYPE_BINDINGS)) {
+ builderType = ctxt.getTypeFactory().constructParametricType(builderClass, valueType.getBindings());
+ } else {
+ builderType = ctxt.constructType(builderClass);
+ }
BeanDescription builderDesc = ctxt.getConfig().introspectForBuilder(builderType);
return buildBuilderBasedDeserializer(ctxt, valueType, builderDesc);
}
@@ -175,7 +187,27 @@ public class BeanDeserializerFactory
}
return deser;
}
-
+
+ /**
+ * Helper method called to see if given type, otherwise to be taken as POJO type,
+ * is "known but not supported" JDK type, and if so, return alternate handler
+ * (deserializer).
+ * Initially added to support more meaningful error messages when "Java 8 date/time"
+ * support module not registered.
+ *
+ * @since 2.12
+ */
+ protected JsonDeserializer<Object> _findUnsupportedTypeDeserializer(DeserializationContext ctxt,
+ JavaType type, BeanDescription beanDesc)
+ throws JsonMappingException
+ {
+ // 05-May-2020, tatu: Should we check for possible Shape override to "POJO"?
+ // (to let users force 'serialize-as-POJO'? Or not?
+ final String errorMsg = BeanUtil.checkUnsupportedType(type);
+ return (errorMsg == null) ? null
+ : new UnsupportedTypeDeserializer(type, errorMsg);
+ }
+
protected JavaType materializeAbstractType(DeserializationContext ctxt,
JavaType type, BeanDescription beanDesc)
throws JsonMappingException
@@ -428,8 +460,7 @@ public class BeanDeserializerFactory
/*
/**********************************************************
- /* Helper methods for Bean deserializer construction,
- /* overridable by sub-classes
+ /* Helper methods for Bean deserializer construction
/**********************************************************
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java
index 1ed76ce3b..fa15ed319 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java
@@ -5,6 +5,7 @@ import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.deser.impl.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.util.NameTransformer;
@@ -200,7 +201,7 @@ public class BuilderBasedDeserializer
return finishBuild(ctxt, deserializeFromObject(p, ctxt));
}
// and then others, generally requiring use of @JsonCreator
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
return finishBuild(ctxt, deserializeFromString(p, ctxt));
case JsonTokenId.ID_NUMBER_INT:
@@ -264,7 +265,7 @@ public class BuilderBasedDeserializer
throws IOException
{
Object bean = _valueInstantiator.createUsingDefault(ctxt);
- for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
+ for (; p.currentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
@@ -309,7 +310,7 @@ public class BuilderBasedDeserializer
return deserializeWithView(p, ctxt, bean, view);
}
}
- for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
+ for (; p.currentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
@@ -350,7 +351,7 @@ public class BuilderBasedDeserializer
// 04-Jan-2010, tatu: May need to collect unknown properties for polymorphic cases
TokenBuffer unknown = null;
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value
@@ -454,7 +455,7 @@ public class BuilderBasedDeserializer
return deserializeWithView(p, ctxt, builder, view);
}
}
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
// 23-Mar-2010, tatu: In some cases, we start with full JSON object too...
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
@@ -492,23 +493,29 @@ public class BuilderBasedDeserializer
}
return finishBuild(ctxt, builder);
}
- if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- JsonToken t = p.nextToken();
- if (t == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
- return null;
- }
- final Object value = deserialize(p, ctxt);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return value;
- }
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
+ final CoercionAction act = _findCoercionFromEmptyArray(ctxt);
+ final boolean unwrap = ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ if (unwrap || (act != CoercionAction.Fail)) {
JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
- return null;
+ switch (act) {
+ case AsEmpty:
+ return getEmptyValue(ctxt);
+ case AsNull:
+ case TryConvert:
+ return getNullValue(ctxt);
+ default:
+ }
+ return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
+ }
+ if (unwrap) {
+ final Object value = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
+ }
+ return value;
}
- return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
}
return ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
@@ -523,7 +530,7 @@ public class BuilderBasedDeserializer
Object bean, Class<?> activeView)
throws IOException
{
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
@@ -575,7 +582,7 @@ public class BuilderBasedDeserializer
}
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
- for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
+ for (; p.currentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
@@ -625,7 +632,7 @@ public class BuilderBasedDeserializer
tokens.writeStartObject();
Object builder = null;
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value
@@ -687,7 +694,7 @@ public class BuilderBasedDeserializer
throws IOException
{
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
- for (JsonToken t = p.getCurrentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
+ for (JsonToken t = p.currentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
SettableBeanProperty prop = _beanProperties.find(propName);
p.nextToken();
@@ -742,7 +749,7 @@ public class BuilderBasedDeserializer
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
final ExternalTypeHandler ext = _externalTypeIdHandler.start();
- for (JsonToken t = p.getCurrentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
+ for (JsonToken t = p.currentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
t = p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java
index cdc90ed2e..cf43d1f56 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.databind.deser;
+import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
@@ -8,7 +9,7 @@ import com.fasterxml.jackson.annotation.ObjectIdResolver;
import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
import com.fasterxml.jackson.core.JsonParser;
-
+import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId;
@@ -43,10 +44,16 @@ public abstract class DefaultDeserializationContext
protected DefaultDeserializationContext(DeserializerFactory df, DeserializerCache cache) {
super(df, cache);
}
-
+
+ protected DefaultDeserializationContext(DefaultDeserializationContext src,
+ DeserializationConfig config, JsonParser p, InjectableValues values) {
+ super(src, config, p, values);
+ }
+
+ // @since 2.12
protected DefaultDeserializationContext(DefaultDeserializationContext src,
- DeserializationConfig config, JsonParser jp, InjectableValues values) {
- super(src, config, jp, values);
+ DeserializationConfig config) {
+ super(src, config);
}
protected DefaultDeserializationContext(DefaultDeserializationContext src,
@@ -60,7 +67,7 @@ public abstract class DefaultDeserializationContext
protected DefaultDeserializationContext(DefaultDeserializationContext src) {
super(src);
}
-
+
/**
* Method needed to ensure that {@link ObjectMapper#copy} will work
* properly; specifically, that caches are cleared, but settings
@@ -82,9 +89,8 @@ public abstract class DefaultDeserializationContext
@Override
public ReadableObjectId findObjectId(Object id, ObjectIdGenerator<?> gen, ObjectIdResolver resolverType)
{
- /* 02-Apr-2015, tatu: As per [databind#742] should allow 'null', similar to how
- * missing id already works.
- */
+ // 02-Apr-2015, tatu: As per [databind#742] should allow 'null', similar to how
+ // missing id already works.
if (id == null) {
return null;
}
@@ -209,9 +215,8 @@ public abstract class DefaultDeserializationContext
if (deserDef instanceof JsonDeserializer) {
deser = (JsonDeserializer<?>) deserDef;
} else {
- /* Alas, there's no way to force return type of "either class
- * X or Y" -- need to throw an exception after the fact
- */
+ // Alas, there's no way to force return type of "either class
+ // X or Y" -- need to throw an exception after the fact
if (!(deserDef instanceof Class)) {
throw new IllegalStateException("AnnotationIntrospector returned deserializer definition of type "+deserDef.getClass().getName()+"; expected type JsonDeserializer or Class<JsonDeserializer> instead");
}
@@ -280,7 +285,7 @@ public abstract class DefaultDeserializationContext
/*
/**********************************************************
- /* Extended API
+ /* Extended API, life-cycle
/**********************************************************
*/
@@ -295,10 +300,73 @@ public abstract class DefaultDeserializationContext
* context instance.
*/
public abstract DefaultDeserializationContext createInstance(
- DeserializationConfig config, JsonParser jp, InjectableValues values);
+ DeserializationConfig config, JsonParser p, InjectableValues values);
+
+ public abstract DefaultDeserializationContext createDummyInstance(
+ DeserializationConfig config);
/*
/**********************************************************
+ /* Extended API, read methods
+ /**********************************************************
+ */
+
+ public Object readRootValue(JsonParser p, JavaType valueType,
+ JsonDeserializer<Object> deser, Object valueToUpdate)
+ throws IOException
+ {
+ if (_config.useRootWrapping()) {
+ return _unwrapAndDeserialize(p, valueType, deser, valueToUpdate);
+ }
+ if (valueToUpdate == null) {
+ return deser.deserialize(p, this);
+ }
+ return deser.deserialize(p, this, valueToUpdate);
+ }
+
+ protected Object _unwrapAndDeserialize(JsonParser p,
+ JavaType rootType, JsonDeserializer<Object> deser,
+ Object valueToUpdate)
+ throws IOException
+ {
+ PropertyName expRootName = _config.findRootName(rootType);
+ // 12-Jun-2015, tatu: Should try to support namespaces etc but...
+ String expSimpleName = expRootName.getSimpleName();
+ if (p.currentToken() != JsonToken.START_OBJECT) {
+ reportWrongTokenException(rootType, JsonToken.START_OBJECT,
+ "Current token not START_OBJECT (needed to unwrap root name '%s'), but %s",
+ expSimpleName, p.currentToken());
+ }
+ if (p.nextToken() != JsonToken.FIELD_NAME) {
+ reportWrongTokenException(rootType, JsonToken.FIELD_NAME,
+ "Current token not FIELD_NAME (to contain expected root name '%s'), but %s",
+ expSimpleName, p.currentToken());
+ }
+ String actualName = p.getCurrentName();
+ if (!expSimpleName.equals(actualName)) {
+ reportPropertyInputMismatch(rootType, actualName,
+ "Root name '%s' does not match expected ('%s') for type %s",
+ actualName, expSimpleName, rootType);
+ }
+ // ok, then move to value itself....
+ p.nextToken();
+ final Object result;
+ if (valueToUpdate == null) {
+ result = deser.deserialize(p, this);
+ } else {
+ result = deser.deserialize(p, this, valueToUpdate);
+ }
+ // and last, verify that we now get matching END_OBJECT
+ if (p.nextToken() != JsonToken.END_OBJECT) {
+ reportWrongTokenException(rootType, JsonToken.END_OBJECT,
+ "Current token not END_OBJECT (to match wrapper object with root name '%s'), but %s",
+ expSimpleName, p.currentToken());
+ }
+ return result;
+ }
+
+ /*
+ /**********************************************************
/* And then the concrete implementation class
/**********************************************************
*/
@@ -318,23 +386,27 @@ public abstract class DefaultDeserializationContext
super(df, null);
}
- protected Impl(Impl src,
- DeserializationConfig config, JsonParser jp, InjectableValues values) {
- super(src, config, jp, values);
+ private Impl(Impl src,
+ DeserializationConfig config, JsonParser p, InjectableValues values) {
+ super(src, config, p, values);
}
- protected Impl(Impl src) { super(src); }
-
- protected Impl(Impl src, DeserializerFactory factory) {
+ private Impl(Impl src) { super(src); }
+
+ private Impl(Impl src, DeserializerFactory factory) {
super(src, factory);
}
+ private Impl(Impl src, DeserializationConfig config) {
+ super(src, config);
+ }
+
@Override
public DefaultDeserializationContext copy() {
ClassUtil.verifyMustOverride(Impl.class, this, "copy");
- return new Impl(this);
+ return new Impl(this);
}
-
+
@Override
public DefaultDeserializationContext createInstance(DeserializationConfig config,
JsonParser p, InjectableValues values) {
@@ -342,8 +414,14 @@ public abstract class DefaultDeserializationContext
}
@Override
+ public DefaultDeserializationContext createDummyInstance(DeserializationConfig config) {
+ // need to be careful to create "real", not blue-print, instance
+ return new Impl(this, config);
+ }
+
+ @Override
public DefaultDeserializationContext with(DeserializerFactory factory) {
return new Impl(this, factory);
- }
+ }
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java
index 9ec8880df..d5c09f38a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java
@@ -381,7 +381,7 @@ public final class DeserializerCache
// but that won't work for other reasons. So do it here.
// (read: rewrite for 3.0)
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
- if ((format == null) || format.getShape() != JsonFormat.Shape.OBJECT) {
+ if (format.getShape() != JsonFormat.Shape.OBJECT) {
MapLikeType mlt = (MapLikeType) type;
if (mlt.isTrueMapType()) {
return factory.createMapDeserializer(ctxt,(MapType) mlt, beanDesc);
@@ -396,7 +396,7 @@ public final class DeserializerCache
* reasons. So do it here.
*/
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
- if ((format == null) || format.getShape() != JsonFormat.Shape.OBJECT) {
+ if (format.getShape() != JsonFormat.Shape.OBJECT) {
CollectionLikeType clt = (CollectionLikeType) type;
if (clt.isTrueCollectionType()) {
return factory.createCollectionDeserializer(ctxt, (CollectionType) clt, beanDesc);
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java
index 701ca6816..572e99bbc 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java
@@ -3,9 +3,11 @@ package com.fasterxml.jackson.databind.deser;
import java.io.IOException;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer;
-import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
+import com.fasterxml.jackson.databind.type.LogicalType;
/**
* Class that defines simple API implemented by objects that create value
@@ -32,6 +34,45 @@ public abstract class ValueInstantiator
{
/*
/**********************************************************
+ /* Introspection
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ public interface Gettable {
+ public ValueInstantiator getValueInstantiator();
+ }
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ /**
+ * "Contextualization" method that is called after construction but before first
+ * use, to allow instantiator access to context needed to possible resolve its
+ * dependencies.
+ *
+ * @param ctxt Currently active deserialization context: needed to (for example)
+ * resolving {@link com.fasterxml.jackson.databind.jsontype.TypeDeserializer}s.
+ *
+ * @return This instance, if no change, or newly constructed instance
+ *
+ * @throws JsonMappingException If there are issues with contextualization
+ *
+ * @since 2.12
+ */
+ public ValueInstantiator createContextual(DeserializationContext ctxt, BeanDescription beanDesc)
+ throws JsonMappingException
+ {
+ return this;
+ }
+
+ /*
+ /**********************************************************
/* Metadata accessors
/**********************************************************
*/
@@ -63,7 +104,7 @@ public abstract class ValueInstantiator
}
/**
- * Method that will return true if any of <code>canCreateXxx</code> method
+ * Method that will return true if any of {@code canCreateXxx} method
* returns true: that is, if there is any way that an instance could
* be created.
*/
@@ -77,7 +118,10 @@ public abstract class ValueInstantiator
/**
* Method that can be called to check whether a String-based creator
- * is available for this instantiator
+ * is available for this instantiator.
+ *<p>
+ * NOTE: does NOT include possible case of fallbacks, or coercion; only
+ * considers explicit creator.
*/
public boolean canCreateFromString() { return false; }
@@ -249,13 +293,15 @@ public abstract class ValueInstantiator
/*
/**********************************************************
- /* Instantiation methods for JSON scalar types
- /* (String, Number, Boolean)
+ /* Instantiation methods for JSON scalar types (String, Number, Boolean)
/**********************************************************
*/
-
+
public Object createFromString(DeserializationContext ctxt, String value) throws IOException {
- return _createFromStringFallbacks(ctxt, value);
+ return ctxt.handleMissingInstantiator(getValueClass(), this, ctxt.getParser(),
+ "no String-argument constructor/factory method to deserialize from String value ('%s')",
+ value);
+
}
public Object createFromInt(DeserializationContext ctxt, int value) throws IOException {
@@ -331,12 +377,6 @@ public abstract class ValueInstantiator
*/
public AnnotatedWithParams getWithArgsCreator() { return null; }
- /**
- * If an incomplete creator was found, this is the first parameter that
- * needs further annotation to help make the creator complete.
- */
- public AnnotatedParameter getIncompleteParameter() { return null; }
-
/*
/**********************************************************
/* Helper methods
@@ -345,27 +385,34 @@ public abstract class ValueInstantiator
/**
* @since 2.4 (demoted from <code>StdValueInstantiator</code>)
+ * @deprecated Since 2.12 should not handle coercions here
*/
+ @Deprecated // since 2.12
protected Object _createFromStringFallbacks(DeserializationContext ctxt, String value)
throws IOException
{
+ // also, empty Strings might be accepted as null Object...
+ if (value.length() == 0) {
+ if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
+ return null;
+ }
+ }
+
/* 28-Sep-2011, tatu: Ok this is not clean at all; but since there are legacy
* systems that expect conversions in some cases, let's just add a minimal
* patch (note: same could conceivably be used for numbers too).
*/
if (canCreateFromBoolean()) {
- String str = value.trim();
- if ("true".equals(str)) {
- return createFromBoolean(ctxt, true);
- }
- if ("false".equals(str)) {
- return createFromBoolean(ctxt, false);
- }
- }
- // also, empty Strings might be accepted as null Object...
- if (value.length() == 0) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
- return null;
+ // 29-May-2020, tatu: With 2.12 can and should use CoercionConfig so:
+ if (ctxt.findCoercionAction(LogicalType.Boolean, Boolean.class,
+ CoercionInputShape.String) == CoercionAction.TryConvert) {
+ String str = value.trim();
+ if ("true".equals(str)) {
+ return createFromBoolean(ctxt, true);
+ }
+ if ("false".equals(str)) {
+ return createFromBoolean(ctxt, false);
+ }
}
}
return ctxt.handleMissingInstantiator(getValueClass(), this, ctxt.getParser(),
@@ -375,19 +422,6 @@ public abstract class ValueInstantiator
/*
/**********************************************************
- /* Introspection
- /**********************************************************
- */
-
- /**
- * @since 2.9
- */
- public interface Gettable {
- public ValueInstantiator getValueInstantiator();
- }
-
- /*
- /**********************************************************
/* Standard Base implementation (since 2.8)
/**********************************************************
*/
@@ -421,4 +455,151 @@ public abstract class ValueInstantiator
return _valueType;
}
}
+
+ /**
+ * Delegating {@link ValueInstantiator} implementation meant as a base type
+ * that by default delegates methods to specified fallback instantiator.
+ *
+ * @since 2.12
+ */
+ public static class Delegating extends ValueInstantiator
+ implements java.io.Serializable
+ {
+ private static final long serialVersionUID = 1L;
+
+ protected final ValueInstantiator _delegate;
+
+ protected Delegating(ValueInstantiator delegate) {
+ _delegate = delegate;
+ }
+
+ @Override
+ public ValueInstantiator createContextual(DeserializationContext ctxt, BeanDescription beanDesc)
+ throws JsonMappingException
+ {
+ ValueInstantiator d = _delegate.createContextual(ctxt, beanDesc);
+ return (d == _delegate) ? this : new Delegating(d);
+ }
+
+ protected ValueInstantiator delegate() { return _delegate; }
+
+ @Override
+ public Class<?> getValueClass() { return delegate().getValueClass(); }
+
+ @Override
+ public String getValueTypeDesc() { return delegate().getValueTypeDesc(); }
+
+ @Override
+ public boolean canInstantiate() { return delegate().canInstantiate(); }
+
+ @Override
+ public boolean canCreateFromString() { return delegate().canCreateFromString(); }
+ @Override
+ public boolean canCreateFromInt() { return delegate().canCreateFromInt(); }
+ @Override
+ public boolean canCreateFromLong() { return delegate().canCreateFromLong(); }
+ @Override
+ public boolean canCreateFromDouble() { return delegate().canCreateFromDouble(); }
+ @Override
+ public boolean canCreateFromBoolean() { return delegate().canCreateFromBoolean(); }
+ @Override
+ public boolean canCreateUsingDefault() { return delegate().canCreateUsingDefault(); }
+ @Override
+ public boolean canCreateUsingDelegate() { return delegate().canCreateUsingDelegate(); }
+ @Override
+ public boolean canCreateUsingArrayDelegate() { return delegate().canCreateUsingArrayDelegate(); }
+ @Override
+ public boolean canCreateFromObjectWith() { return delegate().canCreateFromObjectWith(); }
+
+ @Override
+ public SettableBeanProperty[] getFromObjectArguments(DeserializationConfig config) {
+ return delegate().getFromObjectArguments(config);
+ }
+
+ @Override
+ public JavaType getDelegateType(DeserializationConfig config) {
+ return delegate().getDelegateType(config);
+ }
+
+ @Override
+ public JavaType getArrayDelegateType(DeserializationConfig config) {
+ return delegate().getArrayDelegateType(config);
+ }
+
+ /*
+ /**********************************************************
+ /* Creation methods
+ /**********************************************************
+ */
+
+ @Override
+ public Object createUsingDefault(DeserializationContext ctxt) throws IOException {
+ return delegate().createUsingDefault(ctxt);
+ }
+
+ @Override
+ public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) throws IOException {
+ return delegate().createFromObjectWith(ctxt, args);
+ }
+
+ @Override
+ public Object createFromObjectWith(DeserializationContext ctxt,
+ SettableBeanProperty[] props, PropertyValueBuffer buffer)
+ throws IOException {
+ return delegate().createFromObjectWith(ctxt, props, buffer);
+ }
+
+ @Override
+ public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) throws IOException {
+ return delegate().createUsingDelegate(ctxt, delegate);
+ }
+
+ @Override
+ public Object createUsingArrayDelegate(DeserializationContext ctxt, Object delegate) throws IOException {
+ return delegate().createUsingArrayDelegate(ctxt, delegate);
+ }
+
+ @Override
+ public Object createFromString(DeserializationContext ctxt, String value) throws IOException {
+ return delegate().createFromString(ctxt, value);
+ }
+
+ @Override
+ public Object createFromInt(DeserializationContext ctxt, int value) throws IOException {
+ return delegate().createFromInt(ctxt, value);
+ }
+
+ @Override
+ public Object createFromLong(DeserializationContext ctxt, long value) throws IOException {
+ return delegate().createFromLong(ctxt, value);
+ }
+
+ @Override
+ public Object createFromDouble(DeserializationContext ctxt, double value) throws IOException {
+ return delegate().createFromDouble(ctxt, value);
+ }
+
+ @Override
+ public Object createFromBoolean(DeserializationContext ctxt, boolean value) throws IOException {
+ return delegate().createFromBoolean(ctxt, value);
+ }
+
+ /*
+ /**********************************************************
+ /* Accessors for underlying creator objects (optional)
+ /**********************************************************
+ */
+
+ @Override
+ public AnnotatedWithParams getDefaultCreator() { return delegate().getDefaultCreator(); }
+
+ @Override
+ public AnnotatedWithParams getDelegateCreator() { return delegate().getDelegateCreator(); }
+
+ @Override
+ public AnnotatedWithParams getArrayDelegateCreator() { return delegate().getArrayDelegateCreator(); }
+
+ @Override
+ public AnnotatedWithParams getWithArgsCreator() { return delegate().getWithArgsCreator(); }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java
index c583d021b..3c9cf8b07 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayBuilderDeserializer.java
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.NameTransformer;
public class BeanAsArrayBuilderDeserializer
@@ -314,7 +315,8 @@ public class BeanAsArrayBuilderDeserializer
*/
return ctxt.reportBadDefinition(_beanType, String.format(
"Cannot support implicit polymorphic deserialization for POJOs-as-Arrays style: nominal type %s, actual type %s",
- _beanType.getRawClass().getName(), builder.getClass().getName()));
+ ClassUtil.getTypeDescription(_beanType),
+ builder.getClass().getName()));
}
}
continue;
@@ -350,7 +352,7 @@ public class BeanAsArrayBuilderDeserializer
// Let's start with failure
String message = "Cannot deserialize a POJO (of type %s) from non-Array representation (token: %s): "
+ "type/property designed to be serialized as JSON Array";
- return ctxt.handleUnexpectedToken(getValueType(ctxt), p.getCurrentToken(), p, message, _beanType.getRawClass().getName(), p.getCurrentToken());
+ return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, message, _beanType.getRawClass().getName(), p.currentToken());
// in future, may allow use of "standard" POJO serialization as well; if so, do:
//return _delegate.deserialize(p, ctxt);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java
index f51899d1e..f0116727b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanAsArrayDeserializer.java
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.*;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.NameTransformer;
/**
@@ -335,7 +336,8 @@ public class BeanAsArrayDeserializer
ctxt.reportBadDefinition(_beanType, String.format(
"Cannot support implicit polymorphic deserialization for POJOs-as-Arrays style: "
+"nominal type %s, actual type %s",
- _beanType.getRawClass().getName(), bean.getClass().getName()));
+ ClassUtil.getTypeDescription(_beanType),
+ ClassUtil.getClassDescription(bean)));
}
}
continue;
@@ -370,7 +372,8 @@ public class BeanAsArrayDeserializer
{
String message = "Cannot deserialize a POJO (of type %s) from non-Array representation (token: %s): "
+"type/property designed to be serialized as JSON Array";
- return ctxt.handleUnexpectedToken(getValueType(ctxt), p.getCurrentToken(), p, message, _beanType.getRawClass().getName(), p.getCurrentToken());
+ return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p,
+ message, ClassUtil.getTypeDescription(_beanType), p.currentToken());
// in future, may allow use of "standard" POJO serialization as well; if so, do:
//return _delegate.deserialize(p, ctxt);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java
index 8a3b6f152..68e17ad0b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java
@@ -178,13 +178,6 @@ public class BeanPropertyMap
_hashArea[ix+1] = newProp;
}
- @Deprecated // since 2.8
- public BeanPropertyMap(boolean caseInsensitive, Collection<SettableBeanProperty> props)
- {
- this(caseInsensitive, props, Collections.<String,List<PropertyName>>emptyMap(),
- Locale.getDefault());
- }
-
/**
* @since 2.8
*/
@@ -276,8 +269,22 @@ public class BeanPropertyMap
}
/**
+ * @since 2.12
+ */
+ public static BeanPropertyMap construct(MapperConfig<?> config,
+ Collection<SettableBeanProperty> props,
+ Map<String,List<PropertyName>> aliasMapping,
+ boolean caseInsensitive) {
+ return new BeanPropertyMap(caseInsensitive,
+ props, aliasMapping,
+ config.getLocale());
+ }
+
+ /**
* @since 2.11
+ * @deprecated since 2.12
*/
+ @Deprecated
public static BeanPropertyMap construct(MapperConfig<?> config,
Collection<SettableBeanProperty> props,
Map<String,List<PropertyName>> aliasMapping) {
@@ -295,12 +302,6 @@ public class BeanPropertyMap
return new BeanPropertyMap(caseInsensitive, props, aliasMapping);
}
- @Deprecated // since 2.9
- public static BeanPropertyMap construct(Collection<SettableBeanProperty> props, boolean caseInsensitive) {
- return construct(props, caseInsensitive,
- Collections.<String,List<PropertyName>>emptyMap());
- }
-
/**
* Fluent copy method that creates a new instance that is a copy
* of this instance except for one additional property that is
@@ -363,7 +364,7 @@ public class BeanPropertyMap
}
// should we try to re-index? Ordering probably changed but caller probably doesn't want changes...
// 26-Feb-2017, tatu: Probably SHOULD handle renaming wrt Aliases?
- return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs);
+ return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs, _locale);
}
/*
@@ -399,7 +400,7 @@ public class BeanPropertyMap
}
}
// should we try to re-index? Apparently no need
- return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs);
+ return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs, _locale);
}
@Deprecated // in 2.9.4 -- must call method that takes old and new property to avoid mismatch
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java
index 65706bf6b..193511992 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java
@@ -29,9 +29,10 @@ public class CreatorCollector {
protected final static String[] TYPE_DESCS = new String[] { "default",
"from-String", "from-int", "from-long", "from-double",
- "from-boolean", "delegate", "property-based" };
+ "from-boolean", "delegate", "property-based", "array-delegate"
+ };
- /// Type of bean being created
+ // Type of bean being created
final protected BeanDescription _beanDesc;
final protected boolean _canFixAccess;
@@ -46,13 +47,13 @@ public class CreatorCollector {
*
* @since 2.5
*/
- protected final AnnotatedWithParams[] _creators = new AnnotatedWithParams[9];
+ final protected AnnotatedWithParams[] _creators = new AnnotatedWithParams[9];
/**
* Bitmask of creators that were explicitly marked as creators; false for
* auto-detected (ones included base on naming and/or visibility, not
* annotation)
- *
+ *
* @since 2.5
*/
protected int _explicitCreators = 0;
@@ -296,31 +297,31 @@ public class CreatorCollector {
Class<?> newType = newOne.getRawParameterType(0);
if (oldType == newType) {
- // 13-Jul-2016, tatu: One more thing to check; since Enum
- // classes always have
- // implicitly created `valueOf()`, let's resolve in favor of
- // other implicit
- // creator (`fromString()`)
+ // 13-Jul-2016, tatu: One more thing to check; since Enum classes
+ // always have implicitly created `valueOf()`, let's resolve in
+ // favor of other implicit creator (`fromString()`)
if (_isEnumValueOf(newOne)) {
return false; // ignore
}
if (_isEnumValueOf(oldOne)) {
;
} else {
- throw new IllegalArgumentException(String.format(
- "Conflicting %s creators: already had %s creator %s, encountered another: %s",
- TYPE_DESCS[typeIndex],
- explicit ? "explicitly marked"
- : "implicitly discovered",
- oldOne, newOne));
+ _reportDuplicateCreator(typeIndex, explicit, oldOne, newOne);
}
}
// otherwise, which one to choose?
else if (newType.isAssignableFrom(oldType)) {
// new type more generic, use old
return false;
+ } else if (oldType.isAssignableFrom(newType)) {
+ // new type more specific, use it
+ ;
+ } else {
+ // 02-May-2020, tatu: Should this only result in exception if both
+ // explicit? Doing so could lead to arbitrary choice between
+ // multiple implicit creators tho?
+ _reportDuplicateCreator(typeIndex, explicit, oldOne, newOne);
}
- // new type more specific, use it
}
}
if (explicit) {
@@ -330,6 +331,17 @@ public class CreatorCollector {
return true;
}
+ // @since 2.12
+ protected void _reportDuplicateCreator(int typeIndex, boolean explicit,
+ AnnotatedWithParams oldOne, AnnotatedWithParams newOne) {
+ throw new IllegalArgumentException(String.format(
+ "Conflicting %s creators: already had %s creator %s, encountered another: %s",
+ TYPE_DESCS[typeIndex],
+ explicit ? "explicitly marked"
+ : "implicitly discovered",
+ oldOne, newOne));
+ }
+
/**
* Helper method for recognizing `Enum.valueOf()` factory method
*
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java
index 516866547..4d956c8f9 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java
@@ -350,9 +350,8 @@ public class ExternalTypeHandler
protected final void _deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object bean, int index, String typeId) throws IOException
{
- /* Ok: time to mix type id, value; and we will actually use "wrapper-array"
- * style to ensure we can handle all kinds of JSON constructs.
- */
+ // Ok: time to mix type id, value; and we will actually use "wrapper-array"
+ // style to ensure we can handle all kinds of JSON constructs.
JsonParser p2 = _tokens[index].asParser(p);
JsonToken t = p2.nextToken();
// 29-Sep-2015, tatu: As per [databind#942], nulls need special support
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/FailingDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/FailingDeserializer.java
index 9df6742bd..ba9b55299 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/FailingDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/FailingDeserializer.java
@@ -4,12 +4,11 @@ import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
/**
* Special bogus "serializer" that will throw
- * {@link JsonMappingException} if an attempt is made to deserialize
+ * {@link com.fasterxml.jackson.databind.exc.MismatchedInputException} if an attempt is made to deserialize
* a value. This is used as placeholder to avoid NPEs for uninitialized
* structured serializers or handlers.
*/
@@ -20,7 +19,12 @@ public class FailingDeserializer extends StdDeserializer<Object>
protected final String _message;
public FailingDeserializer(String m) {
- super(Object.class);
+ this(Object.class, m);
+ }
+
+ // @since 2.12
+ public FailingDeserializer(Class<?> rawType, String m) {
+ super(rawType);
_message = m;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/TypeWrappedDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/TypeWrappedDeserializer.java
index 8d5039de6..c9c291bf7 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/TypeWrappedDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/TypeWrappedDeserializer.java
@@ -6,6 +6,7 @@ import java.util.Collection;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
/**
* Simple deserializer that will call configured type deserializer, passing
@@ -32,6 +33,11 @@ public final class TypeWrappedDeserializer
_deserializer = (JsonDeserializer<Object>) deser;
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return _deserializer.logicalType();
+ }
+
@Override
public Class<?> handledType() {
return _deserializer.handledType();
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/UnsupportedTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/UnsupportedTypeDeserializer.java
new file mode 100644
index 000000000..a7a39148b
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/UnsupportedTypeDeserializer.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.databind.deser.impl;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+/**
+ * Special bogus "serializer" that will throw
+ * {@link com.fasterxml.jackson.databind.exc.MismatchedInputException}
+ * if an attempt is made to deserialize a value.
+ * This is used for "known unknown" types: types that we can recognize
+ * but can not support easily (or support known to be added via extension
+ * module).
+ *
+ * @since 2.12
+ */
+public class UnsupportedTypeDeserializer extends StdDeserializer<Object>
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final JavaType _type;
+
+ protected final String _message;
+
+ public UnsupportedTypeDeserializer(JavaType t, String m) {
+ super(t);
+ _type = t;
+ _message = m;
+ }
+
+ @Override
+ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ ctxt.reportBadDefinition(_type, _message);
+ return null;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ArrayBlockingQueueDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ArrayBlockingQueueDeserializer.java
index 94db83214..a27211b9e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ArrayBlockingQueueDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ArrayBlockingQueueDeserializer.java
@@ -85,18 +85,16 @@ public class ArrayBlockingQueueDeserializer
return null;
}
+ // NOTE: implementation changed between 2.11 and 2.12
@Override
- public Collection<Object> deserialize(JsonParser p, DeserializationContext ctxt,
- Collection<Object> result0) throws IOException
+ protected Collection<Object> _deserializeFromArray(JsonParser p, DeserializationContext ctxt,
+ Collection<Object> result0)
+ throws IOException
{
- if (result0 != null) {
- return super.deserialize(p, ctxt, result0);
- }
- // Ok: must point to START_ARRAY (or equivalent)
- if (!p.isExpectedStartArrayToken()) {
- return handleNonArray(p, ctxt, new ArrayBlockingQueue<>(1));
+ if (result0 == null) { // usual case
+ result0 = new ArrayList<>();
}
- result0 = super.deserialize(p, ctxt, new ArrayList<>());
+ result0 = super._deserializeFromArray(p, ctxt, result0);
if (result0.isEmpty()) {
return new ArrayBlockingQueue<>(1, false);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicBooleanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicBooleanDeserializer.java
index c8aa43173..8d75c01ef 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicBooleanDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicBooleanDeserializer.java
@@ -4,7 +4,10 @@ import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.type.LogicalType;
public class AtomicBooleanDeserializer extends StdScalarDeserializer<AtomicBoolean>
{
@@ -14,6 +17,24 @@ public class AtomicBooleanDeserializer extends StdScalarDeserializer<AtomicBoole
@Override
public AtomicBoolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
- return new AtomicBoolean(_parseBooleanPrimitive(ctxt, p, AtomicBoolean.class));
+ JsonToken t = p.currentToken();
+ if (t == JsonToken.VALUE_TRUE) {
+ return new AtomicBoolean(true);
+ }
+ if (t == JsonToken.VALUE_FALSE) {
+ return new AtomicBoolean(false);
+ }
+ // 12-Jun-2020, tatu: May look convoluted, but need to work correctly with
+ // CoercionConfig
+ Boolean b = _parseBoolean(p, ctxt, AtomicBoolean.class);
+ return (b == null) ? null : new AtomicBoolean(b.booleanValue());
}
-} \ No newline at end of file
+
+ @Override
+ public LogicalType logicalType() { return LogicalType.Boolean; }
+
+ @Override // @since 2.12
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return new AtomicBoolean(false);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ByteBufferDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ByteBufferDeserializer.java
index 633a7b8d6..1c2599b03 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ByteBufferDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ByteBufferDeserializer.java
@@ -5,6 +5,7 @@ import java.nio.ByteBuffer;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ByteBufferBackedOutputStream;
public class ByteBufferDeserializer extends StdScalarDeserializer<ByteBuffer>
@@ -13,6 +14,11 @@ public class ByteBufferDeserializer extends StdScalarDeserializer<ByteBuffer>
protected ByteBufferDeserializer() { super(ByteBuffer.class); }
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Binary;
+ }
+
@Override
public ByteBuffer deserialize(JsonParser parser, DeserializationContext cx) throws IOException {
byte[] b = parser.getBinaryValue();
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java
index 028279265..ce201285b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java
@@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
@@ -129,6 +130,11 @@ public class CollectionDeserializer
;
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Collection;
+ }
+
/*
/**********************************************************
/* Validation, post-processing (ResolvableDeserializer)
@@ -232,22 +238,18 @@ _containerType,
return (Collection<Object>) _valueInstantiator.createUsingDelegate(ctxt,
_delegateDeserializer.deserialize(p, ctxt));
}
+ // 16-May-2020, tatu: As per [dataformats-text#199] need to first check for
+ // possible Array-coercion and only after that String coercion
+ if (p.isExpectedStartArrayToken()) {
+ return _deserializeFromArray(p, ctxt, createDefaultInstance(ctxt));
+ }
// Empty String may be ok; bit tricky to check, however, since
// there is also possibility of "auto-wrapping" of single-element arrays.
// Hence we only accept empty String here.
if (p.hasToken(JsonToken.VALUE_STRING)) {
- // 16-May-2020, tatu: As [dataformats-text#199] need to avoid blocking
- // check to `isExpectedStartArrayToken()` (needed for CSV in-field array/list logic)
- // ... alas, trying to do this here leads to 2 unit test regressions so will
- // need to figure out safer mechanism.
-// if (_valueInstantiator.canCreateFromString()) {
- String str = p.getText();
- if (str.length() == 0) {
- return (Collection<Object>) _valueInstantiator.createFromString(ctxt, str);
-// }
- }
+ return _deserializeFromString(p, ctxt);
}
- return deserialize(p, ctxt, createDefaultInstance(ctxt));
+ return handleNonArray(p, ctxt, createDefaultInstance(ctxt));
}
/**
@@ -259,16 +261,35 @@ _containerType,
{
return (Collection<Object>) _valueInstantiator.createUsingDefault(ctxt);
}
-
+
@Override
public Collection<Object> deserialize(JsonParser p, DeserializationContext ctxt,
Collection<Object> result)
throws IOException
{
// Ok: must point to START_ARRAY (or equivalent)
- if (!p.isExpectedStartArrayToken()) {
- return handleNonArray(p, ctxt, result);
+ if (p.isExpectedStartArrayToken()) {
+ return _deserializeFromArray(p, ctxt, result);
}
+ return handleNonArray(p, ctxt, result);
+ }
+
+ @Override
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
+ throws IOException
+ {
+ // In future could check current token... for now this should be enough:
+ return typeDeserializer.deserializeTypedFromArray(p, ctxt);
+ }
+
+ /**
+ * @since 2.12
+ */
+ protected Collection<Object> _deserializeFromArray(JsonParser p, DeserializationContext ctxt,
+ Collection<Object> result)
+ throws IOException
+ {
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(result);
@@ -310,15 +331,6 @@ _containerType,
return result;
}
- @Override
- public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
- TypeDeserializer typeDeserializer)
- throws IOException
- {
- // In future could check current token... for now this should be enough:
- return typeDeserializer.deserializeTypedFromArray(p, ctxt);
- }
-
/**
* Helper method called when current token is no START_ARRAY. Will either
* throw an exception, or try to handle value as if member of implicit
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ContainerDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ContainerDeserializerBase.java
index 46c1537da..956ab3b0f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ContainerDeserializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ContainerDeserializerBase.java
@@ -126,14 +126,6 @@ public abstract class ContainerDeserializerBase<T>
*/
public abstract JsonDeserializer<Object> getContentDeserializer();
- /**
- * @since 2.9
- */
- @Override
- public ValueInstantiator getValueInstantiator() {
- return null;
- }
-
@Override // since 2.9
public AccessPattern getEmptyAccessPattern() {
// 02-Feb-2017, tatu: Empty containers are usually constructed as needed
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java
index b416764ff..9af1525b0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java
@@ -13,7 +13,9 @@ import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.StdDateFormat;
@@ -101,6 +103,11 @@ public class DateDeserializers
protected abstract DateBasedDeserializer<T> withDateFormat(DateFormat df, String formatStr);
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.DateTime;
+ }
+
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
@@ -185,7 +192,15 @@ public class DateDeserializers
if (p.hasToken(JsonToken.VALUE_STRING)) {
String str = p.getText().trim();
if (str.length() == 0) {
- return (Date) getEmptyValue(ctxt);
+ final CoercionAction act = _checkFromStringCoercion(ctxt, str);
+ switch (act) { // note: Fail handled above
+ case AsEmpty:
+ return new java.util.Date(0L);
+ case AsNull:
+ case TryConvert:
+ default:
+ }
+ return null;
}
synchronized (_customFormat) {
try {
@@ -239,6 +254,13 @@ public class DateDeserializers
return new CalendarDeserializer(this, df, formatString);
}
+ @Override // since 2.12
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.setTimeInMillis(0L);
+ return cal;
+ }
+
@Override
public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -284,6 +306,11 @@ public class DateDeserializers
protected DateDeserializer withDateFormat(DateFormat df, String formatString) {
return new DateDeserializer(this, df, formatString);
}
+
+ @Override // since 2.12
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ return new Date(0L);
+ }
@Override
public java.util.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
@@ -307,7 +334,12 @@ public class DateDeserializers
protected SqlDateDeserializer withDateFormat(DateFormat df, String formatString) {
return new SqlDateDeserializer(this, df, formatString);
}
-
+
+ @Override // since 2.12
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ return new java.sql.Date(0L);
+ }
+
@Override
public java.sql.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Date d = _parseDate(p, ctxt);
@@ -333,7 +365,12 @@ public class DateDeserializers
protected TimestampDeserializer withDateFormat(DateFormat df, String formatString) {
return new TimestampDeserializer(this, df, formatString);
}
-
+
+ @Override // since 2.12
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ return new Timestamp(0L);
+ }
+
@Override
public java.sql.Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java
index f4368780e..764e534ce 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java
@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
/**
@@ -152,6 +153,11 @@ public abstract class DelegatingDeserializer
return _delegatee.getEmptyValue(ctxt);
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return _delegatee.logicalType();
+ }
+
@Override
public Collection<Object> getKnownPropertyNames() { return _delegatee.getKnownPropertyNames(); }
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java
index 2018daf15..ae0957251 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.CompactStringObjectMap;
import com.fasterxml.jackson.databind.util.EnumResolver;
@@ -164,24 +165,23 @@ public class EnumDeserializer
@Override
public boolean isCachable() { return true; }
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Enum;
+ }
+
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
+ String text;
JsonToken curr = p.currentToken();
// Usually should just get string value:
if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) {
- CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
- ? _getToStringLookup(ctxt) : _lookupByName;
- final String name = p.getText();
- Object result = lookup.find(name);
- if (result == null) {
- return _deserializeAltString(p, ctxt, lookup, name);
- }
- return result;
- }
+ text = p.getText();
+
// But let's consider int acceptable as well (if within ordinal range)
- if (curr == JsonToken.VALUE_NUMBER_INT) {
+ } else if (curr == JsonToken.VALUE_NUMBER_INT) {
// ... unless told not to do that
int index = p.getIntValue();
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
@@ -202,8 +202,23 @@ public class EnumDeserializer
_enumsByIndex.length-1);
}
return null;
+ } else if (curr == JsonToken.START_OBJECT) {
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ } else {
+ return _deserializeOther(p, ctxt);
+ }
+
+ CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
+ ? _getToStringLookup(ctxt) : _lookupByName;
+ Object result = lookup.find(text);
+ if (result == null) {
+ String trimmed = text.trim();
+ if ((trimmed == text) || (result = lookup.find(trimmed)) == null) {
+ return _deserializeAltString(p, ctxt, lookup, trimmed);
+ }
}
- return _deserializeOther(p, ctxt);
+ return result;
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java
index cfc1133dc..8fde2a371 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java
@@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator;
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
@@ -200,6 +201,11 @@ public class EnumMapDeserializer
&& (_valueTypeDeserializer == null);
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Map;
+ }
+
/*
/**********************************************************
/* ContainerDeserializerBase API
@@ -211,7 +217,11 @@ public class EnumMapDeserializer
return _valueDeserializer;
}
- // Must override since we do not expose ValueInstantiator
+ @Override
+ public ValueInstantiator getValueInstantiator() {
+ return _valueInstantiator;
+ }
+
@Override // since 2.9
public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
return constructMap(ctxt);
@@ -234,34 +244,21 @@ public class EnumMapDeserializer
return (EnumMap<?,?>) _valueInstantiator.createUsingDelegate(ctxt,
_delegateDeserializer.deserialize(p, ctxt));
}
- // Ok: must point to START_OBJECT (or similar)
+
switch (p.currentTokenId()) {
case JsonTokenId.ID_START_OBJECT:
case JsonTokenId.ID_END_OBJECT:
case JsonTokenId.ID_FIELD_NAME:
return deserialize(p, ctxt, constructMap(ctxt));
case JsonTokenId.ID_STRING:
- return (EnumMap<?,?>) _valueInstantiator.createFromString(ctxt, p.getText());
+ // (empty) String may be ok however; or single-String-arg ctor
+ return _deserializeFromString(p, ctxt);
case JsonTokenId.ID_START_ARRAY:
- {
- JsonToken t = p.nextToken();
- if (t == JsonToken.END_ARRAY) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
- return null;
- }
- } else if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- final Object value = deserialize(p, ctxt);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return (EnumMap<?,?>) value;
- }
- }
- return (EnumMap<?,?>) ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
+ // Empty array, or single-value wrapped in array?
+ return _deserializeFromArray(p, ctxt);
default:
}
- // slightly redundant (since String was passed above), but also handles empty array case:
- return _deserializeFromEmpty(p, ctxt);
+ return (EnumMap<?,?>) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java
index e5286ae78..7b415a5ba 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java
@@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
/**
@@ -126,7 +127,7 @@ public class EnumSetDeserializer
/* Basic metadata
/**********************************************************
*/
-
+
/**
* Because of costs associated with constructing Enum resolvers,
* let's cache instances by default.
@@ -140,6 +141,11 @@ public class EnumSetDeserializer
return true;
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Collection;
+ }
+
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
return Boolean.TRUE;
@@ -165,6 +171,9 @@ public class EnumSetDeserializer
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
+ // 07-May-2020, tatu: Is the argument `EnumSet.class` correct here?
+ // In a way seems like it should rather refer to value class... ?
+ // (as it's individual value of element type, not Container)...
final Boolean unwrapSingle = findFormatFeature(ctxt, property, EnumSet.class,
JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
JsonDeserializer<?> deser = _enumDeserializer;
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java
index 479130923..06488b195 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java
@@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator;
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
@@ -99,11 +100,19 @@ class FactoryBasedEnumDeserializer
return Boolean.FALSE;
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Enum;
+ }
+
// since 2.9.7: should have been the case earlier but
@Override
public boolean isCachable() { return true; }
@Override
+ public ValueInstantiator getValueInstantiator() { return _valueInstantiator; }
+
+ @Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
Object value;
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java
index b13383593..52e52832d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java
@@ -15,7 +15,10 @@ import java.util.regex.Pattern;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.util.VersionUtil;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
@@ -67,13 +70,15 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
TimeZone.class,
InetAddress.class,
InetSocketAddress.class,
+
+ // Special impl:
StringBuilder.class,
};
}
-
+
/*
/**********************************************************
- /* Deserializer implementations
+ /* Life-cycle
/**********************************************************
*/
@@ -85,7 +90,7 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
* Factory method for trying to find a deserializer for one of supported
* types that have simple from-String serialization.
*/
- public static Std findDeserializer(Class<?> rawType)
+ public static FromStringDeserializer<?> findDeserializer(Class<?> rawType)
{
int kind = 0;
if (rawType == File.class) {
@@ -113,12 +118,17 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
} else if (rawType == InetSocketAddress.class) {
kind = Std.STD_INET_SOCKET_ADDRESS;
} else if (rawType == StringBuilder.class) {
- kind = Std.STD_STRING_BUILDER;
+ return new StringBuilderDeserializer();
} else {
return null;
}
return new Std(rawType, kind);
}
+
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.OtherScalar;
+ }
/*
/**********************************************************
@@ -132,33 +142,49 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
{
// Let's get textual value, possibly via coercion from other scalar types
String text = p.getValueAsString();
- if (text != null) { // has String representation
- if (text.length() == 0 || (text = text.trim()).length() == 0) {
- // Usually should become null; but not always
- return _deserializeFromEmptyString();
- }
- Exception cause = null;
- try {
- // 19-May-2017, tatu: Used to require non-null result (assuming `null`
- // indicated error; but that seems wrong. Should be able to return
- // `null` as value.
- return _deserialize(text, ctxt);
- } catch (IllegalArgumentException | MalformedURLException e) {
- cause = e;
- }
- // note: `cause` can't be null
- String msg = "not a valid textual representation";
- String m2 = cause.getMessage();
- if (m2 != null) {
- msg = msg + ", problem: "+m2;
+ if (text == null) {
+ JsonToken t = p.currentToken();
+ if (t != JsonToken.START_OBJECT) {
+ return (T) _deserializeFromOther(p, ctxt, t);
}
- // 05-May-2016, tatu: Unlike most usage, this seems legit, so...
- JsonMappingException e = ctxt.weirdStringException(text, _valueClass, msg);
- e.initCause(cause);
- throw e;
- // nothing to do here, yet? We'll fail anyway
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
}
- JsonToken t = p.currentToken();
+ if (text.length() == 0 || (text = text.trim()).length() == 0) {
+ // 09-Jun-2020, tatu: Commonly `null` but may coerce to "empty" as well
+ return (T) _deserializeFromEmptyString(ctxt);
+ }
+ Exception cause = null;
+ try {
+ // 19-May-2017, tatu: Used to require non-null result (assuming `null`
+ // indicated error; but that seems wrong. Should be able to return
+ // `null` as value.
+ return _deserialize(text, ctxt);
+ } catch (IllegalArgumentException | MalformedURLException e) {
+ cause = e;
+ }
+ // note: `cause` can't be null
+ String msg = "not a valid textual representation";
+ String m2 = cause.getMessage();
+ if (m2 != null) {
+ msg = msg + ", problem: "+m2;
+ }
+ // 05-May-2016, tatu: Unlike most usage, this seems legit, so...
+ JsonMappingException e = ctxt.weirdStringException(text, _valueClass, msg);
+ e.initCause(cause);
+ throw e;
+ }
+
+ /**
+ * Main method from trying to deserialize actual value from non-empty
+ * String.
+ */
+ protected abstract T _deserialize(String value, DeserializationContext ctxt) throws IOException;
+
+ // @since 2.12
+ protected Object _deserializeFromOther(JsonParser p, DeserializationContext ctxt,
+ JsonToken t) throws IOException
+ {
// [databind#381]
if (t == JsonToken.START_ARRAY) {
return _deserializeFromArray(p, ctxt);
@@ -170,15 +196,18 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
return null;
}
if (_valueClass.isAssignableFrom(ob.getClass())) {
- return (T) ob;
+ return ob;
}
return _deserializeEmbedded(ob, ctxt);
}
- return (T) ctxt.handleUnexpectedToken(_valueClass, p);
+ return ctxt.handleUnexpectedToken(_valueClass, p);
}
-
- protected abstract T _deserialize(String value, DeserializationContext ctxt) throws IOException;
+ /**
+ * Overridable method to allow coercion from embedded value that is neither
+ * {@code null} nor directly assignable to target type.
+ * Used, for example, by {@link UUIDDeserializer} to coerce from {@code byte[]}.
+ */
protected T _deserializeEmbedded(Object ob, DeserializationContext ctxt) throws IOException {
// default impl: error out
ctxt.reportInputMismatch(this,
@@ -187,10 +216,41 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
return null;
}
- protected T _deserializeFromEmptyString() throws IOException {
+ @Deprecated // since 2.12 -- override variant that takes context
+ protected final T _deserializeFromEmptyString() throws IOException {
return null;
}
+ /**
+ * @since 2.12
+ */
+ protected Object _deserializeFromEmptyString(DeserializationContext ctxt) throws IOException {
+ CoercionAction act = ctxt.findCoercionAction(logicalType(), _valueClass,
+ CoercionInputShape.EmptyString);
+ if (act == CoercionAction.Fail) {
+ ctxt.reportInputMismatch(this,
+"Cannot coerce empty String (\"\") to %s (but could if enabling coercion using `CoercionConfig`)",
+_coercedTypeDesc());
+ }
+ if (act == CoercionAction.AsNull) {
+ return getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return getEmptyValue(ctxt);
+ }
+ // 09-Jun-2020, tatu: semantics for `TryConvert` are bit interesting due to
+ // historical reasons
+ return _deserializeFromEmptyStringDefault(ctxt);
+ }
+
+ /**
+ * @since 2.12
+ */
+ protected Object _deserializeFromEmptyStringDefault(DeserializationContext ctxt) throws IOException {
+ // by default, "as-null", but overridable by sub-classes
+ return getNullValue(ctxt);
+ }
+
/*
/**********************************************************
/* A general-purpose implementation
@@ -219,10 +279,11 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
public final static int STD_TIME_ZONE = 10;
public final static int STD_INET_ADDRESS = 11;
public final static int STD_INET_SOCKET_ADDRESS = 12;
- public final static int STD_STRING_BUILDER = 13;
+ // No longer implemented here since 2.12
+ // public final static int STD_STRING_BUILDER = 13;
protected final int _kind;
-
+
protected Std(Class<?> valueType, int kind) {
super(valueType);
_kind = kind;
@@ -297,27 +358,32 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
}
// host or unbracketed IPv6, without port number
return new InetSocketAddress(value, 0);
- case STD_STRING_BUILDER:
- return new StringBuilder(value);
}
VersionUtil.throwInternal();
return null;
}
- @Override
- protected Object _deserializeFromEmptyString() throws IOException {
- // As per [databind#398], URI requires special handling
- if (_kind == STD_URI) {
+ @Override // since 2.12
+ public Object getEmptyValue(DeserializationContext ctxt)
+ throws JsonMappingException
+ {
+ switch (_kind) {
+ case STD_URI:
+ // As per [databind#398], URI requires special handling
return URI.create("");
- }
- // As per [databind#1123], Locale too
- if (_kind == STD_LOCALE) {
+ case STD_LOCALE:
+ // As per [databind#1123], Locale too
return Locale.ROOT;
}
- if (_kind == STD_STRING_BUILDER) {
- return new StringBuilder();
- }
- return super._deserializeFromEmptyString();
+ return super.getEmptyValue(ctxt);
+ }
+
+ @Override
+ protected Object _deserializeFromEmptyStringDefault(DeserializationContext ctxt) throws IOException {
+ // 09-Jun-2020, tatu: For backwards compatibility deserialize "as-empty"
+ // as URI and Locale did that in 2.11 (and StringBuilder probably ought to).
+ // But doing this here instead of super-class lets UUID return "as-null" instead
+ return getEmptyValue(ctxt);
}
protected int _firstHyphenOrUnderscore(String str)
@@ -331,4 +397,42 @@ public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
return -1;
}
}
+
+ // @since 2.12 to simplify logic a bit: should not use coercions when reading
+ // String Values
+ static class StringBuilderDeserializer extends FromStringDeserializer<Object>
+ {
+ public StringBuilderDeserializer() {
+ super(StringBuilder.class);
+ }
+
+ @Override
+ public LogicalType logicalType() {
+ return LogicalType.Textual;
+ }
+
+ @Override
+ public Object getEmptyValue(DeserializationContext ctxt)
+ throws JsonMappingException
+ {
+ return new StringBuilder();
+ }
+
+ @Override
+ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
+ {
+ String text = p.getValueAsString();
+ if (text != null) {
+ return _deserialize(text, ctxt);
+ }
+ return super.deserialize(p, ctxt);
+ }
+
+ @Override
+ protected Object _deserialize(String value, DeserializationContext ctxt)
+ throws IOException
+ {
+ return new StringBuilder(value);
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java
index fcc4ab82e..26de3c29a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.node.*;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.RawValue;
/**
@@ -184,10 +185,14 @@ abstract class BaseNodeDeserializer<T extends JsonNode>
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
}
- /* 07-Nov-2014, tatu: When investigating [databind#604], realized that it makes
- * sense to also mark this is cachable, since lookup not exactly free, and
- * since it's not uncommon to "read anything"
- */
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Untyped;
+ }
+
+ // 07-Nov-2014, tatu: When investigating [databind#604], realized that it makes
+ // sense to also mark this is cachable, since lookup not exactly free, and
+ // since it's not uncommon to "read anything"
@Override
public boolean isCachable() { return true; }
@@ -230,6 +235,20 @@ abstract class BaseNodeDeserializer<T extends JsonNode>
"Duplicate field '%s' for `ObjectNode`: not allowed when `DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY` enabled",
fieldName);
}
+ // [databind#2732]: Special case for XML; automatically coerce into `ArrayNode`
+ if (ctxt.isEnabled(StreamReadCapability.DUPLICATE_PROPERTIES)) {
+ // Note that ideally we wouldn't have to shuffle things but... Map.putIfAbsent()
+ // only added in JDK 8, to efficiently check for add. So...
+ if (oldValue.isArray()) { // already was array, to append
+ ((ArrayNode) oldValue).add(newValue);
+ objectNode.replace(fieldName, oldValue);
+ } else { // was not array, convert
+ ArrayNode arr = nodeFactory.arrayNode();
+ arr.add(oldValue);
+ arr.add(newValue);
+ objectNode.replace(fieldName, arr);
+ }
+ }
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java
index b99f66968..cba73fb4a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java
@@ -15,10 +15,11 @@ import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
/**
- * Basic serializer that can take JSON "Object" structure and
+ * Basic deserializer that can take JSON "Object" structure and
* construct a {@link java.util.Map} instance, with typed contents.
*<p>
* Note: for untyped content (one indicated by passing Object.class
@@ -333,6 +334,11 @@ public class MapDeserializer
&& (_ignorableProperties == null);
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Map;
+ }
+
@Override
@SuppressWarnings("unchecked")
public Map<Object,Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
@@ -349,36 +355,26 @@ public class MapDeserializer
getValueInstantiator(), p,
"no default constructor found");
}
- // Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT
- JsonToken t = p.getCurrentToken();
- if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
- // (empty) String may be ok however; or single-String-arg ctor
- if (t == JsonToken.VALUE_STRING) {
- return (Map<Object,Object>) _valueInstantiator.createFromString(ctxt, p.getText());
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_START_OBJECT:
+ case JsonTokenId.ID_END_OBJECT:
+ case JsonTokenId.ID_FIELD_NAME:
+ final Map<Object,Object> result = (Map<Object,Object>) _valueInstantiator.createUsingDefault(ctxt);
+ if (_standardStringKey) {
+ _readAndBindStringKeyMap(p, ctxt, result);
+ return result;
}
- if (t == JsonToken.START_ARRAY) {
- if (p.nextToken() == JsonToken.END_ARRAY) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
- return null;
- }
- } else if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- final Object value = deserialize(p, ctxt);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return (Map<Object,Object>) value;
- }
- // fall through to failing case
- }
- return (Map<Object,Object>) ctxt.handleUnexpectedToken(getValueType(ctxt), t, p, null);
- }
- final Map<Object,Object> result = (Map<Object,Object>) _valueInstantiator.createUsingDefault(ctxt);
- if (_standardStringKey) {
- _readAndBindStringKeyMap(p, ctxt, result);
+ _readAndBind(p, ctxt, result);
return result;
+ case JsonTokenId.ID_STRING:
+ // (empty) String may be ok however; or single-String-arg ctor
+ return _deserializeFromString(p, ctxt);
+ case JsonTokenId.ID_START_ARRAY:
+ // Empty array, or single-value wrapped in array?
+ return _deserializeFromArray(p, ctxt);
+ default:
}
- _readAndBind(p, ctxt, result);
- return result;
+ return (Map<Object,Object>) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
@SuppressWarnings("unchecked")
@@ -391,7 +387,7 @@ public class MapDeserializer
p.setCurrentValue(result);
// Ok: must point to START_OBJECT or FIELD_NAME
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME) {
return (Map<Object,Object>) ctxt.handleUnexpectedToken(getMapClass(), p);
}
@@ -448,7 +444,7 @@ public class MapDeserializer
if (p.isExpectedStartObjectToken()) {
keyStr = p.nextFieldName();
} else {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t != JsonToken.FIELD_NAME) {
if (t == JsonToken.END_OBJECT) {
return;
@@ -512,7 +508,7 @@ public class MapDeserializer
if (p.isExpectedStartObjectToken()) {
key = p.nextFieldName();
} else {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_OBJECT) {
return;
}
@@ -651,7 +647,7 @@ public class MapDeserializer
if (p.isExpectedStartObjectToken()) {
keyStr = p.nextFieldName();
} else {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_OBJECT) {
return;
}
@@ -720,7 +716,7 @@ public class MapDeserializer
if (p.isExpectedStartObjectToken()) {
key = p.nextFieldName();
} else {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_OBJECT) {
return;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java
index 74340131f..2aa0e9adf 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java
@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
/**
* Basic serializer that can take JSON "Object" structure and
@@ -103,6 +104,12 @@ public class MapEntryDeserializer
keyDeser, (JsonDeserializer<Object>) valueDeser, valueTypeDeser);
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ // Slightly tricky, could consider POJO too?
+ return LogicalType.Map;
+ }
+
/*
/**********************************************************
/* Validation, post-processing (ResolvableDeserializer)
@@ -155,7 +162,10 @@ public class MapEntryDeserializer
public JsonDeserializer<Object> getContentDeserializer() {
return _valueDeserializer;
}
-
+
+ // 31-May-2020, tatu: Should probably define but we don't have it yet
+// public ValueInstantiator getValueInstantiator() { }
+
/*
/**********************************************************
/* JsonDeserializer API
@@ -168,13 +178,14 @@ public class MapEntryDeserializer
{
// Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT
JsonToken t = p.currentToken();
- if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
- // String may be ok however:
- // slightly redundant (since String was passed above), but
- return _deserializeFromEmpty(p, ctxt);
- }
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
+ } else if (t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
+ // Empty array, or single-value wrapped in array?
+ if (t == JsonToken.START_ARRAY) {
+ return _deserializeFromArray(p, ctxt);
+ }
+ return (Map.Entry<Object,Object>) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
if (t != JsonToken.FIELD_NAME) {
if (t == JsonToken.END_OBJECT) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java
index aa4dad45a..2088fbca2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java
@@ -9,7 +9,10 @@ import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
/**
@@ -126,6 +129,9 @@ public class NumberDeserializers
{
private static final long serialVersionUID = 1L;
+ // @since 2.12
+ protected final LogicalType _logicalType;
+
protected final T _nullValue;
// @since 2.9
@@ -133,13 +139,21 @@ public class NumberDeserializers
protected final boolean _primitive;
- protected PrimitiveOrWrapperDeserializer(Class<T> vc, T nvl, T empty) {
+ // @since 2.12
+ protected PrimitiveOrWrapperDeserializer(Class<T> vc, LogicalType logicalType,
+ T nvl, T empty) {
super(vc);
+ _logicalType = logicalType;
_nullValue = nvl;
_emptyValue = empty;
_primitive = vc.isPrimitive();
}
+ @Deprecated // since 2.12
+ protected PrimitiveOrWrapperDeserializer(Class<T> vc, T nvl, T empty) {
+ this(vc, LogicalType.OtherScalar, nvl, empty);
+ }
+
@Override
public AccessPattern getNullAccessPattern() {
// 02-Feb-2017, tatu: For primitives we must dynamically check (and possibly throw
@@ -169,6 +183,11 @@ public class NumberDeserializers
public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
return _emptyValue;
}
+
+ @Override // since 2.12
+ public final LogicalType logicalType() {
+ return _logicalType;
+ }
}
/*
@@ -188,20 +207,23 @@ public class NumberDeserializers
public BooleanDeserializer(Class<Boolean> cls, Boolean nvl)
{
- super(cls, nvl, Boolean.FALSE);
+ super(cls, LogicalType.Boolean, nvl, Boolean.FALSE);
}
@Override
public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
- return _parseBoolean(p, ctxt);
+ if (_primitive) {
+ return _parseBooleanPrimitive(p, ctxt);
+ }
+ return _parseBoolean(p, ctxt, _valueClass);
}
// Since we can never have type info ("natural type"; String, Boolean, Integer, Double):
@@ -211,60 +233,17 @@ public class NumberDeserializers
TypeDeserializer typeDeserializer)
throws IOException
{
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
- return _parseBoolean(p, ctxt);
- }
-
- protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt)
- throws IOException
- {
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_NULL) {
- return (Boolean) _coerceNullToken(ctxt, _primitive);
- }
- if (t == JsonToken.START_ARRAY) { // unwrapping?
- return _deserializeFromArray(p, ctxt);
- }
- // should accept ints too, (0 == false, otherwise true)
- if (t == JsonToken.VALUE_NUMBER_INT) {
- return Boolean.valueOf(_parseBooleanFromInt(p, ctxt));
- }
- // And finally, let's allow Strings to be converted too
- if (t == JsonToken.VALUE_STRING) {
- String text = p.getText().trim();
- // [databind#422]: Allow aliases
- if ("true".equals(text) || "True".equals(text)) {
- _verifyStringForScalarCoercion(ctxt, text);
- return Boolean.TRUE;
- }
- if ("false".equals(text) || "False".equals(text)) {
- _verifyStringForScalarCoercion(ctxt, text);
- return Boolean.FALSE;
- }
- if (text.length() == 0) {
- return (Boolean) _coerceEmptyString(ctxt, _primitive);
- }
- if (_hasTextualNull(text)) {
- return (Boolean) _coerceTextualNull(ctxt, _primitive);
- }
- return (Boolean) ctxt.handleWeirdStringValue(_valueClass, text,
- "only \"true\" or \"false\" recognized");
- }
- // usually caller should have handled but:
- if (t == JsonToken.VALUE_TRUE) {
- return Boolean.TRUE;
- }
- if (t == JsonToken.VALUE_FALSE) {
- return Boolean.FALSE;
+ if (_primitive) {
+ return _parseBooleanPrimitive(p, ctxt);
}
- // Otherwise, no can do:
- return (Boolean) ctxt.handleUnexpectedToken(_valueClass, p);
+ return _parseBoolean(p, ctxt, _valueClass);
}
}
@@ -276,67 +255,83 @@ public class NumberDeserializers
final static ByteDeserializer primitiveInstance = new ByteDeserializer(Byte.TYPE, (byte) 0);
final static ByteDeserializer wrapperInstance = new ByteDeserializer(Byte.class, null);
-
+
public ByteDeserializer(Class<Byte> cls, Byte nvl)
{
- super(cls, nvl, (byte) 0);
+ super(cls, LogicalType.Integer, nvl, (byte) 0);
}
@Override
public Byte deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ if (p.isExpectedNumberIntToken()) {
return p.getByteValue();
}
+ if (_primitive) {
+ return _parseBytePrimitive(p, ctxt);
+ }
return _parseByte(p, ctxt);
}
- protected Byte _parseByte(JsonParser p, DeserializationContext ctxt) throws IOException
+ protected Byte _parseByte(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
- String text = p.getText().trim();
- if (_hasTextualNull(text)) {
- return (Byte) _coerceTextualNull(ctxt, _primitive);
- }
- int len = text.length();
- if (len == 0) {
- return (Byte) _coerceEmptyString(ctxt, _primitive);
- }
- _verifyStringForScalarCoercion(ctxt, text);
- int value;
- try {
- value = NumberInput.parseInt(text);
- } catch (IllegalArgumentException iae) {
- return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Byte value");
+ String text;
+
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING: // let's do implicit re-parse
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, _valueClass);
+ if (act == CoercionAction.AsNull) {
+ return (Byte) getNullValue(ctxt);
}
- // So far so good: but does it fit?
- // as per [JACKSON-804], allow range up to 255, inclusive
- if (_byteOverflow(value)) {
- return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
- "overflow, value cannot be represented as 8-bit value");
- // fall-through for deferred fails
- }
- return Byte.valueOf((byte) value);
- }
- if (t == JsonToken.VALUE_NUMBER_FLOAT) {
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "Byte");
+ if (act == CoercionAction.AsEmpty) {
+ return (Byte) getEmptyValue(ctxt);
}
return p.getByteValue();
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
+ return (Byte) getNullValue(ctxt);
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getByteValue();
+ case JsonTokenId.ID_START_ARRAY:
+ return (Byte) _deserializeFromArray(p, ctxt);
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
+ default:
+ return (Byte) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
- if (t == JsonToken.VALUE_NULL) {
- return (Byte) _coerceNullToken(ctxt, _primitive);
+
+ // Rest of the processing is for coercion from String
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return (Byte) getNullValue(ctxt);
}
- // [databind#381]
- if (t == JsonToken.START_ARRAY) {
- return _deserializeFromArray(p, ctxt);
+ if (act == CoercionAction.AsEmpty) {
+ return (Byte) getEmptyValue(ctxt);
}
- if (t == JsonToken.VALUE_NUMBER_INT) { // shouldn't usually be called with it but
- return p.getByteValue();
+ text = text.trim();
+ if (_checkTextualNull(ctxt, text)) {
+ return (Byte) getNullValue(ctxt);
+ }
+ int value;
+ try {
+ value = NumberInput.parseInt(text);
+ } catch (IllegalArgumentException iae) {
+ return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid Byte value");
}
- return (Byte) ctxt.handleUnexpectedToken(_valueClass, p);
+ // So far so good: but does it fit?
+ // as per [JACKSON-804], allow range up to 255, inclusive
+ if (_byteOverflow(value)) {
+ return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
+ "overflow, value cannot be represented as 8-bit value");
+ // fall-through for deferred fails
+ }
+ return Byte.valueOf((byte) value);
}
}
@@ -351,59 +346,78 @@ public class NumberDeserializers
public ShortDeserializer(Class<Short> cls, Short nvl)
{
- super(cls, nvl, (short)0);
+ super(cls, LogicalType.Integer, nvl, (short)0);
}
@Override
public Short deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
+ if (p.isExpectedNumberIntToken()) {
+ return p.getShortValue();
+ }
+ if (_primitive) {
+ return _parseShortPrimitive(p, ctxt);
+ }
return _parseShort(p, ctxt);
}
- protected Short _parseShort(JsonParser p, DeserializationContext ctxt) throws IOException
+ protected Short _parseShort(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_NUMBER_INT) {
- return p.getShortValue();
- }
- if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
- String text = p.getText().trim();
- int len = text.length();
- if (len == 0) {
- return (Short) _coerceEmptyString(ctxt, _primitive);
- }
- if (_hasTextualNull(text)) {
- return (Short) _coerceTextualNull(ctxt, _primitive);
- }
- _verifyStringForScalarCoercion(ctxt, text);
- int value;
- try {
- value = NumberInput.parseInt(text);
- } catch (IllegalArgumentException iae) {
- return (Short) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Short value");
- }
- // So far so good: but does it fit?
- if (_shortOverflow(value)) {
- return (Short) ctxt.handleWeirdStringValue(_valueClass, text,
- "overflow, value cannot be represented as 16-bit value");
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING: // let's do implicit re-parse
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, _valueClass);
+ if (act == CoercionAction.AsNull) {
+ return (Short) getNullValue(ctxt);
}
- return Short.valueOf((short) value);
- }
- if (t == JsonToken.VALUE_NUMBER_FLOAT) {
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "Short");
+ if (act == CoercionAction.AsEmpty) {
+ return (Short) getEmptyValue(ctxt);
}
return p.getShortValue();
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
+ return (Short) getNullValue(ctxt);
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getShortValue();
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ return (Short)_deserializeFromArray(p, ctxt);
+ default:
+ return (Short) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
- if (t == JsonToken.VALUE_NULL) {
- return (Short) _coerceNullToken(ctxt, _primitive);
+
+ // Rest of the processing is for coercion from String
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return (Short) getNullValue(ctxt);
}
- if (t == JsonToken.START_ARRAY) {
- return _deserializeFromArray(p, ctxt);
+ if (act == CoercionAction.AsEmpty) {
+ return (Short) getEmptyValue(ctxt);
+ }
+ text = text.trim();
+ if (_checkTextualNull(ctxt, text)) {
+ return (Short) getNullValue(ctxt);
+ }
+ int value;
+ try {
+ value = NumberInput.parseInt(text);
+ } catch (IllegalArgumentException iae) {
+ return (Short) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid Short value");
}
- return (Short) ctxt.handleUnexpectedToken(_valueClass, p);
+ // So far so good: but does it fit?
+ if (_shortOverflow(value)) {
+ return (Short) ctxt.handleWeirdStringValue(_valueClass, text,
+ "overflow, value cannot be represented as 16-bit value");
+ }
+ return (short) value;
}
}
@@ -418,39 +432,72 @@ public class NumberDeserializers
public CharacterDeserializer(Class<Character> cls, Character nvl)
{
- super(cls, nvl, '\0');
+ super(cls,
+ // 07-Jun-2020, tatu: Debatable if it should be "OtherScalar" or Integer but...
+ LogicalType.Integer, nvl, '\0');
}
@Override
public Character deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- switch (p.getCurrentTokenId()) {
- case JsonTokenId.ID_NUMBER_INT: // ok iff ascii value
- _verifyNumberForScalarCoercion(ctxt, p);
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ // 23-Jun-2020, tatu: Unlike real numeric types, Character/char does not
+ // have canonical shape in JSON, and String in particular does not need
+ // coercion -- as long as it has length of 1.
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_INT: // ok iff Unicode value
+ CoercionAction act = ctxt.findCoercionAction(logicalType(), _valueClass, CoercionInputShape.Integer);
+ switch (act) {
+ case Fail:
+ _checkCoercionActionFail(ctxt, act, "Integer value ("+p.getText()+")");
+ break;
+ case AsNull:
+ return getNullValue(ctxt);
+ case AsEmpty:
+ return (Character) getEmptyValue(ctxt);
+ default:
+ }
int value = p.getIntValue();
if (value >= 0 && value <= 0xFFFF) {
return Character.valueOf((char) value);
}
- break;
- case JsonTokenId.ID_STRING: // this is the usual type
- // But does it have to be exactly one char?
- String text = p.getText();
- if (text.length() == 1) {
- return Character.valueOf(text.charAt(0));
- }
- // actually, empty should become null?
- if (text.length() == 0) {
- return (Character) _coerceEmptyString(ctxt, _primitive);
+ return (Character) ctxt.handleWeirdNumberValue(handledType(), value,
+ "value outside valid Character range (0x0000 - 0xFFFF)");
+ case JsonTokenId.ID_NULL:
+ if (_primitive) {
+ _verifyNullForPrimitive(ctxt);
}
+ return (Character) getNullValue(ctxt);
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
break;
- case JsonTokenId.ID_NULL:
- return (Character) _coerceNullToken(ctxt, _primitive);
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(p, ctxt);
default:
+ return (Character) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ if (text.length() == 1) {
+ return Character.valueOf(text.charAt(0));
}
- return (Character) ctxt.handleUnexpectedToken(_valueClass, p);
+ CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (Character) getEmptyValue(ctxt);
+ }
+ text = text.trim();
+ if (_checkTextualNull(ctxt, text)) {
+ return (Character) getNullValue(ctxt);
+ }
+ return (Character) ctxt.handleWeirdStringValue(handledType(), text,
+ "Expected either Integer value code or 1-character String");
}
}
@@ -464,7 +511,7 @@ public class NumberDeserializers
final static IntegerDeserializer wrapperInstance = new IntegerDeserializer(Integer.class, null);
public IntegerDeserializer(Class<Integer> cls, Integer nvl) {
- super(cls, nvl, 0);
+ super(cls, LogicalType.Integer, nvl, 0);
}
// since 2.6, slightly faster lookups for this very common type
@@ -473,9 +520,12 @@ public class NumberDeserializers
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
- if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ if (p.isExpectedNumberIntToken()) {
return p.getIntValue();
}
+ if (_primitive) {
+ return _parseIntPrimitive(p, ctxt);
+ }
return _parseInteger(p, ctxt);
}
@@ -485,55 +535,58 @@ public class NumberDeserializers
public Integer deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) throws IOException
{
- if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ if (p.isExpectedNumberIntToken()) {
return p.getIntValue();
}
+ if (_primitive) {
+ return _parseIntPrimitive(p, ctxt);
+ }
return _parseInteger(p, ctxt);
}
- protected final Integer _parseInteger(JsonParser p, DeserializationContext ctxt) throws IOException
+ protected final Integer _parseInteger(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
- switch (p.getCurrentTokenId()) {
- // NOTE: caller assumed to usually check VALUE_NUMBER_INT in fast path
- case JsonTokenId.ID_NUMBER_INT:
- return Integer.valueOf(p.getIntValue());
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
case JsonTokenId.ID_NUMBER_FLOAT: // coercing may work too
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "Integer");
- }
- return Integer.valueOf(p.getValueAsInt());
- case JsonTokenId.ID_STRING: // let's do implicit re-parse
- String text = p.getText().trim();
- int len = text.length();
- if (len == 0) {
- return (Integer) _coerceEmptyString(ctxt, _primitive);
+ final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, _valueClass);
+ if (act == CoercionAction.AsNull) {
+ return (Integer) getNullValue(ctxt);
}
- if (_hasTextualNull(text)) {
- return (Integer) _coerceTextualNull(ctxt, _primitive);
+ if (act == CoercionAction.AsEmpty) {
+ return (Integer) getEmptyValue(ctxt);
}
- _verifyStringForScalarCoercion(ctxt, text);
- try {
- if (len > 9) {
- long l = Long.parseLong(text);
- if (_intOverflow(l)) {
- return (Integer) ctxt.handleWeirdStringValue(_valueClass, text, String.format(
- "Overflow: numeric value (%s) out of range of Integer (%d - %d)",
- text, Integer.MIN_VALUE, Integer.MAX_VALUE));
- }
- return Integer.valueOf((int) l);
- }
- return Integer.valueOf(NumberInput.parseInt(text));
- } catch (IllegalArgumentException iae) {
- return (Integer) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Integer value");
- }
- case JsonTokenId.ID_NULL:
- return (Integer) _coerceNullToken(ctxt, _primitive);
+ return p.getValueAsInt();
+ case JsonTokenId.ID_NUMBER_INT: // NOTE: caller assumed to check in fast path
+ return p.getIntValue();
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
+ return (Integer) getNullValue(ctxt);
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
case JsonTokenId.ID_START_ARRAY:
- return _deserializeFromArray(p, ctxt);
+ return (Integer) _deserializeFromArray(p, ctxt);
+ default:
+ return (Integer) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return (Integer) getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (Integer) getEmptyValue(ctxt);
}
- // Otherwise, no can do:
- return (Integer) ctxt.handleUnexpectedToken(_valueClass, p);
+ text = text.trim();
+ if (_checkTextualNull(ctxt, text)) {
+ return (Integer) getNullValue(ctxt);
+ }
+ return _parseIntPrimitive(ctxt, text);
}
}
@@ -547,7 +600,7 @@ public class NumberDeserializers
final static LongDeserializer wrapperInstance = new LongDeserializer(Long.class, null);
public LongDeserializer(Class<Long> cls, Long nvl) {
- super(cls, nvl, 0L);
+ super(cls, LogicalType.Integer, nvl, 0L);
}
// since 2.6, slightly faster lookups for this very common type
@@ -556,46 +609,59 @@ public class NumberDeserializers
@Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
- if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ if (p.isExpectedNumberIntToken()) {
return p.getLongValue();
}
+ if (_primitive) {
+ return _parseLongPrimitive(p, ctxt);
+ }
return _parseLong(p, ctxt);
}
- protected final Long _parseLong(JsonParser p, DeserializationContext ctxt) throws IOException
+ protected final Long _parseLong(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
- switch (p.getCurrentTokenId()) {
- // NOTE: caller assumed to usually check VALUE_NUMBER_INT in fast path
- case JsonTokenId.ID_NUMBER_INT:
- return p.getLongValue();
- case JsonTokenId.ID_NUMBER_FLOAT:
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "Long");
- }
- return p.getValueAsLong();
+ String text;
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
- String text = p.getText().trim();
- if (text.length() == 0) {
- return (Long) _coerceEmptyString(ctxt, _primitive);
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, _valueClass);
+ if (act == CoercionAction.AsNull) {
+ return (Long) getNullValue(ctxt);
}
- if (_hasTextualNull(text)) {
- return (Long) _coerceTextualNull(ctxt, _primitive);
+ if (act == CoercionAction.AsEmpty) {
+ return (Long) getEmptyValue(ctxt);
}
- _verifyStringForScalarCoercion(ctxt, text);
- // let's allow Strings to be converted too
- try {
- return Long.valueOf(NumberInput.parseLong(text));
- } catch (IllegalArgumentException iae) { }
- return (Long) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Long value");
- // fall-through
- case JsonTokenId.ID_NULL:
- return (Long) _coerceNullToken(ctxt, _primitive);
+ return p.getValueAsLong();
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
+ return (Long) getNullValue(ctxt);
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getLongValue();
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
case JsonTokenId.ID_START_ARRAY:
- return _deserializeFromArray(p, ctxt);
+ return (Long) _deserializeFromArray(p, ctxt);
+ default:
+ return (Long) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return (Long) getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (Long) getEmptyValue(ctxt);
}
- // Otherwise, no can do:
- return (Long) ctxt.handleUnexpectedToken(_valueClass, p);
+ text = text.trim();
+ if (_checkTextualNull(ctxt, text)) {
+ return (Long) getNullValue(ctxt);
+ }
+ // let's allow Strings to be converted too
+ return _parseLongPrimitive(ctxt, text);
}
}
@@ -609,65 +675,77 @@ public class NumberDeserializers
final static FloatDeserializer wrapperInstance = new FloatDeserializer(Float.class, null);
public FloatDeserializer(Class<Float> cls, Float nvl) {
- super(cls, nvl, 0.f);
+ super(cls, LogicalType.Float, nvl, 0.f);
}
@Override
public Float deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
+ if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
+ return p.getFloatValue();
+ }
+ if (_primitive) {
+ return _parseFloatPrimitive(p, ctxt);
+ }
return _parseFloat(p, ctxt);
}
protected final Float _parseFloat(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- // We accept couple of different types; obvious ones first:
- JsonToken t = p.getCurrentToken();
-
- if (t == JsonToken.VALUE_NUMBER_FLOAT || t == JsonToken.VALUE_NUMBER_INT) { // coercing should work too
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
+ return (Float) getNullValue(ctxt);
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ case JsonTokenId.ID_NUMBER_INT: // safe coercion
return p.getFloatValue();
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ return _deserializeFromArray(p, ctxt);
+ default:
+ return (Float) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return (Float) getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (Float) getEmptyValue(ctxt);
}
- // And finally, let's allow Strings to be converted too
- if (t == JsonToken.VALUE_STRING) {
- String text = p.getText().trim();
- if ((text.length() == 0)) {
- return (Float) _coerceEmptyString(ctxt, _primitive);
+ text = text.trim();
+ if (_checkTextualNull(ctxt, text)) {
+ return (Float) getNullValue(ctxt);
+ }
+ switch (text.charAt(0)) {
+ case 'I':
+ if (_isPosInf(text)) {
+ return Float.POSITIVE_INFINITY;
}
- if (_hasTextualNull(text)) {
- return (Float) _coerceTextualNull(ctxt, _primitive);
+ break;
+ case 'N':
+ if (_isNaN(text)) {
+ return Float.NaN;
}
- switch (text.charAt(0)) {
- case 'I':
- if (_isPosInf(text)) {
- return Float.POSITIVE_INFINITY;
- }
- break;
- case 'N':
- if (_isNaN(text)) {
- return Float.NaN;
- }
- break;
- case '-':
- if (_isNegInf(text)) {
- return Float.NEGATIVE_INFINITY;
- }
- break;
+ break;
+ case '-':
+ if (_isNegInf(text)) {
+ return Float.NEGATIVE_INFINITY;
}
- _verifyStringForScalarCoercion(ctxt, text);
- try {
- return Float.parseFloat(text);
- } catch (IllegalArgumentException iae) { }
- return (Float) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Float value");
- }
- if (t == JsonToken.VALUE_NULL) {
- return (Float) _coerceNullToken(ctxt, _primitive);
- }
- if (t == JsonToken.START_ARRAY) {
- return _deserializeFromArray(p, ctxt);
+ break;
}
- // Otherwise, no can do:
- return (Float) ctxt.handleUnexpectedToken(_valueClass, p);
+ try {
+ return Float.parseFloat(text);
+ } catch (IllegalArgumentException iae) { }
+ return (Float) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid Float value");
}
}
@@ -681,11 +759,17 @@ public class NumberDeserializers
final static DoubleDeserializer wrapperInstance = new DoubleDeserializer(Double.class, null);
public DoubleDeserializer(Class<Double> cls, Double nvl) {
- super(cls, nvl, 0.d);
+ super(cls, LogicalType.Float, nvl, 0.d);
}
@Override
public Double deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
+ return p.getDoubleValue();
+ }
+ if (_primitive) {
+ return _parseDoublePrimitive(p, ctxt);
+ }
return _parseDouble(p, ctxt);
}
@@ -695,55 +779,71 @@ public class NumberDeserializers
public Double deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) throws IOException
{
+ if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
+ return p.getDoubleValue();
+ }
+ if (_primitive) {
+ return _parseDoublePrimitive(p, ctxt);
+ }
return _parseDouble(p, ctxt);
}
protected final Double _parseDouble(JsonParser p, DeserializationContext ctxt) throws IOException
{
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
+ return (Double) getNullValue(ctxt);
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ case JsonTokenId.ID_NUMBER_INT: // safe coercion
return p.getDoubleValue();
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ return _deserializeFromArray(p, ctxt);
+ default:
+ return (Double) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ // Coercion from String most complicated
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return (Double) getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (Double) getEmptyValue(ctxt);
}
- if (t == JsonToken.VALUE_STRING) {
- String text = p.getText().trim();
- if ((text.length() == 0)) {
- return (Double) _coerceEmptyString(ctxt, _primitive);
+ text = text.trim();
+ if (_checkTextualNull(ctxt, text)) {
+ return (Double) getNullValue(ctxt);
+ }
+ switch (text.charAt(0)) {
+ case 'I':
+ if (_isPosInf(text)) {
+ return Double.POSITIVE_INFINITY;
}
- if (_hasTextualNull(text)) {
- return (Double) _coerceTextualNull(ctxt, _primitive);
+ break;
+ case 'N':
+ if (_isNaN(text)) {
+ return Double.NaN;
}
- switch (text.charAt(0)) {
- case 'I':
- if (_isPosInf(text)) {
- return Double.POSITIVE_INFINITY;
- }
- break;
- case 'N':
- if (_isNaN(text)) {
- return Double.NaN;
- }
- break;
- case '-':
- if (_isNegInf(text)) {
- return Double.NEGATIVE_INFINITY;
- }
- break;
+ break;
+ case '-':
+ if (_isNegInf(text)) {
+ return Double.NEGATIVE_INFINITY;
}
- _verifyStringForScalarCoercion(ctxt, text);
- try {
- return parseDouble(text);
- } catch (IllegalArgumentException iae) { }
- return (Double) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Double value");
- }
- if (t == JsonToken.VALUE_NULL) {
- return (Double) _coerceNullToken(ctxt, _primitive);
- }
- if (t == JsonToken.START_ARRAY) {
- return _deserializeFromArray(p, ctxt);
+ break;
}
- // Otherwise, no can do:
- return (Double) ctxt.handleUnexpectedToken(_valueClass, p);
+ try {
+ return _parseDouble(text);
+ } catch (IllegalArgumentException iae) { }
+ return (Double) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid Double value");
}
}
@@ -768,10 +868,20 @@ public class NumberDeserializers
super(Number.class);
}
+ @Override // since 2.12
+ public final LogicalType logicalType() {
+ // 07-Jun-2020, tatu: Hmmh... tricky choice. For now, use:
+ return LogicalType.Integer;
+ }
+
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- switch (p.getCurrentTokenId()) {
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
case JsonTokenId.ID_NUMBER_INT:
if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return _coerceIntegral(p, ctxt);
@@ -786,56 +896,60 @@ public class NumberDeserializers
}
}
return p.getNumberValue();
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ return _deserializeFromArray(p, ctxt);
+ default:
+ return ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
- case JsonTokenId.ID_STRING:
- /* Textual values are more difficult... not parsing itself, but figuring
- * out 'minimal' type to use
- */
- String text = p.getText().trim();
- if ((text.length() == 0)) {
- // note: no need to call `coerce` as this is never primitive
- return getNullValue(ctxt);
- }
- if (_hasTextualNull(text)) {
- // note: no need to call `coerce` as this is never primitive
- return getNullValue(ctxt);
- }
- if (_isPosInf(text)) {
- return Double.POSITIVE_INFINITY;
- }
- if (_isNegInf(text)) {
- return Double.NEGATIVE_INFINITY;
+ // Textual values are more difficult... not parsing itself, but figuring
+ // out 'minimal' type to use
+ CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return getEmptyValue(ctxt);
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ // note: no need to call `coerce` as this is never primitive
+ return getNullValue(ctxt);
+ }
+ if (_isPosInf(text)) {
+ return Double.POSITIVE_INFINITY;
+ }
+ if (_isNegInf(text)) {
+ return Double.NEGATIVE_INFINITY;
+ }
+ if (_isNaN(text)) {
+ return Double.NaN;
+ }
+ try {
+ if (!_isIntNumber(text)) {
+ if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
+ return new BigDecimal(text);
+ }
+ return Double.valueOf(text);
}
- if (_isNaN(text)) {
- return Double.NaN;
+ if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
+ return new BigInteger(text);
}
- _verifyStringForScalarCoercion(ctxt, text);
- try {
- if (!_isIntNumber(text)) {
- if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
- return new BigDecimal(text);
- }
- return Double.valueOf(text);
- }
- if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
- return new BigInteger(text);
- }
- long value = Long.parseLong(text);
- if (!ctxt.isEnabled(DeserializationFeature.USE_LONG_FOR_INTS)) {
- if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
- return Integer.valueOf((int) value);
- }
+ long value = Long.parseLong(text);
+ if (!ctxt.isEnabled(DeserializationFeature.USE_LONG_FOR_INTS)) {
+ if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
+ return Integer.valueOf((int) value);
}
- return Long.valueOf(value);
- } catch (IllegalArgumentException iae) {
- return ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid number");
}
- case JsonTokenId.ID_START_ARRAY:
- return _deserializeFromArray(p, ctxt);
+ return Long.valueOf(value);
+ } catch (IllegalArgumentException iae) {
+ return ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid number");
}
- // Otherwise, no can do:
- return ctxt.handleUnexpectedToken(_valueClass, p);
}
/**
@@ -849,7 +963,7 @@ public class NumberDeserializers
TypeDeserializer typeDeserializer)
throws IOException
{
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_NUMBER_INT:
case JsonTokenId.ID_NUMBER_FLOAT:
case JsonTokenId.ID_STRING:
@@ -862,8 +976,7 @@ public class NumberDeserializers
/*
/**********************************************************
- /* And then bit more complicated (but non-structured) number
- /* types
+ /* And then bit more complicated (but non-structured) number types
/**********************************************************
*/
@@ -885,45 +998,63 @@ public class NumberDeserializers
return BigInteger.ZERO;
}
- @SuppressWarnings("incomplete-switch")
+ @Override // since 2.12
+ public final LogicalType logicalType() {
+ return LogicalType.Integer;
+ }
+
@Override
public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- switch (p.getCurrentTokenId()) {
- case JsonTokenId.ID_NUMBER_INT:
- switch (p.getNumberType()) {
- case INT:
- case LONG:
- case BIG_INTEGER:
- return p.getBigIntegerValue();
- }
+ if (p.isExpectedNumberIntToken()) {
+ return p.getBigIntegerValue();
+ }
+
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING: // let's do implicit re-parse
+ text = p.getText();
break;
case JsonTokenId.ID_NUMBER_FLOAT:
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "java.math.BigInteger");
+ final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, _valueClass);
+ if (act == CoercionAction.AsNull) {
+ return (BigInteger) getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (BigInteger) getEmptyValue(ctxt);
}
return p.getDecimalValue().toBigInteger();
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(p, ctxt);
- case JsonTokenId.ID_STRING: // let's do implicit re-parse
- String text = p.getText().trim();
+ default:
+ // String is ok too, can easily convert; otherwise, no can do:
+ return (BigInteger) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (BigInteger) getEmptyValue(ctxt);
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
// note: no need to call `coerce` as this is never primitive
- if (_isEmptyOrTextualNull(text)) {
- _verifyNullForScalarCoercion(ctxt, text);
- return getNullValue(ctxt);
- }
- _verifyStringForScalarCoercion(ctxt, text);
- try {
- return new BigInteger(text);
- } catch (IllegalArgumentException iae) { }
- return (BigInteger) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid representation");
+ return getNullValue(ctxt);
}
- // String is ok too, can easily convert; otherwise, no can do:
- return (BigInteger) ctxt.handleUnexpectedToken(_valueClass, p);
+ try {
+ return new BigInteger(text);
+ } catch (IllegalArgumentException iae) { }
+ return (BigInteger) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid representation");
}
}
-
+
@SuppressWarnings("serial")
@JacksonStdImpl
public static class BigDecimalDeserializer
@@ -937,33 +1068,51 @@ public class NumberDeserializers
public Object getEmptyValue(DeserializationContext ctxt) {
return BigDecimal.ZERO;
}
-
+
+ @Override // since 2.12
+ public final LogicalType logicalType() {
+ return LogicalType.Float;
+ }
+
@Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- switch (p.getCurrentTokenId()) {
+ String text;
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_NUMBER_INT:
case JsonTokenId.ID_NUMBER_FLOAT:
return p.getDecimalValue();
case JsonTokenId.ID_STRING:
- String text = p.getText().trim();
- // note: no need to call `coerce` as this is never primitive
- if (_isEmptyOrTextualNull(text)) {
- _verifyNullForScalarCoercion(ctxt, text);
- return getNullValue(ctxt);
- }
- _verifyStringForScalarCoercion(ctxt, text);
- try {
- return new BigDecimal(text);
- } catch (IllegalArgumentException iae) { }
- return (BigDecimal) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid representation");
+ text = p.getText();
+ break;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(p, ctxt);
+ default:
+ return (BigDecimal) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text);
+ if (act == CoercionAction.AsNull) {
+ return getNullValue(ctxt);
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (BigDecimal) getEmptyValue(ctxt);
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ // note: no need to call `coerce` as this is never primitive
+ return getNullValue(ctxt);
}
- // Otherwise, no can do:
- return (BigDecimal) ctxt.handleUnexpectedToken(_valueClass, p);
+ try {
+ return new BigDecimal(text);
+ } catch (IllegalArgumentException iae) { }
+ return (BigDecimal) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid representation");
}
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java
index 09980b153..6225f53ff 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java
@@ -12,6 +12,8 @@ import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.ArrayType;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ObjectBuffer;
@@ -25,8 +27,6 @@ public class ObjectArrayDeserializer
{
private static final long serialVersionUID = 1L;
- protected final static Object[] NO_OBJECTS = new Object[0];
-
// // Configuration
/**
@@ -52,20 +52,27 @@ public class ObjectArrayDeserializer
*/
protected final TypeDeserializer _elementTypeDeserializer;
+ /**
+ * @since 2.12
+ */
+ protected final Object[] _emptyValue;
+
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
- public ObjectArrayDeserializer(JavaType arrayType,
+ public ObjectArrayDeserializer(JavaType arrayType0,
JsonDeserializer<Object> elemDeser, TypeDeserializer elemTypeDeser)
{
- super(arrayType, null, null);
+ super(arrayType0, null, null);
+ ArrayType arrayType = (ArrayType) arrayType0;
_elementClass = arrayType.getContentType().getRawClass();
_untyped = (_elementClass == Object.class);
_elementDeserializer = elemDeser;
_elementTypeDeserializer = elemTypeDeser;
+ _emptyValue = arrayType.getEmptyArray();
}
protected ObjectArrayDeserializer(ObjectArrayDeserializer base,
@@ -75,6 +82,7 @@ public class ObjectArrayDeserializer
super(base, nuller, unwrapSingle);
_elementClass = base._elementClass;
_untyped = base._untyped;
+ _emptyValue = base._emptyValue;
_elementDeserializer = elemDeser;
_elementTypeDeserializer = elemTypeDeser;
@@ -114,11 +122,19 @@ public class ObjectArrayDeserializer
return (_elementDeserializer == null) && (_elementTypeDeserializer == null);
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Array;
+ }
+
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
JsonDeserializer<?> valueDeser = _elementDeserializer;
+ // 07-May-2020, tatu: Is the argument `containerType.getRawClass()` right here?
+ // In a way seems like it should rather refer to value class... ?
+ // (as it's individual value of element type, not Container)...
Boolean unwrapSingle = findFormatFeature(ctxt, property, _containerType.getRawClass(),
JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
// May have a content converter
@@ -157,7 +173,9 @@ public class ObjectArrayDeserializer
// need to override as we can't expose ValueInstantiator
@Override // since 2.9
public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
- return NO_OBJECTS;
+ // 03-Jul-2020, tatu: Must be assignment-compatible; can not just return `new Object[0]`
+ // if element type is different
+ return _emptyValue;
}
/*
@@ -306,34 +324,29 @@ public class ObjectArrayDeserializer
protected Object[] handleNonArray(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- // Empty String can become null...
- if (p.hasToken(JsonToken.VALUE_STRING)
- && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
- String str = p.getText();
- if (str.length() == 0) {
- return null;
- }
- }
-
// Can we do implicit coercion to a single-element array still?
boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
((_unwrapSingle == null) &&
ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
if (!canWrap) {
- // One exception; byte arrays are generally serialized as base64, so that should be handled
- if (p.hasToken(JsonToken.VALUE_STRING)
- // note: not `byte[]`, but `Byte[]` -- former is primitive array
- && _elementClass == Byte.class) {
- return deserializeFromBase64(p, ctxt);
+ // 2 exceptions with Strings:
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ // One exception; byte arrays are generally serialized as base64, so that should be handled
+ // note: not `byte[]`, but `Byte[]` -- former is primitive array
+ if (_elementClass == Byte.class) {
+ return deserializeFromBase64(p, ctxt);
+ }
+ // Second: empty (and maybe blank) String
+ return _deserializeFromString(p, ctxt);
}
- return (Object[]) ctxt.handleUnexpectedToken(_containerType.getRawClass(), p);
+ return (Object[]) ctxt.handleUnexpectedToken(_containerType, p);
}
Object value;
if (p.hasToken(JsonToken.VALUE_NULL)) {
// 03-Feb-2017, tatu: Should this be skipped or not?
if (_skipNullValues) {
- return NO_OBJECTS;
+ return _emptyValue;
}
value = _nullProvider.getNullValue(ctxt);
} else if (_elementTypeDeserializer == null) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java
index 2ca116aac..8665bec3f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/PrimitiveArrayDeserializers.java
@@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.deser.impl.NullsFailProvider;
import com.fasterxml.jackson.databind.exc.InvalidNullException;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
@@ -151,6 +152,11 @@ public abstract class PrimitiveArrayDeserializers<T> extends StdDeserializer<T>
/* Default implementations
/********************************************************
*/
+
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Array;
+ }
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
@@ -205,11 +211,8 @@ public abstract class PrimitiveArrayDeserializers<T> extends StdDeserializer<T>
protected T handleNonArray(JsonParser p, DeserializationContext ctxt) throws IOException
{
// Empty String can become null...
- if (p.hasToken(JsonToken.VALUE_STRING)
- && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
- if (p.getText().length() == 0) {
- return null;
- }
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _deserializeFromString(p, ctxt);
}
boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
((_unwrapSingle == null) &&
@@ -389,7 +392,7 @@ public abstract class PrimitiveArrayDeserializers<T> extends StdDeserializer<T>
_verifyNullForPrimitive(ctxt);
value = false;
} else {
- value = _parseBooleanPrimitive(ctxt, p, Boolean.TYPE);
+ value = _parseBooleanPrimitive(p, ctxt);
}
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
@@ -406,7 +409,7 @@ public abstract class PrimitiveArrayDeserializers<T> extends StdDeserializer<T>
@Override
protected boolean[] handleSingleElementUnwrapped(JsonParser p,
DeserializationContext ctxt) throws IOException {
- return new boolean[] { _parseBooleanPrimitive(ctxt, p, Boolean.TYPE) };
+ return new boolean[] { _parseBooleanPrimitive(p, ctxt) };
}
@Override
@@ -445,6 +448,13 @@ public abstract class PrimitiveArrayDeserializers<T> extends StdDeserializer<T>
return new byte[0];
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ // 30-May-2020, tatu: while technically an array, logically contains
+ // binary data so...
+ return LogicalType.Binary;
+ }
+
@Override
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -485,9 +495,8 @@ public abstract class PrimitiveArrayDeserializers<T> extends StdDeserializer<T>
while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
byte value;
- if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
- // should we catch overflow exceptions?
- value = p.getByteValue();
+ if (t == JsonToken.VALUE_NUMBER_INT) {
+ value = p.getByteValue(); // note: may throw due to overflow
} else {
// should probably accept nulls as 0
if (t == JsonToken.VALUE_NULL) {
@@ -519,9 +528,8 @@ public abstract class PrimitiveArrayDeserializers<T> extends StdDeserializer<T>
{
byte value;
JsonToken t = p.currentToken();
- if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
- // should we catch overflow exceptions?
- value = p.getByteValue();
+ if (t == JsonToken.VALUE_NUMBER_INT) {
+ value = p.getByteValue(); // note: may throw due to overflow
} else {
// should probably accept nulls as 'false'
if (t == JsonToken.VALUE_NULL) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java
index 8c96af3fd..5672c75f6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.type.ReferenceType;
import com.fasterxml.jackson.databind.util.AccessPattern;
@@ -145,7 +146,7 @@ public abstract class ReferenceTypeDeserializer<T>
* @since 2.9
*/
public abstract Object getReferenced(T reference);
-
+
/*
/**********************************************************
/* Overridden accessors
@@ -153,8 +154,19 @@ public abstract class ReferenceTypeDeserializer<T>
*/
@Override
+ public ValueInstantiator getValueInstantiator() { return _valueInstantiator; }
+
+ @Override
public JavaType getValueType() { return _fullType; }
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ if (_valueDeserializer != null) {
+ return _valueDeserializer.logicalType();
+ }
+ return super.logicalType();
+ }
+
/**
* By default we assume that updateability mostly relies on value
* deserializer; if it supports updates, typically that's what
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java
index a7b6022ea..55532feb9 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java
@@ -28,7 +28,7 @@ public class StackTraceElementDeserializer
int lineNumber = -1;
while ((t = p.nextValue()) != JsonToken.END_OBJECT) {
- String propName = p.getCurrentName();
+ String propName = p.currentName();
// TODO: with Java 8, convert to switch
if ("className".equals(propName)) {
className = p.getText();
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java
index 0b7e0ed08..12ef990db 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java
@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
@@ -152,6 +153,11 @@ public class StdDelegatingDeserializer<T>
return _delegateDeserializer.handledType();
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return _delegateDeserializer.logicalType();
+ }
+
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
return _delegateDeserializer.supportsUpdate(config);
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java
index 0b36403c1..a3255d3a1 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java
@@ -5,11 +5,16 @@ import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.Nulls;
+
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.JsonParser.NumberType;
import com.fasterxml.jackson.core.exc.InputCoercionException;
import com.fasterxml.jackson.core.io.NumberInput;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
@@ -19,6 +24,7 @@ import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.deser.impl.NullsFailProvider;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
@@ -30,7 +36,8 @@ import com.fasterxml.jackson.databind.util.Converter;
*/
public abstract class StdDeserializer<T>
extends JsonDeserializer<T>
- implements java.io.Serializable
+ implements java.io.Serializable,
+ ValueInstantiator.Gettable // since 2.12
{
private static final long serialVersionUID = 1L;
@@ -45,7 +52,7 @@ public abstract class StdDeserializer<T>
DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.getMask()
| DeserializationFeature.USE_LONG_FOR_INTS.getMask();
- // @since 2.9
+ @Deprecated // since 2.12
protected final static int F_MASK_ACCEPT_ARRAYS =
DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
@@ -90,7 +97,7 @@ public abstract class StdDeserializer<T>
@Override
public Class<?> handledType() { return _valueClass; }
-
+
/*
/**********************************************************
/* Extended API
@@ -129,6 +136,12 @@ public abstract class StdDeserializer<T>
}
/**
+ * @since 2.12
+ */
+ @Override // for ValueInstantiator.Gettable
+ public ValueInstantiator getValueInstantiator() { return null; }
+
+ /**
* Method that can be called to determine if given deserializer is the default
* deserializer Jackson uses; as opposed to a custom deserializer installed by
* a module or calling application. Determination is done using
@@ -141,7 +154,7 @@ public abstract class StdDeserializer<T>
protected boolean isDefaultKeyDeserializer(KeyDeserializer keyDeser) {
return ClassUtil.isJacksonStdImpl(keyDeser);
}
-
+
/*
/**********************************************************
/* Partial JsonDeserializer implementation
@@ -160,6 +173,194 @@ public abstract class StdDeserializer<T>
}
/*
+ /**********************************************************************
+ /* High-level handling of secondary input shapes (with possible coercion)
+ /**********************************************************************
+ */
+
+ /**
+ * Helper method that allows easy support for array-related coercion features:
+ * checks for either empty array, or single-value array-wrapped value (if coercion
+ * enabled by {@code CoercionConfigs} (since 2.12), and either reports
+ * an exception (if no coercion allowed), or returns appropriate
+ * result value using coercion mechanism indicated.
+ *<p>
+ * This method should NOT be called if Array representation is explicitly supported
+ * for type: it should only be called in case it is otherwise unrecognized.
+ *<p>
+ * NOTE: in case of unwrapped single element, will handle actual decoding
+ * by calling {@link #_deserializeWrappedValue}, which by default calls
+ * {@link #deserialize(JsonParser, DeserializationContext)}.
+ *
+ * @since 2.9
+ */
+ @SuppressWarnings("unchecked")
+ protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
+ {
+ final CoercionAction act = _findCoercionFromEmptyArray(ctxt);
+ final boolean unwrap = ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ if (unwrap || (act != CoercionAction.Fail)) {
+ JsonToken t = p.nextToken();
+ if (t == JsonToken.END_ARRAY) {
+ switch (act) {
+ case AsEmpty:
+ return (T) getEmptyValue(ctxt);
+ case AsNull:
+ case TryConvert:
+ return getNullValue(ctxt);
+ default:
+ }
+ } else if (unwrap) {
+ final T parsed = _deserializeWrappedValue(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
+ }
+ return parsed;
+ }
+ }
+ return (T) ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
+ }
+
+ /**
+ * Helper method that may be used to support fallback for Empty String / Empty Array
+ * non-standard representations; usually for things serialized as JSON Objects.
+ *
+ * @since 2.5
+ *
+ * @deprecated Since 2.12
+ */
+ @SuppressWarnings("unchecked")
+ @Deprecated // since 2.12
+ protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt)
+ throws IOException
+ {
+ if (p.hasToken(JsonToken.START_ARRAY)) {
+ if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
+ JsonToken t = p.nextToken();
+ if (t == JsonToken.END_ARRAY) {
+ return null;
+ }
+ return (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+ }
+ return (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
+ }
+
+ /**
+ * Helper method to call in case deserializer does not support native automatic
+ * use of incoming String values, but there may be standard coercions to consider.
+ *
+ * @since 2.12
+ */
+ @SuppressWarnings("unchecked")
+ protected T _deserializeFromString(JsonParser p, DeserializationContext ctxt)
+ throws IOException
+ {
+ final ValueInstantiator inst = getValueInstantiator();
+ final Class<?> rawTargetType = handledType();
+ String value = p.getValueAsString();
+
+ if ((inst != null) && inst.canCreateFromString()) {
+ return (T) inst.createFromString(ctxt, value);
+ }
+
+ if (value.length() == 0) {
+ final CoercionAction act = ctxt.findCoercionAction(logicalType(), rawTargetType,
+ CoercionInputShape.EmptyString);
+ return (T) _deserializeFromEmptyString(p, ctxt, act, rawTargetType,
+ "empty String (\"\")");
+ }
+ if (_isBlank(value)) {
+ final CoercionAction act = ctxt.findCoercionFromBlankString(logicalType(), rawTargetType,
+ CoercionAction.Fail);
+ return (T) _deserializeFromEmptyString(p, ctxt, act, rawTargetType,
+ "blank String (all whitespace)");
+ }
+
+ // 28-Sep-2011, tatu: Ok this is not clean at all; but since there are legacy
+ // systems that expect conversions in some cases, let's just add a minimal
+ // patch (note: same could conceivably be used for numbers too).
+ if (inst != null) {
+ value = value.trim(); // mostly to avoid problems wrt XML indentation
+ if (inst.canCreateFromInt()) {
+ if (ctxt.findCoercionAction(LogicalType.Integer, Integer.class,
+ CoercionInputShape.String) == CoercionAction.TryConvert) {
+ return (T) inst.createFromInt(ctxt, _parseIntPrimitive(ctxt, value));
+ }
+ }
+ if (inst.canCreateFromLong()) {
+ if (ctxt.findCoercionAction(LogicalType.Integer, Long.class,
+ CoercionInputShape.String) == CoercionAction.TryConvert) {
+ return (T) inst.createFromLong(ctxt, _parseLongPrimitive(ctxt, value));
+ }
+ }
+ if (inst.canCreateFromBoolean()) {
+ // 29-May-2020, tatu: With 2.12 can and should use CoercionConfig so:
+ if (ctxt.findCoercionAction(LogicalType.Boolean, Boolean.class,
+ CoercionInputShape.String) == CoercionAction.TryConvert) {
+ String str = value.trim();
+ if ("true".equals(str)) {
+ return (T) inst.createFromBoolean(ctxt, true);
+ }
+ if ("false".equals(str)) {
+ return (T) inst.createFromBoolean(ctxt, false);
+ }
+ }
+ }
+ }
+ return (T) ctxt.handleMissingInstantiator(rawTargetType, inst, ctxt.getParser(),
+ "no String-argument constructor/factory method to deserialize from String value ('%s')",
+ value);
+ }
+
+ protected Object _deserializeFromEmptyString(JsonParser p, DeserializationContext ctxt,
+ CoercionAction act, Class<?> rawTargetType, String desc) throws IOException
+ {
+ switch (act) {
+ case AsEmpty:
+ return getEmptyValue(ctxt);
+ case TryConvert:
+ // hmmmh... empty or null, typically? Assume "as null" for now
+ case AsNull:
+ return null;
+ case Fail:
+ break;
+ }
+ final ValueInstantiator inst = getValueInstantiator();
+
+ // 03-Jun-2020, tatu: Should ideally call `handleUnexpectedToken()` instead, but
+ // since this call was already made, use it.
+ return ctxt.handleMissingInstantiator(rawTargetType, inst, p,
+"Cannot deserialize value of type %s from %s (no String-argument constructor/factory method; coercion not enabled)",
+ ClassUtil.getTypeDescription(getValueType(ctxt)), desc);
+ }
+
+ /**
+ * Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}:
+ * default implementation simply calls
+ * {@link #deserialize(JsonParser, DeserializationContext)},
+ * but handling may be overridden.
+ *
+ * @since 2.9
+ */
+ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException
+ {
+ // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
+ // either supporting nested arrays, or to cause infinite looping.
+ if (p.hasToken(JsonToken.START_ARRAY)) {
+ String msg = String.format(
+"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
+ ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
+ "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
+ @SuppressWarnings("unchecked")
+ T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
+ return result;
+ }
+ return (T) deserialize(p, ctxt);
+ }
+
+ /*
/**********************************************************
/* Helper methods for sub-classes, parsing: while mostly
/* useful for numeric types, can be also useful for dealing
@@ -167,78 +368,251 @@ public abstract class StdDeserializer<T>
/**********************************************************
*/
- @Deprecated // since 2.11, use overloaded variant
- protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationContext ctxt) throws IOException {
- return _parseBooleanPrimitive(ctxt, p, Boolean.TYPE);
+ @Deprecated // since 2.12, use overloaded variant that does NOT take target type
+ protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt,
+ JsonParser p, Class<?> targetType) throws IOException {
+ return _parseBooleanPrimitive(p, ctxt);
}
- // @since 2.11
- protected final boolean _parseBooleanPrimitive(DeserializationContext ctxt,
- JsonParser p, Class<?> targetType) throws IOException
+ /**
+ * @param ctxt Deserialization context for accessing configuration
+ * @param p Underlying parser
+ * @param targetType Actual type that is being deserialized, typically
+ * same as {@link #handledType}, and not necessarily {@code boolean}
+ * (may be {@code boolean[]} or {@code AtomicBoolean} for example);
+ * used for coercion config access
+ */
+ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_TRUE) return true;
- if (t == JsonToken.VALUE_FALSE) return false;
- if (t == JsonToken.VALUE_NULL) {
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_INT:
+ // may accept ints too, (0 == false, otherwise true)
+
+ // call returns `null`, Boolean.TRUE or Boolean.FALSE so:
+ return _coerceBooleanFromInt(p, ctxt, Boolean.TYPE) == Boolean.TRUE;
+ case JsonTokenId.ID_TRUE: // usually caller should have handled but:
+ return true;
+ case JsonTokenId.ID_FALSE:
+ return false;
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
_verifyNullForPrimitive(ctxt);
return false;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, Boolean.TYPE);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ p.nextToken();
+ final boolean parsed = _parseBooleanPrimitive(p, ctxt);
+ _verifyEndArrayForSingle(p, ctxt);
+ return parsed;
+ }
+ // fall through
+ default:
+ return ((Boolean) ctxt.handleUnexpectedToken(Boolean.TYPE, p)).booleanValue();
}
- // should accept ints too, (0 == false, otherwise true)
- if (t == JsonToken.VALUE_NUMBER_INT) {
- return _parseBooleanFromInt(p, ctxt);
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Boolean, Boolean.TYPE);
+ if (act == CoercionAction.AsNull) {
+ _verifyNullForPrimitive(ctxt);
+ return false;
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return false;
}
- // And finally, let's allow Strings to be converted too
- if (t == JsonToken.VALUE_STRING) {
- String text = p.getText().trim();
- // [databind#422]: Allow aliases
- if ("true".equals(text) || "True".equals(text)) {
+ text = text.trim();
+ final int len = text.length();
+
+ // For [databind#1852] allow some case-insensitive matches (namely,
+ // true/True/TRUE, false/False/FALSE
+ if (len == 4) {
+ if (_isTrue(text)) {
return true;
}
- if ("false".equals(text) || "False".equals(text)) {
- return false;
- }
- if (_isEmptyOrTextualNull(text)) {
- _verifyNullForPrimitiveCoercion(ctxt, text);
+ } else if (len == 5) {
+ if (_isFalse(text)) {
return false;
}
- Boolean b = (Boolean) ctxt.handleWeirdStringValue(targetType, text,
- "only \"true\" or \"false\" recognized");
- return Boolean.TRUE.equals(b);
}
- // [databind#381]
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final boolean parsed = _parseBooleanPrimitive(ctxt, p, targetType);
- _verifyEndArrayForSingle(p, ctxt);
- return parsed;
+ if (_hasTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return false;
}
- // Otherwise, no can do:
- return ((Boolean) ctxt.handleUnexpectedToken(targetType, p)).booleanValue();
+ Boolean b = (Boolean) ctxt.handleWeirdStringValue(Boolean.TYPE, text,
+ "only \"true\"/\"True\"/\"TRUE\" or \"false\"/\"False\"/\"FALSE\" recognized");
+ return Boolean.TRUE.equals(b);
}
- protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt)
+ // [databind#1852]
+ protected boolean _isTrue(String text) {
+ char c = text.charAt(0);
+ if (c == 't') {
+ return "true".equals(text);
+ }
+ if (c == 'T') {
+ return "TRUE".equals(text) || "True".equals(text);
+ }
+ return false;
+ }
+
+ protected boolean _isFalse(String text) {
+ char c = text.charAt(0);
+ if (c == 'f') {
+ return "false".equals(text);
+ }
+ if (c == 'F') {
+ return "FALSE".equals(text) || "False".equals(text);
+ }
+ return false;
+ }
+
+ /**
+ * Helper method called for cases where non-primitive, boolean-based value
+ * is to be deserialized: result of this method will be {@link java.lang.Boolean},
+ * although actual target type may be something different.
+ *<p>
+ * Note: does NOT dynamically access "empty value" or "null value" of deserializer
+ * since those values could be of type other than {@link java.lang.Boolean}.
+ * Caller may need to translate from 3 possible result types into appropriately
+ * matching output types.
+ *
+ * @param p Underlying parser
+ * @param ctxt Deserialization context for accessing configuration
+ * @param targetType Actual type that is being deserialized, may be
+ * same as {@link #handledType} but could be {@code AtomicBoolean} for example.
+ * Used for coercion config access.
+ *
+ * @since 2.12
+ */
+ protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt,
+ Class<?> targetType)
throws IOException
{
- // 13-Oct-2016, tatu: As per [databind#1324], need to be careful wrt
- // degenerate case of huge integers, legal in JSON.
- // ... this is, on the other hand, probably wrong/sub-optimal for non-JSON
- // input. For now, no rea
- _verifyNumberForScalarCoercion(ctxt, p);
- // Anyway, note that since we know it's valid (JSON) integer, it can't have
- // extra whitespace to trim.
- return !"0".equals(p.getText());
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_INT:
+ // may accept ints too, (0 == false, otherwise true)
+ return _coerceBooleanFromInt(p, ctxt, targetType);
+ case JsonTokenId.ID_TRUE:
+ return true;
+ case JsonTokenId.ID_FALSE:
+ return false;
+ case JsonTokenId.ID_NULL: // null fine for non-primitive
+ return null;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, targetType);
+ break;
+ case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
+ return (Boolean) _deserializeFromArray(p, ctxt);
+ default:
+ return (Boolean) ctxt.handleUnexpectedToken(targetType, p);
+ }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Boolean, targetType);
+ if (act == CoercionAction.AsNull) {
+ return null;
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return false;
+ }
+ text = text.trim();
+ final int len = text.length();
+
+ // For [databind#1852] allow some case-insensitive matches (namely,
+ // true/True/TRUE, false/False/FALSE
+ if (len == 4) {
+ if (_isTrue(text)) {
+ return true;
+ }
+ } else if (len == 5) {
+ if (_isFalse(text)) {
+ return false;
+ }
+ }
+ if (_checkTextualNull(ctxt, text)) {
+ return null;
+ }
+ return (Boolean) ctxt.handleWeirdStringValue(targetType, text,
+ "only \"true\" or \"false\" recognized");
}
protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- int value = _parseIntPrimitive(p, ctxt);
- // So far so good: but does it fit?
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Byte.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return (byte) 0;
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (byte) 0;
+ }
+ return p.getByteValue();
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getByteValue();
+ case JsonTokenId.ID_NULL:
+ _verifyNullForPrimitive(ctxt);
+ return (byte) 0;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, Byte.TYPE);
+ break;
+ case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
+ // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ p.nextToken();
+ final byte parsed = _parseBytePrimitive(p, ctxt);
+ _verifyEndArrayForSingle(p, ctxt);
+ return parsed;
+ }
+ // fall through
+ default:
+ return ((Byte) ctxt.handleUnexpectedToken(ctxt.constructType(Byte.TYPE), p)).byteValue();
+ }
+
+ // Coercion from String
+ CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Integer, Byte.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return (byte) 0; // no need to check as does not come from `null`, explicit coercion
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (byte) 0;
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return (byte) 0;
+ }
+ int value;
+ try {
+ value = NumberInput.parseInt(text);
+ } catch (IllegalArgumentException iae) {
+ return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid `byte` value");
+ }
+ // So far so good: but does it fit? Allow both -128 / 255 range (inclusive)
if (_byteOverflow(value)) {
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, String.valueOf(value),
+ return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
"overflow, value cannot be represented as 8-bit value");
- return _nonNullNumber(v).byteValue();
}
return (byte) value;
}
@@ -246,12 +620,65 @@ public abstract class StdDeserializer<T>
protected final short _parseShortPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- int value = _parseIntPrimitive(p, ctxt);
- // So far so good: but does it fit?
+ String text;
+ switch (p.currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Short.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return (short) 0;
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (short) 0;
+ }
+ return p.getShortValue();
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getShortValue();
+ case JsonTokenId.ID_NULL:
+ _verifyNullForPrimitive(ctxt);
+ return (short) 0;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, Short.TYPE);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ p.nextToken();
+ final short parsed = _parseShortPrimitive(p, ctxt);
+ _verifyEndArrayForSingle(p, ctxt);
+ return parsed;
+ }
+ // fall through to fail
+ default:
+ return ((Short) ctxt.handleUnexpectedToken(ctxt.constructType(Short.TYPE), p)).shortValue();
+ }
+
+ CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Integer, Short.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return (short) 0; // no need to check as does not come from `null`, explicit coercion
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return (short) 0;
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return (short) 0;
+ }
+ int value;
+ try {
+ value = NumberInput.parseInt(text);
+ } catch (IllegalArgumentException iae) {
+ return (Short) ctxt.handleWeirdStringValue(Short.TYPE, text,
+ "not a valid `short` value");
+ }
if (_shortOverflow(value)) {
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, String.valueOf(value),
+ return (Short) ctxt.handleWeirdStringValue(Short.TYPE, text,
"overflow, value cannot be represented as 16-bit value");
- return _nonNullNumber(v).shortValue();
}
return (short) value;
}
@@ -259,25 +686,29 @@ public abstract class StdDeserializer<T>
protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- return p.getIntValue();
- }
- switch (p.getCurrentTokenId()) {
+ String text;
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
- String text = p.getText().trim();
- if (_isEmptyOrTextualNull(text)) {
- _verifyNullForPrimitiveCoercion(ctxt, text);
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Integer.TYPE);
+ if (act == CoercionAction.AsNull) {
return 0;
}
- return _parseIntPrimitive(ctxt, text);
- case JsonTokenId.ID_NUMBER_FLOAT:
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "int");
+ if (act == CoercionAction.AsEmpty) {
+ return 0;
}
return p.getValueAsInt();
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getIntValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return 0;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, Integer.TYPE);
+ break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken();
@@ -285,11 +716,25 @@ public abstract class StdDeserializer<T>
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
- break;
+ // fall through to fail
default:
+ return ((Number) ctxt.handleUnexpectedToken(Integer.TYPE, p)).intValue();
}
- // Otherwise, no can do:
- return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).intValue();
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Integer, Integer.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return 0; // no need to check as does not come from `null`, explicit coercion
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return 0;
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return 0;
+ }
+ return _parseIntPrimitive(ctxt, text);
}
/**
@@ -301,7 +746,7 @@ public abstract class StdDeserializer<T>
if (text.length() > 9) {
long l = Long.parseLong(text);
if (_intOverflow(l)) {
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
+ Number v = (Number) ctxt.handleWeirdStringValue(Integer.TYPE, text,
"Overflow: numeric value (%s) out of range of int (%d -%d)",
text, Integer.MIN_VALUE, Integer.MAX_VALUE);
return _nonNullNumber(v).intValue();
@@ -310,34 +755,38 @@ public abstract class StdDeserializer<T>
}
return NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid int value");
+ Number v = (Number) ctxt.handleWeirdStringValue(Integer.TYPE, text,
+ "not a valid `int` value");
return _nonNullNumber(v).intValue();
}
}
protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ctxt)
- throws IOException
+ throws IOException
{
- if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- return p.getLongValue();
- }
- switch (p.getCurrentTokenId()) {
+ String text;
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
- String text = p.getText().trim();
- if (_isEmptyOrTextualNull(text)) {
- _verifyNullForPrimitiveCoercion(ctxt, text);
+ text = p.getText();
+ break;
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ final CoercionAction act = _checkFloatToIntCoercion(p, ctxt, Long.TYPE);
+ if (act == CoercionAction.AsNull) {
return 0L;
}
- return _parseLongPrimitive(ctxt, text);
- case JsonTokenId.ID_NUMBER_FLOAT:
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "long");
+ if (act == CoercionAction.AsEmpty) {
+ return 0L;
}
return p.getValueAsLong();
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getLongValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return 0L;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, Long.TYPE);
+ break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken();
@@ -345,9 +794,25 @@ public abstract class StdDeserializer<T>
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
- break;
+ // fall through
+ default:
+ return ((Number) ctxt.handleUnexpectedToken(Long.TYPE, p)).longValue();
}
- return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).longValue();
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Integer, Long.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return 0L; // no need to check as does not come from `null`, explicit coercion
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return 0L;
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return 0L;
+ }
+ return _parseLongPrimitive(ctxt, text);
}
/**
@@ -359,8 +824,8 @@ public abstract class StdDeserializer<T>
return NumberInput.parseLong(text);
} catch (IllegalArgumentException iae) { }
{
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid long value");
+ Number v = (Number) ctxt.handleWeirdStringValue(Long.TYPE, text,
+ "not a valid `long` value");
return _nonNullNumber(v).longValue();
}
}
@@ -368,22 +833,21 @@ public abstract class StdDeserializer<T>
protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
- return p.getFloatValue();
- }
- switch (p.getCurrentTokenId()) {
+ String text;
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
- String text = p.getText().trim();
- if (_isEmptyOrTextualNull(text)) {
- _verifyNullForPrimitiveCoercion(ctxt, text);
- return 0.0f;
- }
- return _parseFloatPrimitive(ctxt, text);
+ text = p.getText();
+ break;
case JsonTokenId.ID_NUMBER_INT:
+ case JsonTokenId.ID_NUMBER_FLOAT:
return p.getFloatValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
- return 0.0f;
+ return 0f;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, Float.TYPE);
+ break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken();
@@ -391,11 +855,26 @@ public abstract class StdDeserializer<T>
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
- break;
+ // fall through
+ default:
+ return ((Number) ctxt.handleUnexpectedToken(Float.TYPE, p)).floatValue();
}
- // Otherwise, no can do:
- return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).floatValue();
- }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Integer, Float.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return 0.0f; // no need to check as does not come from `null`, explicit coercion
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return 0.0f;
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return 0.0f;
+ }
+ return _parseFloatPrimitive(ctxt, text);
+}
/**
* @since 2.9
@@ -421,30 +900,29 @@ public abstract class StdDeserializer<T>
try {
return Float.parseFloat(text);
} catch (IllegalArgumentException iae) { }
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid float value");
+ Number v = (Number) ctxt.handleWeirdStringValue(Float.TYPE, text,
+ "not a valid `float` value");
return _nonNullNumber(v).floatValue();
}
protected final double _parseDoublePrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
- return p.getDoubleValue();
- }
- switch (p.getCurrentTokenId()) {
+ String text;
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
- String text = p.getText().trim();
- if (_isEmptyOrTextualNull(text)) {
- _verifyNullForPrimitiveCoercion(ctxt, text);
- return 0.0;
- }
- return _parseDoublePrimitive(ctxt, text);
+ text = p.getText();
+ break;
case JsonTokenId.ID_NUMBER_INT:
+ case JsonTokenId.ID_NUMBER_FLOAT:
return p.getDoubleValue();
case JsonTokenId.ID_NULL:
_verifyNullForPrimitive(ctxt);
return 0.0;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, Double.TYPE);
+ break;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken();
@@ -452,10 +930,25 @@ public abstract class StdDeserializer<T>
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
- break;
+ // fall through
+ default:
+ return ((Number) ctxt.handleUnexpectedToken(Double.TYPE, p)).doubleValue();
+ }
+
+ final CoercionAction act = _checkFromStringCoercion(ctxt, text,
+ LogicalType.Integer, Double.TYPE);
+ if (act == CoercionAction.AsNull) {
+ return 0.0; // no need to check as does not come from `null`, explicit coercion
+ }
+ if (act == CoercionAction.AsEmpty) {
+ return 0.0;
+ }
+ text = text.trim();
+ if (_hasTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return 0.0;
}
- // Otherwise, no can do:
- return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).doubleValue();
+ return _parseDoublePrimitive(ctxt, text);
}
/**
@@ -482,19 +975,34 @@ public abstract class StdDeserializer<T>
break;
}
try {
- return parseDouble(text);
+ return _parseDouble(text);
} catch (IllegalArgumentException iae) { }
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid double value (as String to convert)");
+ Number v = (Number) ctxt.handleWeirdStringValue(Double.TYPE, text,
+ "not a valid `double` value (as String to convert)");
return _nonNullNumber(v).doubleValue();
}
+ /**
+ * Helper method for encapsulating calls to low-level double value parsing; single place
+ * just because we need a work-around that must be applied to all calls.
+ */
+ protected final static double _parseDouble(String numStr) throws NumberFormatException
+ {
+ // avoid some nasty float representations... but should it be MIN_NORMAL or MIN_VALUE?
+ if (NumberInput.NASTY_SMALL_DOUBLE.equals(numStr)) {
+ return Double.MIN_NORMAL; // since 2.7; was MIN_VALUE prior
+ }
+ return Double.parseDouble(numStr);
+ }
+
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- switch (p.getCurrentTokenId()) {
+ String text;
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_STRING:
- return _parseDate(p.getText().trim(), ctxt);
+ text = p.getText();
+ break;
case JsonTokenId.ID_NUMBER_INT:
{
long ts;
@@ -504,40 +1012,51 @@ public abstract class StdDeserializer<T>
// (but leave both until 3.0)
} catch (JsonParseException | InputCoercionException e) {
Number v = (Number) ctxt.handleWeirdNumberValue(_valueClass, p.getNumberValue(),
- "not a valid 64-bit long for creating `java.util.Date`");
+ "not a valid 64-bit `long` for creating `java.util.Date`");
ts = v.longValue();
}
return new java.util.Date(ts);
}
case JsonTokenId.ID_NULL:
return (java.util.Date) getNullValue(ctxt);
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ text = ctxt.extractScalarFromObject(p, this, _valueClass);
+ break;
case JsonTokenId.ID_START_ARRAY:
return _parseDateFromArray(p, ctxt);
+ default:
+ return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p);
}
- return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p);
+
+ return _parseDate(text.trim(), ctxt);
}
// @since 2.9
protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- JsonToken t;
- if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) {
- t = p.nextToken();
+ final CoercionAction act = _findCoercionFromEmptyArray(ctxt);
+ final boolean unwrap = ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ if (unwrap || (act != CoercionAction.Fail)) {
+ JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
+ switch (act) {
+ case AsEmpty:
+ return (java.util.Date) getEmptyValue(ctxt);
+ case AsNull:
+ case TryConvert:
return (java.util.Date) getNullValue(ctxt);
+ default:
}
- }
- if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ } else if (unwrap) {
final Date parsed = _parseDate(p, ctxt);
_verifyEndArrayForSingle(p, ctxt);
return parsed;
}
- } else {
- t = p.getCurrentToken();
}
- return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
+ return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, JsonToken.START_ARRAY, p, null);
}
/**
@@ -548,8 +1067,20 @@ public abstract class StdDeserializer<T>
{
try {
// Take empty Strings to mean 'empty' Value, usually 'null':
- if (_isEmptyOrTextualNull(value)) {
- return (java.util.Date) getNullValue(ctxt);
+ if (value.length() == 0) {
+ final CoercionAction act = _checkFromStringCoercion(ctxt, value);
+ switch (act) { // note: Fail handled above
+ case AsEmpty:
+ return new java.util.Date(0L);
+ case AsNull:
+ case TryConvert:
+ default:
+ }
+ return null;
+ }
+ // 10-Jun-2020, tatu: Legacy handling from pre-2.12... should we still have it?
+ if (_hasTextualNull(value)) {
+ return null;
}
return ctxt.parseDate(value);
} catch (IllegalArgumentException iae) {
@@ -560,19 +1091,6 @@ public abstract class StdDeserializer<T>
}
/**
- * Helper method for encapsulating calls to low-level double value parsing; single place
- * just because we need a work-around that must be applied to all calls.
- */
- protected final static double parseDouble(String numStr) throws NumberFormatException
- {
- // avoid some nasty float representations... but should it be MIN_NORMAL or MIN_VALUE?
- if (NumberInput.NASTY_SMALL_DOUBLE.equals(numStr)) {
- return Double.MIN_NORMAL; // since 2.7; was MIN_VALUE prior
- }
- return Double.parseDouble(numStr);
- }
-
- /**
* Helper method used for accessing String value, if possible, doing
* necessary conversion or throwing exception as necessary.
*
@@ -580,12 +1098,11 @@ public abstract class StdDeserializer<T>
*/
protected final String _parseString(JsonParser p, DeserializationContext ctxt) throws IOException
{
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_STRING) {
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
return p.getText();
}
// 07-Nov-2019, tatu: [databind#2535] Need to support byte[]->Base64 same as `StringDeserializer`
- if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
+ if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
Object ob = p.getEmbeddedObject();
if (ob instanceof byte[]) {
return ctxt.getBase64Variant().encode((byte[]) ob, false);
@@ -596,18 +1113,11 @@ public abstract class StdDeserializer<T>
// otherwise, try conversion using toString()...
return ob.toString();
}
-
- // 07-Nov-2016, tatu: Caller should take care of unwrapping and there shouldn't
- // be need for extra pass here...
- /*
- // [databind#381]
- if ((t == JsonToken.START_ARRAY) && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final String parsed = _parseString(p, ctxt);
- _verifyEndArrayForSingle(p, ctxt);
- return parsed;
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ if (p.hasToken(JsonToken.START_OBJECT)) {
+ return ctxt.extractScalarFromObject(p, this, _valueClass);
}
- */
+
String value = p.getValueAsString();
if (value != null) {
return value;
@@ -616,36 +1126,6 @@ public abstract class StdDeserializer<T>
}
/**
- * Helper method that may be used to support fallback for Empty String / Empty Array
- * non-standard representations; usually for things serialized as JSON Objects.
- *
- * @since 2.5
- */
- @SuppressWarnings("unchecked")
- protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt)
- throws IOException
- {
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.START_ARRAY) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
- t = p.nextToken();
- if (t == JsonToken.END_ARRAY) {
- return null;
- }
- return (T) ctxt.handleUnexpectedToken(handledType(), p);
- }
- } else if (t == JsonToken.VALUE_STRING) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
- String str = p.getText().trim();
- if (str.isEmpty()) {
- return null;
- }
- }
- }
- return (T) ctxt.handleUnexpectedToken(handledType(), p);
- }
-
- /**
* Helper method called to determine if we are seeing String value of
* "null", and, further, that it should be coerced to null just like
* null token.
@@ -656,13 +1136,6 @@ public abstract class StdDeserializer<T>
return "null".equals(value);
}
- /**
- * @since 2.9
- */
- protected boolean _isEmptyOrTextualNull(String value) {
- return value.isEmpty() || "null".equals(value);
- }
-
protected final boolean _isNegInf(String text) {
return "-Infinity".equals(text) || "-INF".equals(text);
}
@@ -673,92 +1146,140 @@ public abstract class StdDeserializer<T>
protected final boolean _isNaN(String text) { return "NaN".equals(text); }
+ // @since 2.12
+ protected final static boolean _isBlank(String text)
+ {
+ final int len = text.length();
+ for (int i = 0; i < len; ++i) {
+ if (text.charAt(i) > 0x0020) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/*
- /**********************************************************
- /* Helper methods for sub-classes regarding decoding from
- /* alternate representations
- /**********************************************************
+ /****************************************************
+ /* Helper methods for sub-classes, new (2.12+)
+ /****************************************************
*/
/**
- * Helper method that allows easy support for array-related {@link DeserializationFeature}s
- * `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` and `UNWRAP_SINGLE_VALUE_ARRAYS`: checks for either
- * empty array, or single-value array-wrapped value (respectively), and either reports
- * an exception (if no match, or feature(s) not enabled), or returns appropriate
- * result value.
- *<p>
- * This method should NOT be called if Array representation is explicitly supported
- * for type: it should only be called in case it is otherwise unrecognized.
- *<p>
- * NOTE: in case of unwrapped single element, will handle actual decoding
- * by calling {@link #_deserializeWrappedValue}, which by default calls
- * {@link #deserialize(JsonParser, DeserializationContext)}.
- *
- * @since 2.9
+ * @since 2.12
*/
- protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
+ protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, String value)
+ throws IOException
{
- JsonToken t;
- if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) {
- t = p.nextToken();
- if (t == JsonToken.END_ARRAY) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
- return getNullValue(ctxt);
- }
- }
- if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- final T parsed = deserialize(p, ctxt);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
+ return _checkFromStringCoercion(ctxt, value, logicalType(), handledType());
+ }
+
+ /**
+ * @since 2.12
+ */
+ protected CoercionAction _checkFromStringCoercion(DeserializationContext ctxt, String value,
+ LogicalType logicalType, Class<?> rawTargetType)
+ throws IOException
+ {
+ final CoercionAction act;
+
+ if (value.length() == 0) {
+ act = ctxt.findCoercionAction(logicalType, rawTargetType,
+ CoercionInputShape.EmptyString);
+ return _checkCoercionActionFail(ctxt, act, "empty String (\"\")");
+ } else if (_isBlank(value)) {
+ act = ctxt.findCoercionFromBlankString(logicalType, rawTargetType, CoercionAction.Fail);
+ return _checkCoercionActionFail(ctxt, act, "blank String (all whitespace)");
} else {
- t = p.getCurrentToken();
+ act = ctxt.findCoercionAction(logicalType, rawTargetType, CoercionInputShape.String);
+ if (act == CoercionAction.Fail) {
+ // since it MIGHT (but might not), create desc here, do not use helper
+ ctxt.reportInputMismatch(this,
+"Cannot coerce String value (\"%s\") to %s (but might if coercion using `CoercionConfig` was enabled)",
+value, _coercedTypeDesc());
+ }
}
- @SuppressWarnings("unchecked")
- T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.getCurrentToken(), p, null);
- return result;
+ return act;
}
/**
- * Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}:
- * default implementation simply calls
- * {@link #deserialize(JsonParser, DeserializationContext)},
- * but handling may be overridden.
- *
- * @since 2.9
+ * @since 2.12
*/
- protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException
+ protected CoercionAction _checkFloatToIntCoercion(JsonParser p, DeserializationContext ctxt,
+ Class<?> rawTargetType)
+ throws IOException
{
- // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
- // either supporting nested arrays, or to cause infinite looping.
- if (p.hasToken(JsonToken.START_ARRAY)) {
- String msg = String.format(
-"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
- ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
- "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
- @SuppressWarnings("unchecked")
- T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.getCurrentToken(), p, msg);
- return result;
+ final CoercionAction act = ctxt.findCoercionAction(LogicalType.Integer,
+ rawTargetType, CoercionInputShape.Float);
+ if (act == CoercionAction.Fail) {
+ _checkCoercionActionFail(ctxt, act, "Floating-point value ("+p.getText()+")");
}
- return (T) deserialize(p, ctxt);
+ return act;
}
- /*
- /****************************************************
- /* Helper methods for sub-classes, coercions
- /****************************************************
+ /**
+ * @since 2.12
*/
+ protected Boolean _coerceBooleanFromInt(JsonParser p, DeserializationContext ctxt,
+ Class<?> rawTargetType)
+ throws IOException
+ {
+ CoercionAction act = ctxt.findCoercionAction(LogicalType.Boolean, rawTargetType, CoercionInputShape.Integer);
+ switch (act) {
+ case Fail:
+ _checkCoercionActionFail(ctxt, act, "Integer value ("+p.getText()+")");
+ break;
+ case AsNull:
+ return null;
+ case AsEmpty:
+ return Boolean.FALSE;
+ default:
+ }
+ // 13-Oct-2016, tatu: As per [databind#1324], need to be careful wrt
+ // degenerate case of huge integers, legal in JSON.
+ // Also note that number tokens can not have WS to trim:
+ if (p.getNumberType() == NumberType.INT) {
+ // but minor optimization for common case is possible:
+ return p.getIntValue() != 0;
+ }
+ return !"0".equals(p.getText());
+ }
- protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctxt,
- String type) throws IOException
+ protected CoercionAction _checkCoercionActionFail(DeserializationContext ctxt,
+ CoercionAction act, String inputDesc) throws IOException
{
- ctxt.reportInputMismatch(handledType(),
-"Cannot coerce a floating-point value ('%s') into %s (enable `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to allow)",
- p.getValueAsString(), type);
+ if (act == CoercionAction.Fail) {
+ ctxt.reportInputMismatch(this,
+"Cannot coerce %s to %s (but could if coercion was enabled using `CoercionConfig`)",
+inputDesc, _coercedTypeDesc());
+ }
+ return act;
+ }
+
+ /**
+ * Method called when otherwise unrecognized String value is encountered for
+ * a non-primitive type: should see if it is String value {@code "null"}, and if so,
+ * whether it is acceptable according to configuration or not
+ *
+ * @since 2.12
+ */
+ protected boolean _checkTextualNull(DeserializationContext ctxt, String text)
+ throws JsonMappingException
+ {
+ if (_hasTextualNull(text)) {
+ if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
+ _reportFailedNullCoerce(ctxt, true, MapperFeature.ALLOW_COERCION_OF_SCALARS, "String \"null\"");
+ }
+ return true;
+ }
+ return false;
}
+ /*
+ /**********************************************************************
+ /* Helper methods for sub-classes, coercions, older (pre-2.12), non-deprecated
+ /**********************************************************************
+ */
+
/**
* Helper method called in case where an integral number is encountered, but
* config settings suggest that a coercion may be needed to "upgrade"
@@ -779,29 +1300,35 @@ public abstract class StdDeserializer<T>
if (DeserializationFeature.USE_LONG_FOR_INTS.enabledIn(feats)) {
return p.getLongValue();
}
- return p.getBigIntegerValue(); // should be optimal, whatever it is
+ return p.getNumberValue(); // should be optimal, whatever it is
}
/**
- * Method to call when JSON `null` token is encountered. Note: only called when
- * this deserializer encounters it but NOT when reached via property
+ * Method called to verify that {@code null} token from input is acceptable
+ * for primitive (unboxed) target type. It should NOT be called if {@code null}
+ * was received by other means (coerced due to configuration, or even from
+ * optionally acceptable String {@code "null"} token).
*
* @since 2.9
*/
- protected Object _coerceNullToken(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
+ protected final void _verifyNullForPrimitive(DeserializationContext ctxt) throws JsonMappingException
{
- if (isPrimitive) {
- _verifyNullForPrimitive(ctxt);
+ if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
+ ctxt.reportInputMismatch(this,
+"Cannot coerce `null` to %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)",
+ _coercedTypeDesc());
}
- return getNullValue(ctxt);
}
/**
- * Method called when JSON String with value "null" is encountered.
+ * Method called to verify that text value {@code "null"} from input is acceptable
+ * for primitive (unboxed) target type. It should not be called if actual
+ * {@code null} token was received, or if null is a result of coercion from
+ * Some other input type.
*
* @since 2.9
*/
- protected Object _coerceTextualNull(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
+ protected final void _verifyNullForPrimitiveCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
{
Enum<?> feat;
boolean enable;
@@ -809,52 +1336,93 @@ public abstract class StdDeserializer<T>
if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
enable = true;
- } else if (isPrimitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
+ } else if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
feat = DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES;
enable = false;
} else {
- return getNullValue(ctxt);
+ return;
}
- _reportFailedNullCoerce(ctxt, enable, feat, "String \"null\"");
- return null;
+ String strDesc = str.isEmpty() ? "empty String (\"\")" : String.format("String \"%s\"", str);
+ _reportFailedNullCoerce(ctxt, enable, feat, strDesc);
+ }
+
+ protected void _reportFailedNullCoerce(DeserializationContext ctxt, boolean state, Enum<?> feature,
+ String inputDesc) throws JsonMappingException
+ {
+ String enableDesc = state ? "enable" : "disable";
+ ctxt.reportInputMismatch(this, "Cannot coerce %s to Null value as %s (%s `%s.%s` to allow)",
+ inputDesc, _coercedTypeDesc(), enableDesc, feature.getClass().getSimpleName(), feature.name());
}
/**
- * Method called when JSON String with value "" (that is, zero length) is encountered.
+ * Helper method called to get a description of type into which a scalar value coercion
+ * is (most likely) being applied, to be used for constructing exception messages
+ * on coerce failure.
+ *
+ * @return Message with backtick-enclosed name of type this deserializer supports
*
* @since 2.9
*/
- protected Object _coerceEmptyString(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
- {
- Enum<?> feat;
- boolean enable;
+ protected String _coercedTypeDesc() {
+ boolean structured;
+ String typeDesc;
- if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
- feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
- enable = true;
- } else if (isPrimitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
- feat = DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES;
- enable = false;
+ JavaType t = getValueType();
+ if ((t != null) && !t.isPrimitive()) {
+ structured = (t.isContainerType() || t.isReferenceType());
+ typeDesc = ClassUtil.getTypeDescription(t);
} else {
- return getNullValue(ctxt);
+ Class<?> cls = handledType();
+ structured = cls.isArray() || Collection.class.isAssignableFrom(cls)
+ || Map.class.isAssignableFrom(cls);
+ typeDesc = ClassUtil.getClassDescription(cls);
}
- _reportFailedNullCoerce(ctxt, enable, feat, "empty String (\"\")");
- return null;
+ if (structured) {
+ return "element of "+typeDesc;
+ }
+ return typeDesc+" value";
}
- // @since 2.9
- protected final void _verifyNullForPrimitive(DeserializationContext ctxt) throws JsonMappingException
+ /*
+ /**********************************************************************
+ /* Helper methods for sub-classes, coercions, older (pre-2.12), deprecated
+ /**********************************************************************
+ */
+
+ @Deprecated // since 2.12
+ protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
- if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
- ctxt.reportInputMismatch(this,
-"Cannot coerce `null` %s (disable `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to allow)",
- _coercedTypeDesc());
+ // 13-Oct-2016, tatu: As per [databind#1324], need to be careful wrt
+ // degenerate case of huge integers, legal in JSON.
+ // ... this is, on the other hand, probably wrong/sub-optimal for non-JSON
+ // input. For now, no rea
+ _verifyNumberForScalarCoercion(ctxt, p);
+ // Anyway, note that since we know it's valid (JSON) integer, it can't have
+ // extra whitespace to trim.
+ return !"0".equals(p.getText());
+ }
+
+ /**
+ * @deprecated Since 2.12 use {@link #_checkFromStringCoercion} instead
+ */
+ @Deprecated
+ protected void _verifyStringForScalarCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
+ {
+ MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
+ if (!ctxt.isEnabled(feat)) {
+ ctxt.reportInputMismatch(this, "Cannot coerce String \"%s\" to %s (enable `%s.%s` to allow)",
+ str, _coercedTypeDesc(), feat.getClass().getSimpleName(), feat.name());
}
}
- // NOTE: only for primitive Scalars
- // @since 2.9
- protected final void _verifyNullForPrimitiveCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
+ /**
+ * Method called when JSON String with value "" (that is, zero length) is encountered.
+ *
+ * @deprecated Since 2.12
+ */
+ @Deprecated
+ protected Object _coerceEmptyString(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
{
Enum<?> feat;
boolean enable;
@@ -862,18 +1430,26 @@ public abstract class StdDeserializer<T>
if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
enable = true;
- } else if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
+ } else if (isPrimitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
feat = DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES;
enable = false;
} else {
- return;
+ return getNullValue(ctxt);
}
- String strDesc = str.isEmpty() ? "empty String (\"\")" : String.format("String \"%s\"", str);
- _reportFailedNullCoerce(ctxt, enable, feat, strDesc);
+ _reportFailedNullCoerce(ctxt, enable, feat, "empty String (\"\")");
+ return null;
}
- // NOTE: for non-primitive Scalars
- // @since 2.9
+ @Deprecated // since 2.12
+ protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctxt,
+ String type) throws IOException
+ {
+ ctxt.reportInputMismatch(handledType(),
+"Cannot coerce a floating-point value ('%s') into %s (enable `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to allow)",
+ p.getValueAsString(), type);
+ }
+
+ @Deprecated // since 2.12
protected final void _verifyNullForScalarCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
{
if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
@@ -882,17 +1458,7 @@ public abstract class StdDeserializer<T>
}
}
- // @since 2.9
- protected void _verifyStringForScalarCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
- {
- MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
- if (!ctxt.isEnabled(feat)) {
- ctxt.reportInputMismatch(this, "Cannot coerce String \"%s\" %s (enable `%s.%s` to allow)",
- str, _coercedTypeDesc(), feat.getClass().getSimpleName(), feat.name());
- }
- }
-
- // @since 2.9
+ @Deprecated // since 2.12
protected void _verifyNumberForScalarCoercion(DeserializationContext ctxt, JsonParser p) throws IOException
{
MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
@@ -900,47 +1466,31 @@ public abstract class StdDeserializer<T>
// 31-Mar-2017, tatu: Since we don't know (or this deep, care) about exact type,
// access as a String: may require re-encoding by parser which should be fine
String valueDesc = p.getText();
- ctxt.reportInputMismatch(this, "Cannot coerce Number (%s) %s (enable `%s.%s` to allow)",
+ ctxt.reportInputMismatch(this, "Cannot coerce Number (%s) to %s (enable `%s.%s` to allow)",
valueDesc, _coercedTypeDesc(), feat.getClass().getSimpleName(), feat.name());
}
}
- protected void _reportFailedNullCoerce(DeserializationContext ctxt, boolean state, Enum<?> feature,
- String inputDesc) throws JsonMappingException
+ @Deprecated // since 2.12
+ protected Object _coerceNullToken(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
{
- String enableDesc = state ? "enable" : "disable";
- ctxt.reportInputMismatch(this, "Cannot coerce %s to Null value %s (%s `%s.%s` to allow)",
- inputDesc, _coercedTypeDesc(), enableDesc, feature.getClass().getSimpleName(), feature.name());
+ if (isPrimitive) {
+ _verifyNullForPrimitive(ctxt);
+ }
+ return getNullValue(ctxt);
}
- /**
- * Helper method called to get a description of type into which a scalar value coercion
- * is (most likely) being applied, to be used for constructing exception messages
- * on coerce failure.
- *
- * @return Message with backtick-enclosed name of type this deserializer supports
- *
- * @since 2.9
- */
- protected String _coercedTypeDesc() {
- boolean structured;
- String typeDesc;
-
- JavaType t = getValueType();
- if ((t != null) && !t.isPrimitive()) {
- structured = (t.isContainerType() || t.isReferenceType());
- // 21-Jul-2017, tatu: Probably want to change this (JavaType.toString() not very good) but...
- typeDesc = "'"+t.toString()+"'";
- } else {
- Class<?> cls = handledType();
- structured = cls.isArray() || Collection.class.isAssignableFrom(cls)
- || Map.class.isAssignableFrom(cls);
- typeDesc = ClassUtil.nameOf(cls);
- }
- if (structured) {
- return "as content of type "+typeDesc;
+ @Deprecated // since 2.12
+ protected Object _coerceTextualNull(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException {
+ if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
+ _reportFailedNullCoerce(ctxt, true, MapperFeature.ALLOW_COERCION_OF_SCALARS, "String \"null\"");
}
- return "for type "+typeDesc;
+ return getNullValue(ctxt);
+ }
+
+ @Deprecated // since 2.12
+ protected boolean _isEmptyOrTextualNull(String value) {
+ return value.isEmpty() || "null".equals(value);
}
/*
@@ -968,6 +1518,9 @@ public abstract class StdDeserializer<T>
/**
* Helper method to check whether given text refers to what looks like a clean simple
* integer number, consisting of optional sign followed by a sequence of digits.
+ *<p>
+ * Note that definition is quite loose as leading zeroes are allowed, in addition
+ * to plus sign (not just minus).
*/
protected final boolean _isIntNumber(String text)
{
@@ -975,7 +1528,17 @@ public abstract class StdDeserializer<T>
if (len > 0) {
char c = text.charAt(0);
// skip leading sign (plus not allowed for strict JSON numbers but...)
- int i = (c == '-' || c == '+') ? 1 : 0;
+ int i;
+
+ if (c == '-' || c == '+') {
+ if (len == 1) {
+ return false;
+ }
+ i = 1;
+ } else {
+ i = 0;
+ }
+ // We will allow leading
for (; i < len; ++i) {
int ch = text.charAt(i);
if (ch > '9' || ch < '0') {
@@ -1181,6 +1744,24 @@ public abstract class StdDeserializer<T>
return null;
}
+ // @since 2.12
+ protected CoercionAction _findCoercionFromEmptyString(DeserializationContext ctxt) {
+ return ctxt.findCoercionAction(logicalType(), handledType(),
+ CoercionInputShape.EmptyString);
+ }
+
+ // @since 2.12
+ protected CoercionAction _findCoercionFromEmptyArray(DeserializationContext ctxt) {
+ return ctxt.findCoercionAction(logicalType(), handledType(),
+ CoercionInputShape.EmptyArray);
+ }
+
+ // @since 2.12
+ protected CoercionAction _findCoercionFromBlankString(DeserializationContext ctxt) {
+ return ctxt.findCoercionFromBlankString(logicalType(), handledType(),
+ CoercionAction.Fail);
+ }
+
/*
/**********************************************************
/* Helper methods for sub-classes, problem reporting
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java
index 62f27abef..7c21f672d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java
@@ -5,6 +5,7 @@ import java.io.IOException;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
/**
@@ -20,22 +21,16 @@ public abstract class StdScalarDeserializer<T> extends StdDeserializer<T>
// since 2.5
protected StdScalarDeserializer(StdScalarDeserializer<?> src) { super(src); }
-
- @Override
- public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
- return typeDeserializer.deserializeTypedFromScalar(p, ctxt);
- }
- /**
- * Overridden to simply call <code>deserialize()</code> method that does not take value
- * to update, since scalar values are usually non-mergeable.
+ /*
+ /**********************************************************************
+ /* Overridden accessors
+ /**********************************************************************
*/
- @Override // since 2.9
- public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue) throws IOException {
- // 25-Oct-2016, tatu: And if attempt is made, see if we are to complain...
- ctxt.handleBadMerge(this);
- // if that does not report an exception we can just delegate
- return deserialize(p, ctxt);
+
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.OtherScalar;
}
/**
@@ -59,4 +54,27 @@ public abstract class StdScalarDeserializer<T> extends StdDeserializer<T>
public AccessPattern getEmptyAccessPattern() {
return AccessPattern.CONSTANT;
}
+
+ /*
+ /**********************************************************************
+ /* Default deserialization method impls
+ /**********************************************************************
+ */
+
+ @Override
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
+ return typeDeserializer.deserializeTypedFromScalar(p, ctxt);
+ }
+
+ /**
+ * Overridden to simply call <code>deserialize()</code> method that does not take value
+ * to update, since scalar values are usually non-mergeable.
+ */
+ @Override // since 2.9
+ public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue) throws IOException {
+ // 25-Oct-2016, tatu: And if attempt is made, see if we are to complain...
+ ctxt.handleBadMerge(this);
+ // if that does not report an exception we can just delegate
+ return deserialize(p, ctxt);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java
index e4ac0fd6f..2f4151271 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java
@@ -6,7 +6,6 @@ import java.lang.reflect.InvocationTargetException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.*;
-import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
import com.fasterxml.jackson.databind.util.ClassUtil;
@@ -66,9 +65,6 @@ public class StdValueInstantiator
protected AnnotatedWithParams _fromDoubleCreator;
protected AnnotatedWithParams _fromBooleanCreator;
- // // // Incomplete creator
- protected AnnotatedParameter _incompleteParameter;
-
/*
/**********************************************************
/* Life-cycle
@@ -165,10 +161,6 @@ public class StdValueInstantiator
_fromBooleanCreator = creator;
}
- public void configureIncompleteParameter(AnnotatedParameter parameter) {
- _incompleteParameter = parameter;
- }
-
/*
/**********************************************************
/* Public API implementation; metadata
@@ -259,7 +251,7 @@ public class StdValueInstantiator
/* Public API implementation; instantiation from JSON Object
/**********************************************************
*/
-
+
@Override
public Object createUsingDefault(DeserializationContext ctxt) throws IOException
{
@@ -319,17 +311,17 @@ public class StdValueInstantiator
@Override
public Object createFromString(DeserializationContext ctxt, String value) throws IOException
{
- if (_fromStringCreator == null) {
- return _createFromStringFallbacks(ctxt, value);
- }
- try {
- return _fromStringCreator.call1(value);
- } catch (Throwable t) {
- return ctxt.handleInstantiationProblem(_fromStringCreator.getDeclaringClass(),
- value, rewrapCtorProblem(ctxt, t));
+ if (_fromStringCreator != null) {
+ try {
+ return _fromStringCreator.call1(value);
+ } catch (Throwable t) {
+ return ctxt.handleInstantiationProblem(_fromStringCreator.getDeclaringClass(),
+ value, rewrapCtorProblem(ctxt, t));
+ }
}
+ return super.createFromString(ctxt, value);
}
-
+
@Override
public Object createFromInt(DeserializationContext ctxt, int value) throws IOException
{
@@ -400,7 +392,7 @@ public class StdValueInstantiator
arg, rewrapCtorProblem(ctxt, t0));
}
}
-
+
/*
/**********************************************************
/* Extended API: configuration mutators, accessors
@@ -427,11 +419,6 @@ public class StdValueInstantiator
return _withArgsCreator;
}
- @Override
- public AnnotatedParameter getIncompleteParameter() {
- return _incompleteParameter;
- }
-
/*
/**********************************************************
/* Internal methods
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java
index a348a4019..798a92624 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java
@@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ObjectBuffer;
@@ -75,6 +76,11 @@ public final class StringArrayDeserializer
_skipNullValues = NullsConstantProvider.isSkipper(nuller);
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Array;
+ }
+
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
return Boolean.TRUE;
@@ -144,7 +150,7 @@ public final class StringArrayDeserializer
while (true) {
String value = p.nextTextValue();
if (value == null) {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_ARRAY) {
break;
}
@@ -200,7 +206,7 @@ public final class StringArrayDeserializer
*/
String value;
if (p.nextTextValue() == null) {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_ARRAY) {
break;
}
@@ -264,7 +270,7 @@ public final class StringArrayDeserializer
while (true) {
String value = p.nextTextValue();
if (value == null) {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_ARRAY) {
break;
}
@@ -304,12 +310,8 @@ public final class StringArrayDeserializer
: _parseString(p, ctxt);
return new String[] { value };
}
- if (p.hasToken(JsonToken.VALUE_STRING)
- && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
- String str = p.getText();
- if (str.length() == 0) {
- return null;
- }
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _deserializeFromString(p, ctxt);
}
return (String[]) ctxt.handleUnexpectedToken(_valueClass, p);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java
index 101d03b0f..f4ac7ea73 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
/**
* Specifically optimized version for {@link java.util.Collection}s
@@ -90,7 +91,12 @@ public final class StringCollectionDeserializer
// are involved
return (_valueDeserializer == null) && (_delegateDeserializer == null);
}
-
+
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Collection;
+ }
+
/*
/**********************************************************
/* Validation, post-processing
@@ -194,7 +200,7 @@ public final class StringCollectionDeserializer
result.add(value);
continue;
}
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_ARRAY) {
break;
}
@@ -226,7 +232,7 @@ public final class StringCollectionDeserializer
*/
String value;
if (p.nextTextValue() == null) {
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.END_ARRAY) {
break;
}
@@ -271,11 +277,14 @@ public final class StringCollectionDeserializer
((_unwrapSingle == null) &&
ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
if (!canWrap) {
- return (Collection<String>) ctxt.handleUnexpectedToken(_containerType.getRawClass(), p);
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _deserializeFromString(p, ctxt);
+ }
+ return (Collection<String>) ctxt.handleUnexpectedToken(_containerType, p);
}
// Strings are one of "native" (intrinsic) types, so there's never type deserializer involved
JsonDeserializer<String> valueDes = _valueDeserializer;
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
String value;
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java
index a4b25c060..2cdf67a1d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
@JacksonStdImpl
public class StringDeserializer extends StdScalarDeserializer<String> // non-final since 2.9
@@ -19,6 +20,11 @@ public class StringDeserializer extends StdScalarDeserializer<String> // non-fin
public StringDeserializer() { super(String.class); }
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Textual;
+ }
+
// since 2.6, slightly faster lookups for this very common type
@Override
public boolean isCachable() { return true; }
@@ -51,6 +57,10 @@ public class StringDeserializer extends StdScalarDeserializer<String> // non-fin
// otherwise, try conversion using toString()...
return ob.toString();
}
+ // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ if (t == JsonToken.START_OBJECT) {
+ return ctxt.extractScalarFromObject(p, this, _valueClass);
+ }
// allow coercions for other scalar types
// 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
// "real" scalar
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ThrowableDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ThrowableDeserializer.java
index 087895682..65d5aa018 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ThrowableDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ThrowableDeserializer.java
@@ -85,7 +85,7 @@ public class ThrowableDeserializer
int pendingIx = 0;
for (; !p.hasToken(JsonToken.END_OBJECT); p.nextToken()) {
- String propName = p.getCurrentName();
+ String propName = p.currentName();
SettableBeanProperty prop = _beanProperties.find(propName);
p.nextToken(); // to point to field value
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java
index 5435e97f4..cf2214960 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.TokenBuffer;
/**
@@ -26,6 +27,11 @@ public class TokenBufferDeserializer extends StdScalarDeserializer<TokenBuffer>
public TokenBufferDeserializer() { super(TokenBuffer.class); }
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Untyped;
+ }
+
@Override
public TokenBuffer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return createBufferInstance(p).deserialize(p, ctxt);
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java
index 797569e6e..65e8a87fe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java
@@ -26,6 +26,11 @@ public class UUIDDeserializer extends FromStringDeserializer<UUID>
public UUIDDeserializer() { super(UUID.class); }
+ @Override // since 2.12
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ return new UUID(0L, 0L);
+ }
+
@Override
protected UUID _deserialize(String id, DeserializationContext ctxt) throws IOException
{
@@ -62,15 +67,14 @@ public class UUIDDeserializer extends FromStringDeserializer<UUID>
return new UUID(hi, lo);
}
-
+
@Override
protected UUID _deserializeEmbedded(Object ob, DeserializationContext ctxt) throws IOException
{
if (ob instanceof byte[]) {
return _fromBytes((byte[]) ob, ctxt);
}
- super._deserializeEmbedded(ob, ctxt);
- return null; // never gets here
+ return super._deserializeEmbedded(ob, ctxt);
}
private UUID _badFormat(String uuidStr, DeserializationContext ctxt)
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java
index 2f0df1d78..ec949d469 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java
@@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.ObjectBuffer;
@@ -197,9 +198,11 @@ public class UntypedObjectDeserializer
&& getClass() == UntypedObjectDeserializer.class) {
return Vanilla.instance(preventMerge);
}
+
if (preventMerge != _nonMerging) {
return new UntypedObjectDeserializer(this, preventMerge);
}
+
return this;
}
@@ -222,6 +225,11 @@ public class UntypedObjectDeserializer
return true;
}
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Untyped;
+ }
+
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
// 21-Apr-2017, tatu: Bit tricky... some values, yes. So let's say "dunno"
@@ -231,7 +239,7 @@ public class UntypedObjectDeserializer
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_START_OBJECT:
case JsonTokenId.ID_FIELD_NAME:
// 28-Oct-2015, tatu: [databind#989] We may also be given END_OBJECT (similar to FIELD_NAME),
@@ -298,7 +306,7 @@ public class UntypedObjectDeserializer
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) throws IOException
{
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
// First: does it look like we had type id wrapping of some kind?
case JsonTokenId.ID_START_ARRAY:
case JsonTokenId.ID_START_OBJECT:
@@ -357,7 +365,7 @@ public class UntypedObjectDeserializer
return deserialize(p, ctxt);
}
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_START_OBJECT:
case JsonTokenId.ID_FIELD_NAME:
// We may also be given END_OBJECT (similar to FIELD_NAME),
@@ -487,7 +495,7 @@ public class UntypedObjectDeserializer
{
String key1;
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.START_OBJECT) {
key1 = p.nextFieldName();
@@ -501,18 +509,17 @@ public class UntypedObjectDeserializer
}
if (key1 == null) {
// empty map might work; but caller may want to modify... so better just give small modifiable
- return new LinkedHashMap<String,Object>(2);
+ return new LinkedHashMap<>(2);
}
// minor optimization; let's handle 1 and 2 entry cases separately
// 24-Mar-2015, tatu: Ideally, could use one of 'nextXxx()' methods, but for
// that we'd need new method(s) in JsonDeserializer. So not quite yet.
p.nextToken();
Object value1 = deserialize(p, ctxt);
-
String key2 = p.nextFieldName();
if (key2 == null) { // has to be END_OBJECT, then
// single entry; but we want modifiable
- LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(2);
+ LinkedHashMap<String, Object> result = new LinkedHashMap<>(2);
result.put(key1, value1);
return result;
}
@@ -520,25 +527,74 @@ public class UntypedObjectDeserializer
Object value2 = deserialize(p, ctxt);
String key = p.nextFieldName();
-
if (key == null) {
- LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4);
+ LinkedHashMap<String, Object> result = new LinkedHashMap<>(4);
result.put(key1, value1);
- result.put(key2, value2);
+ if (result.put(key2, value2) != null) {
+ // 22-May-2020, tatu: [databind#2733] may need extra handling
+ return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
+ }
return result;
}
// And then the general case; default map size is 16
- LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
+ LinkedHashMap<String, Object> result = new LinkedHashMap<>();
result.put(key1, value1);
- result.put(key2, value2);
+ if (result.put(key2, value2) != null) {
+ // 22-May-2020, tatu: [databind#2733] may need extra handling
+ return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
+ }
do {
p.nextToken();
- result.put(key, deserialize(p, ctxt));
+ final Object newValue = deserialize(p, ctxt);
+ final Object oldValue = result.put(key, newValue);
+ if (oldValue != null) {
+ return _mapObjectWithDups(p, ctxt, result, key, oldValue, newValue,
+ p.nextFieldName());
+ }
} while ((key = p.nextFieldName()) != null);
return result;
}
+ // @since 2.12 (wrt [databind#2733]
+ protected Object _mapObjectWithDups(JsonParser p, DeserializationContext ctxt,
+ final Map<String, Object> result, String key,
+ Object oldValue, Object newValue, String nextKey) throws IOException
+ {
+ final boolean squashDups = ctxt.isEnabled(StreamReadCapability.DUPLICATE_PROPERTIES);
+
+ if (squashDups) {
+ _squashDups(result, key, oldValue, newValue);
+ }
+
+ while (nextKey != null) {
+ p.nextToken();
+ newValue = deserialize(p, ctxt);
+ oldValue = result.put(nextKey, newValue);
+ if ((oldValue != null) && squashDups) {
+ _squashDups(result, key, oldValue, newValue);
+ }
+ nextKey = p.nextFieldName();
+ }
+
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void _squashDups(final Map<String, Object> result, String key,
+ Object oldValue, Object newValue)
+ {
+ if (oldValue instanceof List<?>) {
+ ((List<Object>) oldValue).add(newValue);
+ result.put(key, oldValue);
+ } else {
+ ArrayList<Object> l = new ArrayList<>();
+ l.add(oldValue);
+ l.add(newValue);
+ result.put(key, l);
+ }
+ }
+
/**
* Method called to map a JSON Array into a Java Object array (Object[]).
*/
@@ -565,7 +621,7 @@ public class UntypedObjectDeserializer
protected Object mapObject(JsonParser p, DeserializationContext ctxt,
Map<Object,Object> m) throws IOException
{
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
}
@@ -629,7 +685,12 @@ public class UntypedObjectDeserializer
}
return std;
}
-
+
+ @Override // since 2.12
+ public LogicalType logicalType() {
+ return LogicalType.Untyped;
+ }
+
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
// 21-Apr-2017, tatu: Bit tricky... some values, yes. So let's say "dunno"
@@ -640,7 +701,7 @@ public class UntypedObjectDeserializer
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_START_OBJECT:
{
JsonToken t = p.nextToken();
@@ -703,7 +764,7 @@ public class UntypedObjectDeserializer
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException
{
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_START_ARRAY:
case JsonTokenId.ID_START_OBJECT:
case JsonTokenId.ID_FIELD_NAME:
@@ -747,7 +808,7 @@ public class UntypedObjectDeserializer
return deserialize(p, ctxt);
}
- switch (p.getCurrentTokenId()) {
+ switch (p.currentTokenId()) {
case JsonTokenId.ID_END_OBJECT:
case JsonTokenId.ID_END_ARRAY:
return intoValue;
@@ -881,18 +942,72 @@ public class UntypedObjectDeserializer
if (key == null) {
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4);
result.put(key1, value1);
- result.put(key2, value2);
+ if (result.put(key2, value2) != null) {
+ // 22-May-2020, tatu: [databind#2733] may need extra handling
+ return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
+ }
return result;
}
// And then the general case; default map size is 16
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
result.put(key1, value1);
- result.put(key2, value2);
+ if (result.put(key2, value2) != null) {
+ // 22-May-2020, tatu: [databind#2733] may need extra handling
+ return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
+ }
+
do {
p.nextToken();
- result.put(key, deserialize(p, ctxt));
+ final Object newValue = deserialize(p, ctxt);
+ final Object oldValue = result.put(key, newValue);
+ if (oldValue != null) {
+ return _mapObjectWithDups(p, ctxt, result, key, oldValue, newValue,
+ p.nextFieldName());
+ }
} while ((key = p.nextFieldName()) != null);
return result;
}
+
+ // NOTE: copied from above (alas, no easy way to share/reuse)
+ // @since 2.12 (wrt [databind#2733]
+ protected Object _mapObjectWithDups(JsonParser p, DeserializationContext ctxt,
+ final Map<String, Object> result, String key,
+ Object oldValue, Object newValue, String nextKey) throws IOException
+ {
+ final boolean squashDups = ctxt.isEnabled(StreamReadCapability.DUPLICATE_PROPERTIES);
+
+ if (squashDups) {
+ _squashDups(result, key, oldValue, newValue);
+ }
+
+ while (nextKey != null) {
+ p.nextToken();
+ newValue = deserialize(p, ctxt);
+ oldValue = result.put(nextKey, newValue);
+ if ((oldValue != null) && squashDups) {
+ _squashDups(result, key, oldValue, newValue);
+ }
+ nextKey = p.nextFieldName();
+ }
+
+ return result;
+ }
+
+ // NOTE: copied from above (alas, no easy way to share/reuse)
+ @SuppressWarnings("unchecked")
+ private void _squashDups(final Map<String, Object> result, String key,
+ Object oldValue, Object newValue)
+ {
+ if (oldValue instanceof List<?>) {
+ ((List<Object>) oldValue).add(newValue);
+ result.put(key, oldValue);
+ } else {
+ ArrayList<Object> l = new ArrayList<>();
+ l.add(oldValue);
+ l.add(newValue);
+ result.put(key, l);
+ }
+ }
+
}
}