aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--pom.xml72
-rw-r--r--release-notes/CREDITS-2.x38
-rw-r--r--release-notes/VERSION-2.x48
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java176
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java123
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java217
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java20
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/JsonNode.java4
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/MapperFeature.java28
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java227
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ObjectReader.java181
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java2
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java6
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/cfg/CoercionAction.java42
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfig.java41
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfigs.java307
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/cfg/CoercionInputShape.java96
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java2
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/cfg/MutableCoercionConfig.java36
-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
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java14
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java4
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java48
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java103
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java8
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java10
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java2
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/node/TreeTraversingParser.java9
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java22
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java28
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/impl/FailingSerializer.java20
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/impl/PropertySerializerMap.java8
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/impl/UnsupportedTypeSerializer.java37
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java14
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/ser/std/StdScalarSerializer.java13
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/type/ArrayType.java15
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/type/LogicalType.java136
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java36
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java46
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java5
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java53
-rw-r--r--src/test-jdk14/java/com/fasterxml/jackson/databind/RecordTest.java105
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java4
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java8
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/CoerceContainersTest.java175
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/CoerceEmptyArrayTest.java255
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java298
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/CoerceJDKScalarsTest.java (renamed from src/test/java/com/fasterxml/jackson/databind/struct/ScalarCoercionTest.java)60
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java281
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/CoercePojosTest.java188
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/CoerceToBooleanTest.java273
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java84
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/TestArrayConversions.java4
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/TestMapConversions.java6
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/convert/TestStringConversions.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeser1805Test.java78
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeserFailOnUnknown2719Test.java52
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyListDeser2118Test.java (renamed from src/test/java/com/fasterxml/jackson/failing/PropertyAccessReadOnly2118Test.java)21
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyListDeser2283Test.java (renamed from src/test/java/com/fasterxml/jackson/failing/ReadOnlyList2283Test.java)4
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/ReadOrWriteOnlyTest.java97
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java20
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderErrorHandling.java14
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderFailTest.java3
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithTypeParametersTest.java (renamed from src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java)43
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreatorsTest.java26
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java52
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java37
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/ArrayDeserializationTest.java (renamed from src/test/java/com/fasterxml/jackson/databind/deser/TestArrayDeserialization.java)4
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java65
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberLeniencyTest.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java152
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java25
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapWithGenericValuesDeserTest.java (renamed from src/test/java/com/fasterxml/jackson/databind/deser/TestGenericMapDeser.java)4
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/deser/jdk/VoidProperties2675Test.java61
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/interop/DateJava8FallbacksTest.java45
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/jsontype/TestMultipleTypeNames.java136
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java24
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitiveDeserTest.java53
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId825BTest.java7
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/ser/TestArraySerialization.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java151
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java27
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java9
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java27
-rw-r--r--src/test/java/com/fasterxml/jackson/failing/BuilderWithIgnored1214Test.java49
-rw-r--r--src/test/java/com/fasterxml/jackson/failing/JDKNumberDeser2644Test.java86
-rw-r--r--src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java2
-rw-r--r--src/test/java/com/fasterxml/jackson/failing/ParsingContextExtTypeId2747Test.java65
-rw-r--r--src/test/java/com/fasterxml/jackson/failing/TestUnknownProperty426.java50
-rw-r--r--src/test/java/com/fasterxml/jackson/failing/VoidProperties2675Test.java25
146 files changed, 7098 insertions, 2443 deletions
diff --git a/.travis.yml b/.travis.yml
index 0e9095c2e..4b1d45a5e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,8 @@ git:
submodules: false
# Since Jackson 2.10 build requires JDK 8 even if only JDK 7 runtime
+# But we also want to test against newer LTS versions
+# 08-Jul-2020, tatu: can not yet enable JDK14 due to new Record tests failing
jdk:
- openjdk8
- openjdk11
@@ -24,7 +26,7 @@ after_success:
branches:
only:
- master
- - "2.11"
+ - "2.12"
env:
global:
diff --git a/pom.xml b/pom.xml
index b53cf9cd3..9a51e4f71 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,16 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <!-- This module was also published with a richer model, Gradle metadata, -->
+ <!-- which should be used instead. Do not delete the following line which -->
+ <!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
+ <!-- that they should prefer consuming it instead. -->
+ <!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-base</artifactId>
- <version>2.11.2-SNAPSHOT</version>
+ <version>2.12.0-SNAPSHOT</version>
</parent>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
- <version>2.11.2-SNAPSHOT</version>
+ <version>2.12.0-SNAPSHOT</version>
<name>jackson-databind</name>
<packaging>bundle</packaging>
<description>General data-binding functionality for Jackson: works on core streaming API</description>
@@ -122,7 +127,6 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
- <version>0.8.4</version>
<executions>
<execution>
<goals>
@@ -177,8 +181,8 @@
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<links combine.children="append">
- <link>http://fasterxml.github.com/jackson-annotations/javadoc/2.11</link>
- <link>http://fasterxml.github.com/jackson-core/javadoc/2.11</link>
+ <link>http://fasterxml.github.com/jackson-annotations/javadoc/2.12</link>
+ <link>http://fasterxml.github.com/jackson-core/javadoc/2.12</link>
</links>
</configuration>
</plugin>
@@ -196,7 +200,12 @@
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
</plugin>
- </plugins>
+
+ <plugin>
+ <groupId>de.jjohannes</groupId>
+ <artifactId>gradle-module-metadata-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
</build>
<profiles>
@@ -207,6 +216,57 @@
<skipTests>true</skipTests>
</properties>
</profile>
+ <profile>
+ <!-- Build Record tests using Java 14 if JDK is available -->
+ <id>java14+</id>
+ <activation>
+ <jdk>[14,</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-test-source</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>add-test-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>src/test-jdk14/java</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <inherited>true</inherited>
+ <configuration>
+ <optimize>true</optimize>
+ <!-- Enable Java 14+ for all sources so that Intellij picks the right language level -->
+ <source>14</source>
+ <release>14</release>
+ <compilerArgs>
+ <arg>-parameters</arg>
+ <arg>--enable-preview</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <argLine>--enable-preview</argLine>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x
index be405fbab..258b188e0 100644
--- a/release-notes/CREDITS-2.x
+++ b/release-notes/CREDITS-2.x
@@ -1069,6 +1069,8 @@ Ville Koskela (vjkoskela@github)
(2.11.0)
* Reported #2486: Builder Deserialization with JsonCreator Value vs Array
(2.11.1)
+ * Contributed fix for #792: Deserialization Not Working Right with Generic Types and Builders
+ (2.12.0)
Fitz (Joongsoo.Park) (joongsoo@github)
* Contributed #2511: Add `SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL`
@@ -1144,3 +1146,39 @@ Joshua Shannon (retrodaredevil@github)
Daniel Hrabovcak (TheSpiritXIII@github)
* Reported #2796: `TypeFactory.constructType()` does not take `TypeBindings` correctly
(2.11.2)
+
+Mike Gilbode (gilbode@github)
+ * Reported #792: Deserialization Not Working Right with Generic Types and Builders
+ (2.12.0)
+
+Patrick Jungermann (pjungermann@github)
+ * Requested #1852: Allow case insensitive deserialization of String value into
+ `boolean`/`Boolean` (esp for Excel)
+ (2.12.0)
+
+Nate Bauernfeind (nbauernfeind@github)
+ * Reported #2091: `ReferenceType` does not expose valid containedType
+ (2.12.0)
+
+Xiang Zhang (zhangyangyu@github)
+ * Reported #2118: `JsonProperty.Access.READ_ONLY` does not work with "getter-as-setter"
+ Collections
+ (2.12.0)
+
+Yona Appletree (Yona-Appletree@github)
+ * Reported #2283: `JsonProperty.Access.READ_ONLY` fails with collections when a
+ property name is specified
+ (2.12.0)
+
+David Bidorff (bidorffOL@github)
+ * Reported, contributed fix for #2719: `FAIL_ON_IGNORED_PROPERTIES` does not throw
+ on `READONLY` properties with an explicit name
+ (2.12.0)
+
+Jendrik Johannes (jjohannes@github)
+ * Contributed #2726: Add Gradle Module Metadata for version alignment with Gradle 6
+ (2.12.0)
+
+Swayam Raina (swayamraina@github)
+ * Contributed #2761: Support multiple names in `JsonSubType.Type`
+ (2.12.0)
diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x
index d1182d17f..6039a2f07 100644
--- a/release-notes/VERSION-2.x
+++ b/release-notes/VERSION-2.x
@@ -4,6 +4,54 @@ Project: jackson-databind
=== Releases ===
------------------------------------------------------------------------
+2.12.0 (not yet released)
+
+#426: `@JsonIgnoreProperties` does not prevent Exception Conflicting getter/setter
+ definitions for property
+ (reported by gmkll@github)
+#921: Deserialization Not Working Right with Generic Types and Builders
+ (reported by Mike G; fix contributed by Ville K)
+#1852: Allow case insensitive deserialization of String value into
+ `boolean`/`Boolean` (esp for Excel)
+ (requested by Patrick J)
+#1886: Allow use of `@JsonFormat(with=JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)`
+ on Class
+#1919: Abstract class included as part of known type ids for error message
+ when using JsonSubTypes
+ (reported by Incara@github)
+#2066: Distinguish null from empty string for UUID deserialization
+ (requested by leonshaw@github)
+#2091: `ReferenceType` does not expose valid containedType
+ (reported by Nate B)
+#2113: Add `CoercionConfig[s]` mechanism for configuring allowed coercions
+#2118: `JsonProperty.Access.READ_ONLY` does not work with "getter-as-setter" `Collection`s
+ (reported by Xiang Z)
+#2283: `JsonProperty.Access.READ_ONLY` fails with collections when a property name is specified
+ (reported by Yona A)
+#2644: `BigDecimal` precision not retained for polymorphic deserialization
+ (reported by rost5000@github)
+#2675: Support use of `Void` valued properties (`MapperFeature.ALLOW_VOID_VALUED_PROPERTIES`)
+#2683: Explicitly fail (de)serialization of `java.time.*` types in absence of
+ registered custom (de)serializers
+#2707: Improve description included in by `DeserializationContext.handleUnexpectedToken()`
+#2719: `FAIL_ON_IGNORED_PROPERTIES` does not throw on `READONLY` properties with
+ an explicit name
+ (reported, fix contributed by David B)
+#2726: Add Gradle Module Metadata for version alignment with Gradle 6
+ (contributed by Jendrik J)
+#2732: Allow `JsonNode` auto-convert into `ArrayNode` if duplicates found (for XML)
+#2733: Allow values of "untyped" auto-convert into `List` if duplicates found (for XML)
+#2751: Add `ValueInstantiator.createContextual(...)
+#2761: Support multiple names in `JsonSubType.Type`
+ (contributed by Swayam R)
+#2776: Explicitly fail (de)serialization of `org.joda.time.*` types in absence of registered
+ custom (de)serializers
+#2784: Trailing zeros are stripped when deserializing BigDecimal values inside a
+ @JsonUnwrapped property
+ (reported by mjustin@github)
+- Add `BeanDeserializerBase.isCaseInsensitive()`
+- Some refactoring of `CollectionDeserializer` to solve CSV array handling issues
+
2.11.2 (not yet released)
#2783: Parser/Generator features not set when using `ObjectMapper.createParser()`,
diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java
index d0401ddd9..d8035f031 100644
--- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java
@@ -101,6 +101,8 @@ public abstract class AnnotationIntrospector
* Factory method for accessing "no operation" implementation
* of introspector: instance that will never find any annotation-based
* configuration.
+ *
+ * @return "no operation" instance
*/
public static AnnotationIntrospector nopInstance() {
return NopAnnotationIntrospector.instance;
@@ -126,6 +128,9 @@ public abstract class AnnotationIntrospector
* as contents.
* This usually works for sub-classes, except for proxy or delegating "container
* introspectors" which need to override implementation.
+ *
+ * @return Collection of all introspectors starting with this one, in case
+ * multiple introspectors are chained
*/
public Collection<AnnotationIntrospector> allIntrospectors() {
return Collections.singletonList(this);
@@ -140,6 +145,11 @@ public abstract class AnnotationIntrospector
* Default implementation adds this introspector in result; this usually
* works for sub-classes, except for proxy or delegating "container
* introspectors" which need to override implementation.
+ *
+ * @param result Container to add introspectors to
+ *
+ * @return Passed in {@code Collection} filled with introspectors as explained
+ * above
*/
public Collection<AnnotationIntrospector> allIntrospectors(Collection<AnnotationIntrospector> result) {
result.add(this);
@@ -165,8 +175,11 @@ public abstract class AnnotationIntrospector
* Method for checking whether given annotation is considered an
* annotation bundle: if so, all meta-annotations it has will
* be used instead of annotation ("bundle") itself.
- *
- * @since 2.0
+ *
+ * @param ann Annotated entity to introspect
+ *
+ * @return True if given annotation is considered an annotation
+ * bundle; false if not
*/
public boolean isAnnotationBundle(Annotation ann) {
return false;
@@ -185,8 +198,11 @@ public abstract class AnnotationIntrospector
* values referenced by annotated property; latter
* having precedence) should include Object Identifier,
* and if so, specify details of Object Identity used.
+ *
+ * @param ann Annotated entity to introspect
*
- * @since 2.0
+ * @return Details of Object Id as explained above, if Object Id
+ * handling to be applied; {@code null} otherwise.
*/
public ObjectIdInfo findObjectIdInfo(Annotated ann) {
return null;
@@ -194,6 +210,11 @@ public abstract class AnnotationIntrospector
/**
* Method for figuring out additional properties of an Object Identity reference
+ *
+ * @param ann Annotated entity to introspect
+ * @param objectIdInfo (optional) Base Object Id information, if any; {@code null} if none
+ *
+ * @return {@link ObjectIdInfo} augmented with possible additional information
*
* @since 2.1
*/
@@ -216,6 +237,10 @@ public abstract class AnnotationIntrospector
*<p>
* NOTE: method signature changed in 2.1, to return {@link PropertyName}
* instead of String.
+ *
+ * @param ac Annotated class to introspect
+ *
+ * @return Root name to use, if any; {@code null} if not
*/
public PropertyName findRootName(AnnotatedClass ac) {
return null;
@@ -229,6 +254,8 @@ public abstract class AnnotationIntrospector
* {@link #findPropertiesToIgnore(Annotated, boolean)} and
* {@link #findIgnoreUnknownProperties(AnnotatedClass)}.
*
+ * @param ac Annotated class to introspect
+ *
* @since 2.8
*/
public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac)
@@ -243,7 +270,7 @@ public abstract class AnnotationIntrospector
* (class, not generics aware) should be completely ignored for
* serialization and deserialization purposes.
*
- * @param ac Type to check
+ * @param ac Annotated class to introspect
*
* @return Boolean.TRUE if properties of type should be ignored;
* Boolean.FALSE if they are not to be ignored, null for default
@@ -254,6 +281,8 @@ public abstract class AnnotationIntrospector
/**
* Method for finding if annotated class has associated filter; and if so,
* to return id that is used to locate filter.
+ *
+ * @param ann Annotated entity to introspect
*
* @return Id of the filter to use for filtering properties of annotated
* class, if any; or null if none found.
@@ -265,7 +294,9 @@ public abstract class AnnotationIntrospector
* class, if any specified by annotations; and if so, either return
* a {@link PropertyNamingStrategy} instance, or Class to use for
* creating instance
- *
+ *
+ * @param ac Annotated class to introspect
+ *
* @return Sub-class or instance of {@link PropertyNamingStrategy}, if one
* is specified for given class; null if not.
*
@@ -279,6 +310,8 @@ public abstract class AnnotationIntrospector
* There are no further definitions for contents; for example, whether
* these may be marked up using HTML (or something like wiki format like Markup)
* is not defined.
+ *
+ * @param ac Annotated class to introspect
*
* @return Human-readable description, if any.
*
@@ -289,7 +322,10 @@ public abstract class AnnotationIntrospector
/**
* @param forSerialization True if requesting properties to ignore for serialization;
* false if for deserialization
- *
+ * @param ac Annotated class to introspect
+ *
+ * @return Array of names of properties to ignore
+ *
* @since 2.6
*
* @deprecated Since 2.8, use {@link #findPropertyIgnorals} instead
@@ -300,6 +336,10 @@ public abstract class AnnotationIntrospector
}
/**
+ * @param ac Annotated class to introspect
+ *
+ * @return Array of names of properties to ignore
+ *
* @deprecated Since 2.6, use variant that takes second argument.
*/
@Deprecated // since 2.6
@@ -309,6 +349,11 @@ public abstract class AnnotationIntrospector
/**
* Method for checking whether an annotation indicates that all unknown properties
+ * should be ignored.
+ *
+ * @param ac Annotated class to introspect
+ *
+ * @return True if class has something indicating "ignore [all] unknown properties"
*
* @deprecated Since 2.8, use {@link #findPropertyIgnorals} instead
*/
@@ -941,16 +986,18 @@ public abstract class AnnotationIntrospector
* method to be used for accessing set of miscellaneous "extra"
* properties, often bound with matching "any setter" method.
*
+ * @param ann Annotated entity to check
+ *
* @return True if such annotation is found (and is not disabled),
* false otherwise
*
* @since 2.9
*/
- public Boolean hasAnyGetter(Annotated a) {
+ public Boolean hasAnyGetter(Annotated ann) {
// 21-Nov-2016, tatu: Delegate in 2.9; remove redirect from later versions
- if (a instanceof AnnotatedMethod) {
- if (hasAnyGetterAnnotation((AnnotatedMethod) a)) {
+ if (ann instanceof AnnotatedMethod) {
+ if (hasAnyGetterAnnotation((AnnotatedMethod) ann)) {
return true;
}
}
@@ -962,6 +1009,13 @@ public abstract class AnnotationIntrospector
* have explicitly defined name. Method will overwrite entries in incoming <code>names</code>
* array with explicit names found, if any, leaving other entries unmodified.
*
+ * @param enumType Type of Enumeration
+ * @param enumValues Values of enumeration
+ * @param names Matching declared names of enumeration values (with indexes
+ * matching {@code enumValues} entries)
+ *
+ * @return Array of names to use (possible {@code names} passed as argument)
+ *
* @since 2.7
*/
public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[] names) {
@@ -976,6 +1030,11 @@ public abstract class AnnotationIntrospector
* If so, these aliases should be returned in {@code aliases} {@link List} passed
* as argument (and initialized for proper size by caller).
*
+ * @param enumType Type of Enumeration
+ * @param enumValues Values of enumeration
+ * @param aliases (in/out) Pre-allocated array where aliases found, if any, may be
+ * added (in indexes matching those of {@code enumValues})
+ *
* @since 2.11
*/
public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][] aliases) {
@@ -985,8 +1044,9 @@ public abstract class AnnotationIntrospector
/**
* Finds the Enum value that should be considered the default value, if possible.
*
- * @param enumCls The Enum class to scan for the default value.
- * @return null if none found or it's not possible to determine one.
+ * @param enumCls The Enum class to scan for the default value
+ *
+ * @return null if none found or it's not possible to determine one
*
* @since 2.8
*/
@@ -999,6 +1059,8 @@ public abstract class AnnotationIntrospector
* given enumeration entry; used when serializing enumerations
* as Strings (the standard method).
*
+ * @param value Enum value to introspect
+ *
* @return Serialized enum value.
*
* @deprecated Since 2.8: use {@link #findEnumValues} instead because this method
@@ -1012,6 +1074,8 @@ public abstract class AnnotationIntrospector
}
/**
+ * @param am Annotated method to check
+ *
* @deprecated Since 2.9 Use {@link #hasAsValue(Annotated)} instead.
*/
@Deprecated // since 2.9
@@ -1020,6 +1084,8 @@ public abstract class AnnotationIntrospector
}
/**
+ * @param am Annotated method to check
+ *
* @deprecated Since 2.9 Use {@link #hasAnyGetter} instead
*/
@Deprecated
@@ -1142,6 +1208,7 @@ public abstract class AnnotationIntrospector
* Type is usually narrowing conversion (i.e.subtype of declared type).
* Declared return type of the method is also considered acceptable.
*
+ * @param ann Annotated entity to introspect
* @param baseType Assumed type before considering annotations
*
* @return Class to use for deserialization instead of declared type
@@ -1149,7 +1216,7 @@ public abstract class AnnotationIntrospector
* @deprecated Since 2.7 call {@link #refineDeserializationType} instead
*/
@Deprecated
- public Class<?> findDeserializationType(Annotated am, JavaType baseType) {
+ public Class<?> findDeserializationType(Annotated ann, JavaType baseType) {
return null;
}
@@ -1158,6 +1225,7 @@ public abstract class AnnotationIntrospector
* method can have, to define more specific key type to use.
* It should be only be used with {@link java.util.Map} types.
*
+ * @param ann Annotated entity to introspect
* @param baseKeyType Assumed key type before considering annotations
*
* @return Class specifying more specific type to use instead of
@@ -1166,7 +1234,7 @@ public abstract class AnnotationIntrospector
* @deprecated Since 2.7 call {@link #refineDeserializationType} instead
*/
@Deprecated
- public Class<?> findDeserializationKeyType(Annotated am, JavaType baseKeyType) {
+ public Class<?> findDeserializationKeyType(Annotated ann, JavaType baseKeyType) {
return null;
}
@@ -1176,6 +1244,7 @@ public abstract class AnnotationIntrospector
* content refers to Map values and Collection/array elements.
* It should be only be used with Map, Collection and array types.
*
+ * @param ann Annotated entity to introspect
* @param baseContentType Assumed content (value) type before considering annotations
*
* @return Class specifying more specific type to use instead of
@@ -1184,7 +1253,7 @@ public abstract class AnnotationIntrospector
* @deprecated Since 2.7 call {@link #refineDeserializationType} instead
*/
@Deprecated
- public Class<?> findDeserializationContentType(Annotated am, JavaType baseContentType) {
+ public Class<?> findDeserializationContentType(Annotated ann, JavaType baseContentType) {
return null;
}
@@ -1198,6 +1267,8 @@ public abstract class AnnotationIntrospector
* Method getting {@link ValueInstantiator} to use for given
* type (class): return value can either be an instance of
* instantiator, or class of instantiator to create.
+ *
+ * @param ac Annotated class to introspect
*/
public Object findValueInstantiator(AnnotatedClass ac) {
return null;
@@ -1213,6 +1284,8 @@ public abstract class AnnotationIntrospector
* method does not allow returning instances: the reason is
* that builders have state, and a separate instance needs
* to be created for each deserialization call.
+ *
+ * @param ac Annotated class to introspect
*
* @since 2.0
*/
@@ -1221,6 +1294,8 @@ public abstract class AnnotationIntrospector
}
/**
+ * @param ac Annotated class to introspect
+ *
* @since 2.0
*/
public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
@@ -1241,13 +1316,13 @@ public abstract class AnnotationIntrospector
* is found; otherwise a non-null name (possibly
* {@link PropertyName#USE_DEFAULT}, which means "use default heuristics").
*
- * @param a Property accessor to check
- *
+ * @param ann Annotated entity to check
+ *
* @return Name to use if found; null if not.
- *
+ *
* @since 2.1
*/
- public PropertyName findNameForDeserialization(Annotated a) {
+ public PropertyName findNameForDeserialization(Annotated ann) {
return null;
}
@@ -1256,32 +1331,38 @@ public abstract class AnnotationIntrospector
* that suggests that the method is to serve as "any setter";
* method to be used for setting values of any properties for
* which no dedicated setter method is found.
+ *
+ * @param ann Annotated entity to check
*
* @return True if such annotation is found (and is not disabled),
* false otherwise
- *
+ *
* @since 2.9
*/
- public Boolean hasAnySetter(Annotated a) {
+ public Boolean hasAnySetter(Annotated ann) {
return null;
}
/**
* Method for finding possible settings for property, given annotations
* on an accessor.
+ *
+ * @param ann Annotated entity to check
*
* @since 2.9
*/
- public JsonSetter.Value findSetterInfo(Annotated a) {
+ public JsonSetter.Value findSetterInfo(Annotated ann) {
return JsonSetter.Value.empty();
}
/**
* Method for finding merge settings for property, if any.
+ *
+ * @param ann Annotated entity to check
*
* @since 2.9
*/
- public Boolean findMergeInfo(Annotated a) {
+ public Boolean findMergeInfo(Annotated ann) {
return null;
}
@@ -1296,15 +1377,15 @@ public abstract class AnnotationIntrospector
* as well as possibly as when using mix-in annotations.
*
* @param config Configuration settings in effect (for serialization or deserialization)
- * @param a Annotated accessor (usually constructor or static method) to check
+ * @param ann Annotated accessor (usually constructor or static method) to check
*
* @since 2.9
*/
- public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
+ public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated ann) {
// 13-Sep-2016, tatu: for backwards compatibility, implement using delegation
/// (remove from version AFTER 2.9)
- if (hasCreatorAnnotation(a)) {
- JsonCreator.Mode mode = findCreatorBinding(a);
+ if (hasCreatorAnnotation(ann)) {
+ JsonCreator.Mode mode = findCreatorBinding(ann);
if (mode == null) {
mode = JsonCreator.Mode.DEFAULT;
}
@@ -1319,6 +1400,8 @@ public abstract class AnnotationIntrospector
* that suggests that the method is a "creator" (aka factory)
* method to be used for construct new instances of deserialized
* values.
+ *
+ * @param ann Annotated entity to check
*
* @return True if such annotation is found (and is not disabled),
* false otherwise
@@ -1326,7 +1409,7 @@ public abstract class AnnotationIntrospector
* @deprecated Since 2.9 use {@link #findCreatorAnnotation} instead.
*/
@Deprecated
- public boolean hasCreatorAnnotation(Annotated a) {
+ public boolean hasCreatorAnnotation(Annotated ann) {
return false;
}
@@ -1336,16 +1419,22 @@ public abstract class AnnotationIntrospector
* true), for cases where there may be ambiguity (currently: single-argument
* creator with implicit but no explicit name for the argument).
*
+ * @param ann Annotated entity to check
+ *
* @since 2.5
* @deprecated Since 2.9 use {@link #findCreatorAnnotation} instead.
*/
@Deprecated
- public JsonCreator.Mode findCreatorBinding(Annotated a) {
+ public JsonCreator.Mode findCreatorBinding(Annotated ann) {
return null;
}
/**
+ * @param am Annotated method to check
+ *
* @deprecated Since 2.9 use {@link #hasAnySetter} instead.
+ *
+ * @return {@code true} if "any-setter" annotation was found; {@code false} otherwise
*/
@Deprecated // since 2.9
public boolean hasAnySetterAnnotation(AnnotatedMethod am) {
@@ -1370,12 +1459,18 @@ public abstract class AnnotationIntrospector
*<code>
* return annotated.getAnnotation(annoClass);
*</code>
- *
+ *
+ * @param ann Annotated entity to check for specified annotation
+ * @param annoClass Type of annotation to find
+ *
+ * @return Value of given annotation (as per {@code annoClass}), if entity
+ * has one; {@code null} otherwise
+ *
* @since 2.5
*/
- protected <A extends Annotation> A _findAnnotation(Annotated annotated,
+ protected <A extends Annotation> A _findAnnotation(Annotated ann,
Class<A> annoClass) {
- return annotated.getAnnotation(annoClass);
+ return ann.getAnnotation(annoClass);
}
/**
@@ -1388,20 +1483,31 @@ public abstract class AnnotationIntrospector
*<code>
* return annotated.hasAnnotation(annoClass);
*</code>
+ *
+ * @param ann Annotated entity to check for specified annotation
+ * @param annoClass Type of annotation to find
+ *
+ * @return {@code true} if specified annotation exists in given entity; {@code false} if not
*
* @since 2.5
*/
- protected boolean _hasAnnotation(Annotated annotated, Class<? extends Annotation> annoClass) {
- return annotated.hasAnnotation(annoClass);
+ protected boolean _hasAnnotation(Annotated ann, Class<? extends Annotation> annoClass) {
+ return ann.hasAnnotation(annoClass);
}
/**
* Alternative lookup method that is used to see if annotation has at least one of
* annotations of types listed in second argument.
*
+ * @param ann Annotated entity to check for specified annotation
+ * @param annoClasses Types of annotation to find
+ *
+ * @return {@code true} if at least one of specified annotation exists in given entity;
+ * {@code false} otherwise
+ *
* @since 2.7
*/
- protected boolean _hasOneOf(Annotated annotated, Class<? extends Annotation>[] annoClasses) {
- return annotated.hasOneOf(annoClasses);
+ protected boolean _hasOneOf(Annotated ann, Class<? extends Annotation>[] annoClasses) {
+ return ann.hasOneOf(annoClasses);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java
index 08977d36e..1458023ad 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.*;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.LinkedNode;
import com.fasterxml.jackson.databind.util.RootNameLookup;
@@ -49,6 +50,11 @@ public final class DeserializationConfig
*/
protected final JsonNodeFactory _nodeFactory;
+ /**
+ * @since 2.12
+ */
+ protected final CoercionConfigs _coercionConfigs;
+
/*
/**********************************************************
/* Deserialization features
@@ -98,15 +104,19 @@ public final class DeserializationConfig
/**
* Constructor used by ObjectMapper to create default configuration object instance.
+ *
+ * @since 2.12
*/
public DeserializationConfig(BaseSettings base,
SubtypeResolver str, SimpleMixInResolver mixins, RootNameLookup rootNames,
- ConfigOverrides configOverrides)
+ ConfigOverrides configOverrides,
+ CoercionConfigs coercionConfigs)
{
super(base, str, mixins, rootNames, configOverrides);
_deserFeatures = DESER_FEATURE_DEFAULTS;
- _nodeFactory = JsonNodeFactory.instance;
_problemHandlers = null;
+ _nodeFactory = JsonNodeFactory.instance;
+ _coercionConfigs = coercionConfigs;
_parserFeatures = 0;
_parserFeaturesToChange = 0;
_formatReadFeatures = 0;
@@ -116,32 +126,36 @@ public final class DeserializationConfig
/**
* Copy-constructor used for making a copy used by new {@link ObjectMapper}.
*
- * @since 2.11.2
+ * @since 2.12
*/
protected DeserializationConfig(DeserializationConfig src,
SubtypeResolver str, SimpleMixInResolver mixins, RootNameLookup rootNames,
- ConfigOverrides configOverrides)
+ ConfigOverrides configOverrides,
+ CoercionConfigs coercionConfigs)
{
super(src, str, mixins, rootNames, configOverrides);
_deserFeatures = src._deserFeatures;
_problemHandlers = src._problemHandlers;
_nodeFactory = src._nodeFactory;
+ _coercionConfigs = coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
_formatReadFeaturesToChange = src._formatReadFeaturesToChange;
}
- /**
- * @since 2.9
- * @deprecated since 2.11.2
- */
- @Deprecated
+ @Deprecated // since 2.12, remove from 2.13 or later
+ public DeserializationConfig(BaseSettings base,
+ SubtypeResolver str, SimpleMixInResolver mixins, RootNameLookup rootNames,
+ ConfigOverrides configOverrides) {
+ this(base, str, mixins, rootNames, configOverrides, new CoercionConfigs());
+ }
+
+ @Deprecated // since 2.11.2, remove from 2.13 or later
protected DeserializationConfig(DeserializationConfig src,
SimpleMixInResolver mixins, RootNameLookup rootNames,
- ConfigOverrides configOverrides)
- {
- this(src, src._subtypeResolver, mixins, rootNames, configOverrides);
+ ConfigOverrides configOverrides) {
+ this(src, src._subtypeResolver, mixins, rootNames, configOverrides, new CoercionConfigs());
}
/*
@@ -158,14 +172,15 @@ public final class DeserializationConfig
{
super(src, mapperFeatures);
_deserFeatures = deserFeatures;
- _nodeFactory = src._nodeFactory;
_problemHandlers = src._problemHandlers;
+ _nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = parserFeatures;
_parserFeaturesToChange = parserFeatureMask;
_formatReadFeatures = formatFeatures;
_formatReadFeaturesToChange = formatFeatureMask;
}
-
+
/**
* Copy constructor used to create a non-shared instance with given mix-in
* annotation definitions and subtype resolver.
@@ -174,8 +189,9 @@ public final class DeserializationConfig
{
super(src, str);
_deserFeatures = src._deserFeatures;
- _nodeFactory = src._nodeFactory;
_problemHandlers = src._problemHandlers;
+ _nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -186,8 +202,9 @@ public final class DeserializationConfig
{
super(src, base);
_deserFeatures = src._deserFeatures;
- _nodeFactory = src._nodeFactory;
_problemHandlers = src._problemHandlers;
+ _nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -200,6 +217,7 @@ public final class DeserializationConfig
_deserFeatures = src._deserFeatures;
_problemHandlers = src._problemHandlers;
_nodeFactory = f;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -213,6 +231,7 @@ public final class DeserializationConfig
_deserFeatures = src._deserFeatures;
_problemHandlers = problemHandlers;
_nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -225,6 +244,7 @@ public final class DeserializationConfig
_deserFeatures = src._deserFeatures;
_problemHandlers = src._problemHandlers;
_nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -237,6 +257,7 @@ public final class DeserializationConfig
_deserFeatures = src._deserFeatures;
_problemHandlers = src._problemHandlers;
_nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -249,6 +270,7 @@ public final class DeserializationConfig
_deserFeatures = src._deserFeatures;
_problemHandlers = src._problemHandlers;
_nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -261,6 +283,7 @@ public final class DeserializationConfig
_deserFeatures = src._deserFeatures;
_problemHandlers = src._problemHandlers;
_nodeFactory = src._nodeFactory;
+ _coercionConfigs = src._coercionConfigs;
_parserFeatures = src._parserFeatures;
_parserFeaturesToChange = src._parserFeaturesToChange;
_formatReadFeatures = src._formatReadFeatures;
@@ -705,13 +728,30 @@ public final class DeserializationConfig
*
* @since 2.5
*/
- public void initialize(JsonParser p) {
+ public JsonParser initialize(JsonParser p) {
+ if (_parserFeaturesToChange != 0) {
+ p.overrideStdFeatures(_parserFeatures, _parserFeaturesToChange);
+ }
+ if (_formatReadFeaturesToChange != 0) {
+ p.overrideFormatFeatures(_formatReadFeatures, _formatReadFeaturesToChange);
+ }
+ return p;
+ }
+
+ /**
+ * @since 2.12
+ */
+ public JsonParser initialize(JsonParser p, FormatSchema schema) {
if (_parserFeaturesToChange != 0) {
p.overrideStdFeatures(_parserFeatures, _parserFeaturesToChange);
}
if (_formatReadFeaturesToChange != 0) {
p.overrideFormatFeatures(_formatReadFeatures, _formatReadFeaturesToChange);
}
+ if (schema != null) {
+ p.setSchema(schema);
+ }
+ return p;
}
/*
@@ -867,4 +907,53 @@ public final class DeserializationConfig
}
return b.buildTypeDeserializer(this, baseType, subtypes);
}
+
+ /*
+ /**********************************************************************
+ /* CoercionConfig access
+ /**********************************************************************
+ */
+
+ /**
+ * General-purpose accessor for finding what to do when specified coercion
+ * from shape that is now always allowed to be coerced from is requested.
+ *
+ * @param targetType Logical target type of coercion
+ * @param targetClass Physical target type of coercion
+ * @param inputShape Input shape to coerce from
+ *
+ * @return CoercionAction configured for specific coercion
+ *
+ * @since 2.12
+ */
+ public CoercionAction findCoercionAction(LogicalType targetType,
+ Class<?> targetClass, CoercionInputShape inputShape)
+ {
+ return _coercionConfigs.findCoercion(this,
+ targetType, targetClass, inputShape);
+ }
+
+ /**
+ * More specialized accessor called in case of input being a blank
+ * String (one consisting of only white space characters with length of at least one).
+ * Will basically first determine if "blank as empty" is allowed: if not,
+ * returns {@code actionIfBlankNotAllowed}, otherwise returns action for
+ * {@link CoercionInputShape#EmptyString}.
+ *
+ * @param targetType Logical target type of coercion
+ * @param targetClass Physical target type of coercion
+ * @param actionIfBlankNotAllowed Return value to use in case "blanks as empty"
+ * is not allowed
+ *
+ * @return CoercionAction configured for specified coercion from blank string
+ *
+ * @since 2.12
+ */
+ public CoercionAction findCoercionFromBlankString(LogicalType targetType,
+ Class<?> targetClass,
+ CoercionAction actionIfBlankNotAllowed)
+ {
+ return _coercionConfigs.findCoercionFromBlankString(this,
+ targetType, targetClass, actionIfBlankNotAllowed);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
index 275cbcba4..438886fdd 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
@@ -11,7 +11,9 @@ import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdResolver;
import com.fasterxml.jackson.core.*;
-
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.cfg.ContextAttributes;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader;
@@ -29,6 +31,7 @@ import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.*;
@@ -97,6 +100,13 @@ public abstract class DeserializationContext
protected final int _featureFlags;
/**
+ * Capabilities of the input format.
+ *
+ * @since 2.12
+ */
+ protected final JacksonFeatureSet<StreamReadCapability> _readCapabilities;
+
+ /**
* Currently active view, if any.
*/
protected final Class<?> _view;
@@ -164,6 +174,7 @@ public abstract class DeserializationContext
}
_cache = cache;
_featureFlags = 0;
+ _readCapabilities = null;
_config = null;
_injectableValues = null;
_view = null;
@@ -178,6 +189,7 @@ public abstract class DeserializationContext
_config = src._config;
_featureFlags = src._featureFlags;
+ _readCapabilities = src._readCapabilities;
_view = src._view;
_parser = src._parser;
_injectableValues = src._injectableValues;
@@ -193,7 +205,11 @@ public abstract class DeserializationContext
{
_cache = src._cache;
_factory = src._factory;
-
+ // 08-Jun-2020. tatu: Called only for `ObjectMapper.canDeserialize()`
+ // (see [databind#2749]), not sure what's the best work-around but
+ // start with this:
+ _readCapabilities = (p == null) ? null : p.getReadCapabilities();
+
_config = config;
_featureFlags = config.getDeserializationFeatures();
_view = config.getActiveView();
@@ -203,6 +219,27 @@ public abstract class DeserializationContext
}
/**
+ * Constructor used for creating bogus per-call style instance, but
+ * without underlying parser: needed for deserializer pre-fetching
+ *
+ * @since 2.12
+ */
+ protected DeserializationContext(DeserializationContext src,
+ DeserializationConfig config)
+ {
+ _cache = src._cache;
+ _factory = src._factory;
+ _readCapabilities = null;
+
+ _config = config;
+ _featureFlags = config.getDeserializationFeatures();
+ _view = null;
+ _parser = null;
+ _injectableValues = null;
+ _attributes = null;
+ }
+
+ /**
* Copy-constructor for use with <code>copy()</code> by {@link ObjectMapper#copy()}
*/
protected DeserializationContext(DeserializationContext src) {
@@ -211,6 +248,7 @@ public abstract class DeserializationContext
_config = src._config;
_featureFlags = src._featureFlags;
+ _readCapabilities = src._readCapabilities;
_view = src._view;
_injectableValues = null;
}
@@ -323,7 +361,7 @@ public abstract class DeserializationContext
/*
/**********************************************************
- /* Public API, config setting accessors
+ /* Public API, config accessors
/**********************************************************
*/
@@ -346,6 +384,18 @@ public abstract class DeserializationContext
}
/**
+ * Accessor for checking whether input format has specified capability
+ * or not.
+ *
+ * @return True if input format has specified capability; false if not
+ *
+ * @since 2.12
+ */
+ public final boolean isEnabled(StreamReadCapability cap) {
+ return _readCapabilities.isEnabled(cap);
+ }
+
+ /**
* Bulk access method for getting the bit mask of all {@link DeserializationFeature}s
* that are enabled.
*
@@ -419,6 +469,53 @@ public abstract class DeserializationContext
}
/*
+ /**********************************************************************
+ /* Public API, CoercionConfig access (2.12+)
+ /**********************************************************************
+ */
+
+ /**
+ * General-purpose accessor for finding what to do when specified coercion
+ * from shape that is now always allowed to be coerced from is requested.
+ *
+ * @param targetType Logical target type of coercion
+ * @param targetClass Physical target type of coercion
+ * @param inputShape Input shape to coerce from
+ *
+ * @return CoercionAction configured for specific coercion
+ *
+ * @since 2.12
+ */
+ public CoercionAction findCoercionAction(LogicalType targetType,
+ Class<?> targetClass, CoercionInputShape inputShape)
+ {
+ return _config.findCoercionAction(targetType, targetClass, inputShape);
+ }
+
+ /**
+ * More specialized accessor called in case of input being a blank
+ * String (one consisting of only white space characters with length of at least one).
+ * Will basically first determine if "blank as empty" is allowed: if not,
+ * returns {@code actionIfBlankNotAllowed}, otherwise returns action for
+ * {@link CoercionInputShape#EmptyString}.
+ *
+ * @param targetType Logical target type of coercion
+ * @param targetClass Physical target type of coercion
+ * @param actionIfBlankNotAllowed Return value to use in case "blanks as empty"
+ * is not allowed
+ *
+ * @return CoercionAction configured for specified coercion from blank string
+ *
+ * @since 2.12
+ */
+ public CoercionAction findCoercionFromBlankString(LogicalType targetType,
+ Class<?> targetClass,
+ CoercionAction actionIfBlankNotAllowed)
+ {
+ return _config.findCoercionFromBlankString(targetType, targetClass, actionIfBlankNotAllowed);
+ }
+
+ /*
/**********************************************************
/* Public API, pass-through to DeserializerCache
/**********************************************************
@@ -428,7 +525,10 @@ public abstract class DeserializationContext
* Method for checking whether we could find a deserializer
* for given type.
*
- * @param type
+ * @param type Type to check
+ * @param cause (optional) Reference set to root cause if no deserializer
+ * could be found due to exception (to find the reason for failure)
+ *
* @since 2.3
*/
public boolean hasValueDeserializerFor(JavaType type, AtomicReference<Throwable> cause) {
@@ -720,7 +820,7 @@ public abstract class DeserializationContext
public Date parseDate(String dateStr) throws IllegalArgumentException
{
try {
- DateFormat df = getDateFormat();
+ DateFormat df = _getDateFormat();
return df.parse(dateStr);
} catch (ParseException e) {
throw new IllegalArgumentException(String.format(
@@ -742,6 +842,40 @@ public abstract class DeserializationContext
/*
/**********************************************************
+ /* Extension points for more esoteric data coercion (2.12)
+ /**********************************************************
+ */
+
+ /**
+ * Method to call in case incoming shape is Object Value (and parser thereby
+ * points to {@link com.fasterxml.jackson.core.JsonToken#START_OBJECT} token),
+ * but a Scalar value (potentially coercible from String value) is expected.
+ * This would typically be used to deserializer a Number, Boolean value or some other
+ * "simple" unstructured value type.
+ *
+ * @param p Actual parser to read content from
+ * @param deser Deserializer that needs extracted String value
+ * @param scalarType Immediate type of scalar to extract; usually type deserializer
+ * handles but not always (for example, deserializer for {@code int[]} would pass
+ * scalar type of {@code int})
+ *
+ * @return String value found; not {@code null} (exception should be thrown if no suitable
+ * value found)
+ *
+ * @throws IOException If there are problems either reading content (underlying parser
+ * problem) or finding expected scalar value
+ */
+ public String extractScalarFromObject(JsonParser p, JsonDeserializer<?> deser,
+ Class<?> scalarType)
+ throws IOException
+ {
+ return reportInputMismatch(scalarType, String.format(
+"Cannot deserialize value of type %s from %s (token `JsonToken.START_OBJECT`)",
+ClassUtil.getClassDescription(scalarType), _shapeForToken(JsonToken.START_OBJECT)));
+ }
+
+ /*
+ /**********************************************************
/* Convenience methods for reading parsed values
/**********************************************************
*/
@@ -1145,7 +1279,7 @@ public abstract class DeserializationContext
public Object handleUnexpectedToken(Class<?> instClass, JsonParser p)
throws IOException
{
- return handleUnexpectedToken(constructType(instClass), p.getCurrentToken(), p, null);
+ return handleUnexpectedToken(constructType(instClass), p.currentToken(), p, null);
}
/**
@@ -1187,7 +1321,7 @@ public abstract class DeserializationContext
public Object handleUnexpectedToken(JavaType targetType, JsonParser p)
throws IOException
{
- return handleUnexpectedToken(targetType, p.getCurrentToken(), p, null);
+ return handleUnexpectedToken(targetType, p.currentToken(), p, null);
}
/**
@@ -1220,19 +1354,20 @@ public abstract class DeserializationContext
}
reportBadDefinition(targetType, String.format(
"DeserializationProblemHandler.handleUnexpectedToken() for type %s returned value of type %s",
- ClassUtil.getClassDescription(targetType),
+ ClassUtil.getTypeDescription(targetType),
ClassUtil.classNameOf(instance)
));
}
h = h.next();
}
if (msg == null) {
+ final String targetDesc = ClassUtil.getTypeDescription(targetType);
if (t == null) {
- msg = String.format("Unexpected end-of-input when binding data into %s",
- ClassUtil.getTypeDescription(targetType));
+ msg = String.format("Unexpected end-of-input when trying read value of type %s",
+ targetDesc);
} else {
- msg = String.format("Cannot deserialize instance of %s out of %s token",
- ClassUtil.getTypeDescription(targetType), t);
+ msg = String.format("Cannot deserialize value of type %s from %s (token `JsonToken.%s`)",
+ targetDesc, _shapeForToken(t), t);
}
}
// 18-Jun-2020, tatu: to resolve [databind#2770], force access to `getText()` for scalars
@@ -1645,7 +1780,7 @@ trailingToken, ClassUtil.nameOf(targetType)
JsonToken expToken, String extra)
{
String msg = String.format("Unexpected token (%s), expected %s",
- p.getCurrentToken(), expToken);
+ p.currentToken(), expToken);
msg = _colonConcat(msg, extra);
return MismatchedInputException.from(p, targetType, msg);
}
@@ -1654,7 +1789,7 @@ trailingToken, ClassUtil.nameOf(targetType)
JsonToken expToken, String extra)
{
String msg = String.format("Unexpected token (%s), expected %s",
- p.getCurrentToken(), expToken);
+ p.currentToken(), expToken);
msg = _colonConcat(msg, extra);
return MismatchedInputException.from(p, targetType, msg);
}
@@ -1890,7 +2025,7 @@ trailingToken, ClassUtil.nameOf(targetType)
*/
@Deprecated
public JsonMappingException mappingException(Class<?> targetClass) {
- return mappingException(targetClass, _parser.getCurrentToken());
+ return mappingException(targetClass, _parser.currentToken());
}
/**
@@ -1909,8 +2044,12 @@ trailingToken, ClassUtil.nameOf(targetType)
/**********************************************************
*/
- protected DateFormat getDateFormat()
- {
+ @Deprecated // since 2.12, remove from 2.13 or later
+ protected DateFormat getDateFormat() {
+ return _getDateFormat();
+ }
+
+ protected DateFormat _getDateFormat() {
if (_dateFormat != null) {
return _dateFormat;
}
@@ -1923,4 +2062,48 @@ trailingToken, ClassUtil.nameOf(targetType)
_dateFormat = df = (DateFormat) df.clone();
return df;
}
+
+ // @since 2.12
+ /**
+ * Helper method for constructing description like "Object value" given
+ * {@link JsonToken} encountered.
+ */
+ protected String _shapeForToken(JsonToken t) {
+ if (t != null) {
+ switch (t) {
+ // Likely Object values
+ case START_OBJECT:
+ case END_OBJECT:
+ case FIELD_NAME:
+ return "Object value";
+
+ // Likely Array values
+ case START_ARRAY:
+ case END_ARRAY:
+ return "Array value";
+
+ case VALUE_FALSE:
+ case VALUE_TRUE:
+ return "Boolean value";
+
+ case VALUE_EMBEDDED_OBJECT:
+ return "Embedded Object";
+
+ case VALUE_NUMBER_FLOAT:
+ return "Floating-point value";
+ case VALUE_NUMBER_INT:
+ return "Integer value";
+ case VALUE_STRING:
+ return "String value";
+
+ case VALUE_NULL:
+ return "Null value";
+
+ case NOT_AVAILABLE:
+ default:
+ return "[Unavailable value]";
+ }
+ }
+ return "<end of input>";
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java
index 9cbed3eea..23ac69194 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java
@@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.*;
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;
import com.fasterxml.jackson.databind.util.NameTransformer;
@@ -207,7 +208,7 @@ public abstract class JsonDeserializer<T>
*/
/**
- * Method for accessing type of values this deserializer produces.
+ * Method for accessing concrete physical type of values this deserializer produces.
* Note that this information is not guaranteed to be exact -- it
* may be a more generic (super-type) -- but it should not be
* incorrect (return a non-related type).
@@ -215,12 +216,27 @@ public abstract class JsonDeserializer<T>
* Default implementation will return null, which means almost same
* same as returning <code>Object.class</code> would; that is, that
* nothing is known about handled type.
- *<p>
+ *
+ * @return Physical type of values this deserializer produces, if known;
+ * {@code null} if not
+ *
* @since 2.3
*/
public Class<?> handledType() { return null; }
/**
+ * Method for accessing logical type of values this deserializer produces.
+ * Typically used for further configuring handling of values, for example,
+ * to find which coercions are legal.
+ *
+ * @return Logical type of values this deserializer produces, if known;
+ * {@code null} if not
+ *
+ * @since 2.12
+ */
+ public LogicalType logicalType() { return null; }
+
+ /**
* Method called to see if deserializer instance is cachable and
* usable for other properties of same type (type for which instance
* was created).
diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java
index 646d54737..0c94ca294 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java
@@ -598,7 +598,7 @@ public abstract class JsonNode
* and 1 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation cannot be converted to an long (including structured types
+ * If representation cannot be converted to a long (including structured types
* like Objects and Arrays),
* default value of <b>0</b> will be returned; no exceptions are thrown.
*/
@@ -612,7 +612,7 @@ public abstract class JsonNode
* and 1 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation cannot be converted to an long (including structured types
+ * If representation cannot be converted to a long (including structured types
* like Objects and Arrays),
* specified <b>defaultValue</b> will be returned; no exceptions are thrown.
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
index c669a1b0e..e166f5f3c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
@@ -213,6 +213,19 @@ public enum MapperFeature implements ConfigFeature
*/
INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES(true),
+ /**
+ * Feature that determines whether nominal property type of {@link Void} is
+ * allowed for Getter methods to indicate {@code null} valued pseudo-property
+ * or not. If enabled, such properties are recognized (see [databind#2675] for
+ * reasons -- mostly things related to frameworks, code generation); if disabled,
+ * such property accessors (or at least getters) are ignored.
+ *<p>
+ * Feature is disabled by default (in 2.12) for backwards compatibility.
+ *
+ * @since 2.12
+ */
+ ALLOW_VOID_VALUED_PROPERTIES(false),
+
/*
/******************************************************
/* Access modifier handling
@@ -300,6 +313,21 @@ public enum MapperFeature implements ConfigFeature
*/
USE_BASE_TYPE_AS_DEFAULT_IMPL(false),
+ /**
+ * Feature that enables inferring builder type bindings from the value type
+ * being deserialized. This requires that the generic type declaration on
+ * the value type match that on the builder exactly: mismatched type declarations
+ * are not necessarily detected by databind.
+ *<p>
+ * Feature is enabled by default which means that deserialization does
+ * support deserializing types via builders with type parameters (generic types).
+ *<p>
+ * See: https://github.com/FasterXML/jackson-databind/issues/921
+ *
+ * @since 2.12
+ */
+ INFER_BUILDER_TYPE_BINDINGS(true),
+
/*
/******************************************************
/* View-related features
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
index 45b6373f7..df1c6ebab 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
@@ -191,8 +191,14 @@ public class ObjectMapper
* all non-final types, with exception of small number of
* "natural" types (String, Boolean, Integer, Double) that
* can be correctly inferred from JSON, and primitives (which
- * can not be polymorphic either). Typing is also enabled for
- * all array types.
+ * can not be polymorphic either).
+ * Typing is also enabled for all array types.
+ *<p>
+ * WARNING: most of the time this is <b>NOT</b> the setting you want
+ * as it tends to add Type Ids everywhere, even in cases
+ * where type can not be anything other than declared (for example
+ * if declared value type of a property is {@code final} -- for example,
+ * properties of type {@code long} (or wrapper {@code Long}).
*<p>
* Note that the only known use case for this setting is for serialization
* when passing instances of final class, and base type is not
@@ -401,6 +407,14 @@ public class ObjectMapper
*/
protected final ConfigOverrides _configOverrides;
+ /**
+ * Current set of coercion configuration definitions that define allowed
+ * (and not allowed) coercions from secondary shapes.
+ *
+ * @since 2.12
+ */
+ protected final CoercionConfigs _coercionConfigs;
+
/*
/**********************************************************
/* Configuration settings: mix-in annotations
@@ -561,13 +575,15 @@ public class ObjectMapper
_typeFactory = src._typeFactory;
_injectableValues = src._injectableValues;
_configOverrides = src._configOverrides.copy();
+ _coercionConfigs = src._coercionConfigs.copy();
_mixIns = src._mixIns.copy();
RootNameLookup rootNames = new RootNameLookup();
_serializationConfig = new SerializationConfig(src._serializationConfig,
_subtypeResolver, _mixIns, rootNames, _configOverrides);
_deserializationConfig = new DeserializationConfig(src._deserializationConfig,
- _subtypeResolver, _mixIns, rootNames, _configOverrides);
+ _subtypeResolver, _mixIns, rootNames, _configOverrides,
+ _coercionConfigs);
_serializerProvider = src._serializerProvider.copy();
_deserializationContext = src._deserializationContext.copy();
@@ -598,12 +614,8 @@ public class ObjectMapper
public ObjectMapper(JsonFactory jf,
DefaultSerializerProvider sp, DefaultDeserializationContext dc)
{
- /* 02-Mar-2009, tatu: Important: we MUST default to using
- * the mapping factory, otherwise tree serialization will
- * have problems with POJONodes.
- * 03-Jan-2010, tatu: and obviously we also must pass 'this',
- * to create actual linking.
- */
+ // 02-Mar-2009, tatu: Important: we MUST default to using the mapping factory,
+ // otherwise tree serialization will have problems with POJONodes.
if (jf == null) {
_jsonFactory = new MappingJsonFactory(this);
} else {
@@ -621,10 +633,12 @@ public class ObjectMapper
_mixIns = mixins;
BaseSettings base = DEFAULT_BASE.withClassIntrospector(defaultClassIntrospector());
_configOverrides = new ConfigOverrides();
+ _coercionConfigs = new CoercionConfigs();
_serializationConfig = new SerializationConfig(base,
_subtypeResolver, mixins, rootNames, _configOverrides);
_deserializationConfig = new DeserializationConfig(base,
- _subtypeResolver, mixins, rootNames, _configOverrides);
+ _subtypeResolver, mixins, rootNames, _configOverrides,
+ _coercionConfigs);
// Some overrides we may need
final boolean needOrder = _jsonFactory.requiresPropertyOrdering();
@@ -736,7 +750,7 @@ public class ObjectMapper
protected ObjectWriter _newWriter(SerializationConfig config, FormatSchema schema) {
return new ObjectWriter(this, config, schema);
}
-
+
/**
* Factory method sub-classes must override, to produce {@link ObjectWriter}
* instances of proper sub-type
@@ -753,7 +767,7 @@ public class ObjectMapper
/* Versioned impl
/**********************************************************
*/
-
+
/**
* Method that will return version information stored in and read from jar
* that contains this class.
@@ -1193,9 +1207,7 @@ public class ObjectMapper
*/
public JsonParser createParser(File src) throws IOException {
_assertNotNull("src", src);
- JsonParser p = _jsonFactory.createParser(src);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(src));
}
/**
@@ -1208,9 +1220,7 @@ public class ObjectMapper
*/
public JsonParser createParser(URL src) throws IOException {
_assertNotNull("src", src);
- JsonParser p = _jsonFactory.createParser(src);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(src));
}
/**
@@ -1223,9 +1233,7 @@ public class ObjectMapper
*/
public JsonParser createParser(InputStream in) throws IOException {
_assertNotNull("in", in);
- JsonParser p = _jsonFactory.createParser(in);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(in));
}
/**
@@ -1238,9 +1246,7 @@ public class ObjectMapper
*/
public JsonParser createParser(Reader r) throws IOException {
_assertNotNull("r", r);
- JsonParser p = _jsonFactory.createParser(r);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(r));
}
/**
@@ -1253,9 +1259,7 @@ public class ObjectMapper
*/
public JsonParser createParser(byte[] content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _jsonFactory.createParser(content);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(content));
}
/**
@@ -1268,9 +1272,7 @@ public class ObjectMapper
*/
public JsonParser createParser(byte[] content, int offset, int len) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _jsonFactory.createParser(content, offset, len);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(content, offset, len));
}
/**
@@ -1283,9 +1285,7 @@ public class ObjectMapper
*/
public JsonParser createParser(String content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _jsonFactory.createParser(content);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(content));
}
/**
@@ -1298,9 +1298,7 @@ public class ObjectMapper
*/
public JsonParser createParser(char[] content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _jsonFactory.createParser(content);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(content));
}
/**
@@ -1313,9 +1311,7 @@ public class ObjectMapper
*/
public JsonParser createParser(char[] content, int offset, int len) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _jsonFactory.createParser(content, offset, len);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(content, offset, len));
}
/**
@@ -1328,9 +1324,7 @@ public class ObjectMapper
*/
public JsonParser createParser(DataInput content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _jsonFactory.createParser(content);
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createParser(content));
}
/**
@@ -1342,9 +1336,7 @@ public class ObjectMapper
* @since 2.11
*/
public JsonParser createNonBlockingByteArrayParser() throws IOException {
- JsonParser p = _jsonFactory.createNonBlockingByteArrayParser();
- _deserializationConfig.initialize(p);
- return p;
+ return _deserializationConfig.initialize(_jsonFactory.createNonBlockingByteArrayParser());
}
/*
@@ -2052,7 +2044,7 @@ public class ObjectMapper
/*
/**********************************************************
- /* Configuration, basic type handling
+ /* Configuration, config, coercion overrides
/**********************************************************
*/
@@ -2079,6 +2071,45 @@ public class ObjectMapper
/*
/**********************************************************
+ /* Configuration, coercion config (2.x only)
+ /**********************************************************
+ */
+
+ /**
+ * Accessor for {@link MutableCoercionConfig} through which
+ * default (fallback) coercion configurations can be changed.
+ * Note that such settings are only applied if more specific
+ * (by logical and physical type) configuration have
+ * not been defined.
+ *
+ * @since 2.12
+ */
+ public MutableCoercionConfig coercionConfigDefaults() {
+ return _coercionConfigs.defaultCoercions();
+ }
+
+ /**
+ * Accessor for {@link MutableCoercionConfig} through which
+ * coercion configuration for specified logical target type can be set.
+ *
+ * @since 2.12
+ */
+ public MutableCoercionConfig coercionConfigFor(LogicalType logicalType) {
+ return _coercionConfigs.findOrCreateCoercion(logicalType);
+ }
+
+ /**
+ * Accessor for {@link MutableCoercionConfig} through which
+ * coercion configuration for specified physical target type can be set.
+ *
+ * @since 2.12
+ */
+ public MutableCoercionConfig coercionConfigFor(Class<?> physicalType) {
+ return _coercionConfigs.findOrCreateCoercion(physicalType);
+ }
+
+ /*
+ /**********************************************************
/* Configuration, basic type handling
/**********************************************************
*/
@@ -2827,7 +2858,7 @@ public class ObjectMapper
_assertNotNull("p", p);
// Must check for EOF here before calling readValue(), since that'll choke on it otherwise
DeserializationConfig cfg = getDeserializationConfig();
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == null) {
t = p.nextToken();
if (t == null) {
@@ -3303,7 +3334,7 @@ public class ObjectMapper
return createDeserializationContext(null,
getDeserializationConfig()).hasValueDeserializerFor(type, cause);
}
-
+
/*
/**********************************************************
/* Extended Public API, deserialization,
@@ -4474,26 +4505,19 @@ public class ObjectMapper
JavaType valueType)
throws IOException
{
- /* First: may need to read the next token, to initialize
- * state (either before first read from parser, or after
- * previous token has been cleared)
- */
- Object result;
+ // First: may need to read the next token, to initialize
+ // state (either before first read from parser, or after
+ // previous token has been cleared)
+ final Object result;
JsonToken t = _initForReading(p, valueType);
- final DeserializationContext ctxt = createDeserializationContext(p, cfg);
+ final DefaultDeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else { // pointing to event other than null
- JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
- // ok, let's get the value
- if (cfg.useRootWrapping()) {
- result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
- } else {
- result = deser.deserialize(p, ctxt);
- }
+ result = ctxt.readRootValue(p, valueType, _findRootDeserializer(ctxt, valueType), null);
}
// Need to consume the token too
p.clearCurrentToken();
@@ -4507,22 +4531,18 @@ public class ObjectMapper
throws IOException
{
try (JsonParser p = p0) {
- Object result;
- JsonToken t = _initForReading(p, valueType);
+ final Object result;
final DeserializationConfig cfg = getDeserializationConfig();
- final DeserializationContext ctxt = createDeserializationContext(p, cfg);
+ final DefaultDeserializationContext ctxt = createDeserializationContext(p, cfg);
+ JsonToken t = _initForReading(p, valueType);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else {
- JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
- if (cfg.useRootWrapping()) {
- result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
- } else {
- result = deser.deserialize(p, ctxt);
- }
+ result = ctxt.readRootValue(p, valueType,
+ _findRootDeserializer(ctxt, valueType), null);
ctxt.checkUnresolvedObjectId();
}
if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
@@ -4548,7 +4568,7 @@ public class ObjectMapper
// special requirements by tree reading (no fail on eof)
cfg.initialize(p); // since 2.5
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == null) {
t = p.nextToken();
if (t == null) {
@@ -4557,72 +4577,25 @@ public class ObjectMapper
return cfg.getNodeFactory().missingNode();
}
}
- final boolean checkTrailing = cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
- DeserializationContext ctxt;
- JsonNode resultNode;
+ final JsonNode resultNode;
+ final DefaultDeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
resultNode = cfg.getNodeFactory().nullNode();
- if (!checkTrailing) {
- return resultNode;
- }
- ctxt = createDeserializationContext(p, cfg);
} else {
- ctxt = createDeserializationContext(p, cfg);
- JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
- if (cfg.useRootWrapping()) {
- resultNode = (JsonNode) _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
- } else {
- resultNode = (JsonNode) deser.deserialize(p, ctxt);
- }
+ resultNode = (JsonNode) ctxt.readRootValue(p, valueType,
+ _findRootDeserializer(ctxt, valueType), null);
+ // No ObjectIds so can ignore
+// ctxt.checkUnresolvedObjectId();
}
- if (checkTrailing) {
+ if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, valueType);
}
- // No ObjectIds so can ignore
-// ctxt.checkUnresolvedObjectId();
return resultNode;
}
}
- protected Object _unwrapAndDeserialize(JsonParser p, DeserializationContext ctxt,
- DeserializationConfig config,
- JavaType rootType, JsonDeserializer<Object> deser)
- throws IOException
- {
- PropertyName expRootName = config.findRootName(rootType);
- // 12-Jun-2015, tatu: Should try to support namespaces etc but...
- String expSimpleName = expRootName.getSimpleName();
- if (p.getCurrentToken() != JsonToken.START_OBJECT) {
- ctxt.reportWrongTokenException(rootType, JsonToken.START_OBJECT,
- "Current token not START_OBJECT (needed to unwrap root name '%s'), but %s",
- expSimpleName, p.getCurrentToken());
- }
- if (p.nextToken() != JsonToken.FIELD_NAME) {
- ctxt.reportWrongTokenException(rootType, JsonToken.FIELD_NAME,
- "Current token not FIELD_NAME (to contain expected root name '%s'), but %s",
- expSimpleName, p.getCurrentToken());
- }
- String actualName = p.getCurrentName();
- if (!expSimpleName.equals(actualName)) {
- ctxt.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();
- Object result = deser.deserialize(p, ctxt);
- // and last, verify that we now get matching END_OBJECT
- if (p.nextToken() != JsonToken.END_OBJECT) {
- ctxt.reportWrongTokenException(rootType, JsonToken.END_OBJECT,
- "Current token not END_OBJECT (to match wrapper object with root name '%s'), but %s",
- expSimpleName, p.getCurrentToken());
- }
- if (config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
- _verifyNoTrailingTokens(p, ctxt, rootType);
- }
- return result;
- }
+
/**
* Internal helper method called to create an instance of {@link DeserializationContext}
@@ -4656,7 +4629,7 @@ public class ObjectMapper
// First: must point to a token; if not pointing to one, advance.
// This occurs before first read from JsonParser, as well as
// after clearing of current token.
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == null) {
// and then we must get something...
t = p.nextToken();
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
index 483e55338..6c817c186 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
@@ -343,16 +343,13 @@ public class ObjectReader
protected JsonToken _initForReading(DeserializationContext ctxt, JsonParser p)
throws IOException
{
- if (_schema != null) {
- p.setSchema(_schema);
- }
- _config.initialize(p); // since 2.5
+ _config.initialize(p, _schema);
/* First: must point to a token; if not pointing to one, advance.
* This occurs before first read from JsonParser, as well as
* after clearing of current token.
*/
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == null) { // and then we must get something...
t = p.nextToken();
if (t == null) {
@@ -376,10 +373,7 @@ public class ObjectReader
protected void _initForMultiRead(DeserializationContext ctxt, JsonParser p)
throws IOException
{
- if (_schema != null) {
- p.setSchema(_schema);
- }
- _config.initialize(p);
+ _config.initialize(p, _schema);
}
/*
@@ -1024,9 +1018,7 @@ public class ObjectReader
*/
public JsonParser createParser(File src) throws IOException {
_assertNotNull("src", src);
- JsonParser p = _parserFactory.createParser(src);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(src), _schema);
}
/**
@@ -1039,9 +1031,7 @@ public class ObjectReader
*/
public JsonParser createParser(URL src) throws IOException {
_assertNotNull("src", src);
- JsonParser p = _parserFactory.createParser(src);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(src), _schema);
}
/**
@@ -1054,9 +1044,7 @@ public class ObjectReader
*/
public JsonParser createParser(InputStream in) throws IOException {
_assertNotNull("in", in);
- JsonParser p = _parserFactory.createParser(in);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(in), _schema);
}
/**
@@ -1069,9 +1057,7 @@ public class ObjectReader
*/
public JsonParser createParser(Reader r) throws IOException {
_assertNotNull("r", r);
- JsonParser p = _parserFactory.createParser(r);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(r), _schema);
}
/**
@@ -1084,9 +1070,7 @@ public class ObjectReader
*/
public JsonParser createParser(byte[] content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _parserFactory.createParser(content);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(content), _schema);
}
/**
@@ -1099,9 +1083,7 @@ public class ObjectReader
*/
public JsonParser createParser(byte[] content, int offset, int len) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _parserFactory.createParser(content, offset, len);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(content, offset, len), _schema);
}
/**
@@ -1114,9 +1096,7 @@ public class ObjectReader
*/
public JsonParser createParser(String content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _parserFactory.createParser(content);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(content), _schema);
}
/**
@@ -1129,9 +1109,7 @@ public class ObjectReader
*/
public JsonParser createParser(char[] content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _parserFactory.createParser(content);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(content), _schema);
}
/**
@@ -1144,9 +1122,7 @@ public class ObjectReader
*/
public JsonParser createParser(char[] content, int offset, int len) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _parserFactory.createParser(content, offset, len);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(content, offset, len), _schema);
}
/**
@@ -1159,9 +1135,7 @@ public class ObjectReader
*/
public JsonParser createParser(DataInput content) throws IOException {
_assertNotNull("content", content);
- JsonParser p = _parserFactory.createParser(content);
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createParser(content), _schema);
}
/**
@@ -1173,9 +1147,7 @@ public class ObjectReader
* @since 2.11
*/
public JsonParser createNonBlockingByteArrayParser() throws IOException {
- JsonParser p = _parserFactory.createNonBlockingByteArrayParser();
- _config.initialize(p);
- return p;
+ return _config.initialize(_parserFactory.createNonBlockingByteArrayParser(), _schema);
}
/*
@@ -2019,11 +1991,10 @@ public class ObjectReader
*/
protected Object _bind(JsonParser p, Object valueToUpdate) throws IOException
{
- /* First: may need to read the next token, to initialize state (either
- * before first read from parser, or after previous token has been cleared)
- */
+ // First: may need to read the next token, to initialize state (either
+ // before first read from parser, or after previous token has been cleared)
Object result;
- final DeserializationContext ctxt = createDeserializationContext(p);
+ final DefaultDeserializationContext ctxt = createDeserializationContext(p);
JsonToken t = _initForReading(ctxt, p);
if (t == JsonToken.VALUE_NULL) {
if (valueToUpdate == null) {
@@ -2034,18 +2005,7 @@ public class ObjectReader
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = valueToUpdate;
} else { // pointing to event other than null
- JsonDeserializer<Object> deser = _findRootDeserializer(ctxt);
- if (_unwrapRoot) {
- result = _unwrapAndDeserialize(p, ctxt, _valueType, deser);
- } else {
- if (valueToUpdate == null) {
- result = deser.deserialize(p, ctxt);
- } else {
- // 20-Mar-2017, tatu: Important! May be different from `valueToUpdate`
- // for immutable Objects like Java arrays; logical result
- result = deser.deserialize(p, ctxt, valueToUpdate);
- }
- }
+ result = ctxt.readRootValue(p, _valueType, _findRootDeserializer(ctxt), _valueToUpdate);
}
// Need to consume the token too
p.clearCurrentToken();
@@ -2060,7 +2020,7 @@ public class ObjectReader
try (JsonParser p = p0) {
Object result;
- DeserializationContext ctxt = createDeserializationContext(p);
+ final DefaultDeserializationContext ctxt = createDeserializationContext(p);
JsonToken t = _initForReading(ctxt, p);
if (t == JsonToken.VALUE_NULL) {
if (_valueToUpdate == null) {
@@ -2071,17 +2031,7 @@ public class ObjectReader
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = _valueToUpdate;
} else {
- JsonDeserializer<Object> deser = _findRootDeserializer(ctxt);
- if (_unwrapRoot) {
- result = _unwrapAndDeserialize(p, ctxt, _valueType, deser);
- } else {
- if (_valueToUpdate == null) {
- result = deser.deserialize(p, ctxt);
- } else {
- deser.deserialize(p, ctxt, _valueToUpdate);
- result = _valueToUpdate;
- }
- }
+ result = ctxt.readRootValue(p, _valueType, _findRootDeserializer(ctxt), _valueToUpdate);
}
if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, _valueType);
@@ -2104,31 +2054,21 @@ public class ObjectReader
p.setSchema(_schema);
}
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == null) {
t = p.nextToken();
if (t == null) {
return _config.getNodeFactory().missingNode();
}
}
- final DeserializationContext ctxt;
+ final DefaultDeserializationContext ctxt = createDeserializationContext(p);
final JsonNode resultNode;
- final boolean checkTrailing = _config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
if (t == JsonToken.VALUE_NULL) {
resultNode = _config.getNodeFactory().nullNode();
- if (!checkTrailing) {
- return resultNode;
- }
- ctxt = createDeserializationContext(p);
} else {
- ctxt = createDeserializationContext(p);
- final JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
- if (_unwrapRoot) {
- resultNode = (JsonNode) _unwrapAndDeserialize(p, ctxt, _jsonNodeType(), deser);
- } else {
- resultNode = (JsonNode) deser.deserialize(p, ctxt);
- }
+ // Will not be called for merge (need not pass _valueToUpdate)
+ resultNode = (JsonNode) ctxt.readRootValue(p, _jsonNodeType(), _findTreeDeserializer(ctxt), null);
}
if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, _jsonNodeType());
@@ -2146,37 +2086,27 @@ public class ObjectReader
if (_schema != null) {
p.setSchema(_schema);
}
- JsonToken t = p.getCurrentToken();
+ JsonToken t = p.currentToken();
if (t == null) {
t = p.nextToken();
if (t == null) {
return null;
}
}
- final DeserializationContext ctxt;
+ final DefaultDeserializationContext ctxt = createDeserializationContext(p);
final JsonNode resultNode;
- final boolean checkTrailing = _config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
if (t == JsonToken.VALUE_NULL) {
resultNode = _config.getNodeFactory().nullNode();
- if (!checkTrailing) {
- return resultNode;
- }
- ctxt = createDeserializationContext(p);
} else {
- ctxt = createDeserializationContext(p);
- final JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
- if (_unwrapRoot) {
- resultNode = (JsonNode) _unwrapAndDeserialize(p, ctxt, _jsonNodeType(), deser);
- } else {
- resultNode = (JsonNode) deser.deserialize(p, ctxt);
- }
+ // Will not be called for merge (need not pass _valueToUpdate)
+ resultNode = (JsonNode) ctxt.readRootValue(p, _jsonNodeType(), _findTreeDeserializer(ctxt), null);
}
- if (checkTrailing) {
+ if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, _jsonNodeType());
}
return resultNode;
}
-
+
/**
* @since 2.1
*/
@@ -2188,50 +2118,6 @@ public class ObjectReader
return _newIterator(p, ctxt, _findRootDeserializer(ctxt), true);
}
- protected Object _unwrapAndDeserialize(JsonParser p, DeserializationContext ctxt,
- JavaType rootType, JsonDeserializer<Object> deser) throws IOException
- {
- PropertyName expRootName = _config.findRootName(rootType);
- // 12-Jun-2015, tatu: Should try to support namespaces etc but...
- String expSimpleName = expRootName.getSimpleName();
-
- if (p.getCurrentToken() != JsonToken.START_OBJECT) {
- ctxt.reportWrongTokenException(rootType, JsonToken.START_OBJECT,
- "Current token not START_OBJECT (needed to unwrap root name '%s'), but %s",
- expSimpleName, p.getCurrentToken());
- }
- if (p.nextToken() != JsonToken.FIELD_NAME) {
- ctxt.reportWrongTokenException(rootType, JsonToken.FIELD_NAME,
- "Current token not FIELD_NAME (to contain expected root name '%s'), but %s",
- expSimpleName, p.getCurrentToken());
- }
- String actualName = p.getCurrentName();
- if (!expSimpleName.equals(actualName)) {
- ctxt.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();
- Object result;
- if (_valueToUpdate == null) {
- result = deser.deserialize(p, ctxt);
- } else {
- deser.deserialize(p, ctxt, _valueToUpdate);
- result = _valueToUpdate;
- }
- // and last, verify that we now get matching END_OBJECT
- if (p.nextToken() != JsonToken.END_OBJECT) {
- ctxt.reportWrongTokenException(rootType, JsonToken.END_OBJECT,
- "Current token not END_OBJECT (to match wrapper object with root name '%s'), but %s",
- expSimpleName, p.getCurrentToken());
- }
- if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
- _verifyNoTrailingTokens(p, ctxt, _valueType);
- }
- return result;
- }
-
/**
* Consider filter when creating JsonParser.
*/
@@ -2364,6 +2250,11 @@ public class ObjectReader
return _context.createInstance(_config, p, _injectableValues);
}
+ // @since 2.12 -- needed for Deserializer pre-fetch
+ protected DefaultDeserializationContext createDummyDeserializationContext() {
+ return _context.createDummyInstance(_config);
+ }
+
protected InputStream _inputStream(URL src) throws IOException {
return src.openStream();
}
@@ -2450,7 +2341,7 @@ public class ObjectReader
if (deser == null) {
try {
// If not, need to resolve; for which we need a temporary context as well:
- DeserializationContext ctxt = createDeserializationContext(null);
+ DeserializationContext ctxt = createDummyDeserializationContext();
deser = ctxt.findRootValueDeserializer(valueType);
if (deser != null) {
_rootDeserializers.put(valueType, deser);
diff --git a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java
index 0b5563be1..f0bc87e59 100644
--- a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java
+++ b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java
@@ -299,7 +299,7 @@ public class PropertyNamingStrategy // NOTE: was abstract until 2.7
* <li>&quot;user__name&quot; is translated to &quot;user__name&quot;
* (unchanged, with two underscores)</li></ul>
*
- * @since 2.7 (was previously called }
+ * @since 2.7 (was previously called {@link LowerCaseWithUnderscoresStrategy})
*/
public static class SnakeCaseStrategy extends PropertyNamingStrategyBase
{
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
index a1b934083..f0a25ea78 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
@@ -15,13 +15,13 @@ import com.fasterxml.jackson.databind.util.StdDateFormat;
/**
* Immutable container class used to store simple configuration
- * settings. Since instances are fully immutable, instances can
+ * settings for both serialization and deserialization.
+ * Since instances are fully immutable, instances can
* be freely shared and used without synchronization.
*/
public final class BaseSettings
implements java.io.Serializable
{
- // for 2.6
private static final long serialVersionUID = 1L;
/**
@@ -91,7 +91,7 @@ public final class BaseSettings
*/
/**
- * Custom date format to use for de-serialization. If specified, will be
+ * Custom date format to use for deserialization. If specified, will be
* used instead of {@link com.fasterxml.jackson.databind.util.StdDateFormat}.
*<p>
* Note that the configured format object will be cloned once per
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionAction.java b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionAction.java
new file mode 100644
index 000000000..c535ed256
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionAction.java
@@ -0,0 +1,42 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import com.fasterxml.jackson.databind.type.LogicalType;
+
+/**
+ * Set of possible actions for requested coercion from an
+ * input shape {@link CoercionInputShape}
+ * that does not directly or naturally match target type
+ * ({@link LogicalType}).
+ * This action is suggestion for deserializers to use in cases
+ * where alternate actions could be appropriate: it is up to deserializer
+ * to check configured action and take it into consideration.
+ *
+ * @since 2.12
+ */
+public enum CoercionAction
+{
+ /**
+ * Action to fail coercion attempt with exceptipn
+ */
+ Fail,
+
+ /**
+ * Action to attempt coercion (which may lead to failure)
+ */
+ TryConvert,
+
+ /**
+ * Action to convert to {@code null} value
+ */
+ AsNull,
+
+ /**
+ * Action to convert to "empty" value for type, whatever that is: for
+ * primitive types and their wrappers this is "default" value (for example,
+ * for {@code int} that would be {@code 0}); for {@link java.util.Collection}s
+ * empty collection; for POJOs instance configured with default constructor
+ * and so on.
+ */
+ AsEmpty;
+ ;
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfig.java
new file mode 100644
index 000000000..3bfa17b79
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfig.java
@@ -0,0 +1,41 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import java.util.Arrays;
+
+/**
+ * @since 2.12
+ */
+public class CoercionConfig
+ implements java.io.Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ private final static int INPUT_SHAPE_COUNT = CoercionInputShape.values().length;
+
+ protected Boolean _acceptBlankAsEmpty;
+
+ /**
+ * Mapping from {@link CoercionInputShape} into corresponding
+ * {@link CoercionAction}.
+ */
+ protected final CoercionAction[] _coercionsByShape;
+
+ public CoercionConfig() {
+ _coercionsByShape = new CoercionAction[INPUT_SHAPE_COUNT];
+ _acceptBlankAsEmpty = false;
+ }
+
+ protected CoercionConfig(CoercionConfig src) {
+ _acceptBlankAsEmpty = src._acceptBlankAsEmpty;
+ _coercionsByShape = Arrays.copyOf(src._coercionsByShape,
+ src._coercionsByShape.length);
+ }
+
+ public CoercionAction findAction(CoercionInputShape shape) {
+ return _coercionsByShape[shape.ordinal()];
+ }
+
+ public Boolean getAcceptBlankAsEmpty() {
+ return _acceptBlankAsEmpty;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfigs.java b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfigs.java
new file mode 100644
index 000000000..50fae96dc
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfigs.java
@@ -0,0 +1,307 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.type.LogicalType;
+
+/**
+ * @since 2.12
+ */
+public class CoercionConfigs
+ implements java.io.Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ private final static int TARGET_TYPE_COUNT = LogicalType.values().length;
+
+ /**
+ * Global default for cases not explicitly covered
+ */
+ protected CoercionAction _defaultAction;
+
+ /**
+ * Default coercion definitions used if no overrides found
+ * by logical or physical type.
+ */
+ protected final MutableCoercionConfig _defaultCoercions;
+
+ /**
+ * Coercion definitions by logical type ({@link LogicalType})
+ */
+ protected MutableCoercionConfig[] _perTypeCoercions;
+
+ /**
+ * Coercion definitions by physical type (Class).
+ */
+ protected Map<Class<?>, MutableCoercionConfig> _perClassCoercions;
+
+ /*
+ /**********************************************************************
+ /* Life cycle
+ /**********************************************************************
+ */
+
+ public CoercionConfigs() {
+ this(CoercionAction.TryConvert, new MutableCoercionConfig(),
+ null, null);
+ }
+
+ protected CoercionConfigs(CoercionAction defaultAction,
+ MutableCoercionConfig defaultCoercions,
+ MutableCoercionConfig[] perTypeCoercions,
+ Map<Class<?>, MutableCoercionConfig> perClassCoercions)
+ {
+ _defaultCoercions = defaultCoercions;
+ _defaultAction = defaultAction;
+ _perTypeCoercions = perTypeCoercions;
+ _perClassCoercions = perClassCoercions;
+ }
+
+ /**
+ * Method called to create a non-shared copy of configuration settings,
+ * to be used by another {@link com.fasterxml.jackson.databind.ObjectMapper}
+ * instance.
+ *
+ * @return A non-shared copy of configuration settings
+ */
+ public CoercionConfigs copy()
+ {
+ MutableCoercionConfig[] newPerType;
+ if (_perTypeCoercions == null) {
+ newPerType = null;
+ } else {
+ final int size = _perTypeCoercions.length;
+ newPerType = new MutableCoercionConfig[size];
+ for (int i = 0; i < size; ++i) {
+ newPerType[i] = _copy(_perTypeCoercions[i]);
+ }
+ }
+ Map<Class<?>, MutableCoercionConfig> newPerClass;
+ if (_perClassCoercions == null) {
+ newPerClass = null;
+ } else {
+ newPerClass = new HashMap<>();
+ for (Map.Entry<Class<?>, MutableCoercionConfig> entry : _perClassCoercions.entrySet()) {
+ newPerClass.put(entry.getKey(), entry.getValue().copy());
+ }
+ }
+ return new CoercionConfigs(_defaultAction, _defaultCoercions.copy(),
+ newPerType, newPerClass);
+ }
+
+ private static MutableCoercionConfig _copy(MutableCoercionConfig src) {
+ if (src == null) {
+ return null;
+ }
+ return src.copy();
+ }
+
+ /*
+ /**********************************************************************
+ /* Mutators: global defaults
+ /**********************************************************************
+ */
+
+ public MutableCoercionConfig defaultCoercions() {
+ return _defaultCoercions;
+ }
+
+ /*
+ /**********************************************************************
+ /* Mutators: per type
+ /**********************************************************************
+ */
+
+ public MutableCoercionConfig findOrCreateCoercion(LogicalType type) {
+ if (_perTypeCoercions == null) {
+ _perTypeCoercions = new MutableCoercionConfig[TARGET_TYPE_COUNT];
+ }
+ MutableCoercionConfig config = _perTypeCoercions[type.ordinal()];
+ if (config == null) {
+ _perTypeCoercions[type.ordinal()] = config = new MutableCoercionConfig();
+ }
+ return config;
+ }
+
+ public MutableCoercionConfig findOrCreateCoercion(Class<?> type) {
+ if (_perClassCoercions == null) {
+ _perClassCoercions = new HashMap<>();
+ }
+ MutableCoercionConfig config = _perClassCoercions.get(type);
+ if (config == null) {
+ config = new MutableCoercionConfig();
+ _perClassCoercions.put(type, config);
+ }
+ return config;
+ }
+
+ /*
+ /**********************************************************************
+ /* Access
+ /**********************************************************************
+ */
+
+ /**
+ * General-purpose accessor for finding what to do when specified coercion
+ * from shape that is now always allowed to be coerced from is requested.
+ *
+ * @param config Currently active deserialization configuration
+ * @param targetType Logical target type of coercion
+ * @param targetClass Physical target type of coercion
+ * @param inputShape Input shape to coerce from
+ *
+ * @return CoercionAction configured for specified coercion
+ *
+ * @since 2.12
+ */
+ public CoercionAction findCoercion(DeserializationConfig config,
+ LogicalType targetType,
+ Class<?> targetClass, CoercionInputShape inputShape)
+ {
+ // First, see if there is exact match for physical type
+ if ((_perClassCoercions != null) && (targetClass != null)) {
+ MutableCoercionConfig cc = _perClassCoercions.get(targetClass);
+ if (cc != null) {
+ CoercionAction act = cc.findAction(inputShape);
+ if (act != null) {
+ return act;
+ }
+ }
+ }
+
+ // If not, maybe by logical type
+ if ((_perTypeCoercions != null) && (targetType != null)) {
+ MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()];
+ if (cc != null) {
+ CoercionAction act = cc.findAction(inputShape);
+ if (act != null) {
+ return act;
+ }
+ }
+ }
+
+ // Barring that, default coercion for input shape?
+ CoercionAction act = _defaultCoercions.findAction(inputShape);
+ if (act != null) {
+ return act;
+ }
+
+ // Otherwise there are some legacy features that can provide answer
+ if (inputShape == CoercionInputShape.EmptyArray) {
+ // Default for setting is false
+ return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT) ?
+ CoercionAction.AsNull : CoercionAction.Fail;
+ }
+ if ((inputShape == CoercionInputShape.Float)
+ && (targetType == LogicalType.Integer)) {
+ // Default for setting in 2.x is true
+ return config.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT) ?
+ CoercionAction.TryConvert : CoercionAction.Fail;
+ }
+
+ // classic scalars are numbers, booleans; but date/time also considered
+ // scalar for this particular purpose
+ final boolean baseScalar = (targetType == LogicalType.Float)
+ || (targetType == LogicalType.Integer)
+ || (targetType == LogicalType.Boolean)
+ || (targetType == LogicalType.DateTime);
+
+ if (baseScalar) {
+ // Default for setting in 2.x is true
+ if (!config.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
+ return CoercionAction.Fail;
+ }
+ }
+
+ if (inputShape == CoercionInputShape.EmptyString) {
+ // Since coercion of scalar must be enabled (see check above), allow empty-string
+ // coercions by default even without this setting
+ if (baseScalar
+ // Default for setting is false
+ || config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
+ return CoercionAction.AsNull;
+ }
+ // 09-Jun-2020, tatu: Seems necessary to support backwards-compatibility with
+ // 2.11, wrt "FromStringDeserializer" supported types
+ if (targetType == LogicalType.OtherScalar) {
+ return CoercionAction.TryConvert;
+ }
+ // But block from allowing structured types like POJOs, Maps etc
+ return CoercionAction.Fail;
+ }
+
+ // and all else failing, return default
+ return _defaultAction;
+ }
+
+ /**
+ * More specialized accessor called in case of input being a blank
+ * String (one consisting of only white space characters with length of at least one).
+ * Will basically first determine if "blank as empty" is allowed: if not,
+ * returns {@code actionIfBlankNotAllowed}, otherwise returns action for
+ * {@link CoercionInputShape#EmptyString}.
+ *
+ * @param config Currently active deserialization configuration
+ * @param targetType Logical target type of coercion
+ * @param targetClass Physical target type of coercion
+ * @param actionIfBlankNotAllowed Return value to use in case "blanks as empty"
+ * is not allowed
+ *
+ * @return CoercionAction configured for specified coercion from blank string
+ */
+ public CoercionAction findCoercionFromBlankString(DeserializationConfig config,
+ LogicalType targetType,
+ Class<?> targetClass,
+ CoercionAction actionIfBlankNotAllowed)
+ {
+ Boolean acceptBlankAsEmpty = null;
+ CoercionAction action = null;
+
+ // First, see if there is exact match for physical type
+ if ((_perClassCoercions != null) && (targetClass != null)) {
+ MutableCoercionConfig cc = _perClassCoercions.get(targetClass);
+ if (cc != null) {
+ acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty();
+ action = cc.findAction(CoercionInputShape.EmptyString);
+ }
+ }
+
+ // If not, maybe by logical type
+ if ((_perTypeCoercions != null) && (targetType != null)) {
+ MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()];
+ if (cc != null) {
+ if (acceptBlankAsEmpty == null) {
+ acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty();
+ }
+ if (action == null) {
+ action = cc.findAction(CoercionInputShape.EmptyString);
+ }
+ }
+ }
+
+ // Barring that, default coercion for input shape?
+ if (acceptBlankAsEmpty == null) {
+ acceptBlankAsEmpty = _defaultCoercions.getAcceptBlankAsEmpty();
+ }
+ if (action == null) {
+ action = _defaultCoercions.findAction(CoercionInputShape.EmptyString);
+ }
+
+ // First: if using blank as empty is no-go, return what caller specified
+ if (!Boolean.TRUE.equals(acceptBlankAsEmpty)) {
+ return actionIfBlankNotAllowed;
+ }
+
+ // Otherwise, if action found, return that
+ if (action != null) {
+ return action;
+ }
+ // If not, one specific legacy setting to consider...
+ return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) ?
+ CoercionAction.AsNull : CoercionAction.Fail;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionInputShape.java b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionInputShape.java
new file mode 100644
index 000000000..1bd2c2588
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/CoercionInputShape.java
@@ -0,0 +1,96 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import com.fasterxml.jackson.databind.type.LogicalType;
+
+/**
+ * Set of input types (which mostly match one of
+ * {@link com.fasterxml.jackson.core.JsonToken} types) used for
+ * configuring {@link CoercionAction}s to take when reading
+ * input into target types (specific type or {@link LogicalType}).
+ * Contains both physical input shapes (which match one of
+ * {@link com.fasterxml.jackson.core.JsonToken} types) and a few
+ * logical input shapes ("empty" variants).
+ *<p>
+ * Note that {@code null} input shape is explicitly not included as
+ * its configuration is distinct from other types.
+ *
+ * @since 2.12
+ */
+public enum CoercionInputShape
+{
+ // Physical types
+
+ /**
+ * Shape of Array values from input (token sequence from
+ * {@link com.fasterxml.jackson.core.JsonToken#START_ARRAY} to
+ * {@link com.fasterxml.jackson.core.JsonToken#END_ARRAY})
+ */
+ Array,
+
+ /**
+ * Shape of Object values from input (token sequence from
+ * {@link com.fasterxml.jackson.core.JsonToken#START_OBJECT} to
+ * {@link com.fasterxml.jackson.core.JsonToken#END_OBJECT})
+ */
+ Object,
+
+ /**
+ * Shape of integral (non-floating point) numeric values from input (token
+ * {@link com.fasterxml.jackson.core.JsonToken#VALUE_NUMBER_INT})
+ */
+ Integer,
+
+ /**
+ * Shape of floating point (non-integral) numeric values from input (token
+ * {@link com.fasterxml.jackson.core.JsonToken#VALUE_NUMBER_FLOAT})
+ */
+ Float,
+
+ /**
+ * Shape of boolean values from input (tokens
+ * {@link com.fasterxml.jackson.core.JsonToken#VALUE_TRUE} and
+ * {@link com.fasterxml.jackson.core.JsonToken#VALUE_FALSE})
+ */
+ Boolean,
+
+ /**
+ * Shape of string values from input (tokens
+ * {@link com.fasterxml.jackson.core.JsonToken#VALUE_STRING})
+ */
+ String,
+
+ /**
+ * Shape of binary data values from input, if expressed natively
+ * by underlying format (many
+ * textual formats, including JSON, do not have such shape); if so
+ * generally seen as {@link com.fasterxml.jackson.core.JsonToken#VALUE_EMBEDDED_OBJECT}.
+ */
+ Binary,
+
+ // Logical types
+
+ /**
+ * Special case of Array values with no actual content (sequence of 2 tokens:
+ * {@link com.fasterxml.jackson.core.JsonToken#START_ARRAY},
+ * {@link com.fasterxml.jackson.core.JsonToken#END_ARRAY}):
+ * usually used to allow special coercion into "empty" or {@code null} target type.
+ */
+ EmptyArray,
+
+ /**
+ * Special case of Object values with no actual content (sequence of 2 tokens:
+ * {@link com.fasterxml.jackson.core.JsonToken#START_OBJECT},
+ * {@link com.fasterxml.jackson.core.JsonToken#END_OBJECT}):
+ * usually used to allow special coercion into "empty" or {@code null} target type.
+ */
+ EmptyObject,
+
+ /**
+ * Special case for String values with no content (or, if allowed by format or specific
+ * configuration, also "blank" String, that is, all-whitespace content).
+ * usually used to allow special coercion into "empty" or {@code null} target type.
+ */
+ EmptyString
+
+ ;
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
index dfc87e80e..124047470 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
@@ -236,7 +236,7 @@ public class ConfigOverrides
/* Helper methods
/**********************************************************************
*/
-
+
protected Map<Class<?>, MutableConfigOverride> _newMap() {
return new HashMap<Class<?>, MutableConfigOverride>();
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MutableCoercionConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MutableCoercionConfig.java
new file mode 100644
index 000000000..479ff770c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MutableCoercionConfig.java
@@ -0,0 +1,36 @@
+package com.fasterxml.jackson.databind.cfg;
+
+/**
+ * Mutable version of {@link CoercionConfig} (or rather, extended API)
+ * exposed during configuration phase of {@link com.fasterxml.jackson.databind.ObjectMapper}
+ * construction (via Builder).
+ *
+ * @since 2.12
+ */
+public class MutableCoercionConfig
+ extends CoercionConfig
+ implements java.io.Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ public MutableCoercionConfig() { }
+
+ protected MutableCoercionConfig(MutableCoercionConfig src) {
+ super(src);
+ }
+
+ public MutableCoercionConfig copy() {
+ return new MutableCoercionConfig(this);
+ }
+
+ public MutableCoercionConfig setCoercion(CoercionInputShape shape,
+ CoercionAction action) {
+ _coercionsByShape[shape.ordinal()] = action;
+ return this;
+ }
+
+ public MutableCoercionConfig setAcceptBlankAsEmpty(Boolean state) {
+ _acceptBlankAsEmpty = state;
+ return this;
+ }
+}
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);
+ }
+ }
+
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java
index f6d6a997d..77d61e5e6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java
@@ -189,6 +189,14 @@ public final class AnnotatedMethod
@Override
public String getFullName() {
+ final String methodName = super.getFullName();
+ switch (getParameterCount()) {
+ case 0:
+ return methodName+"()";
+ case 1:
+ return methodName+"("+getRawParameterType(0).getName()+")";
+ default:
+ }
return String.format("%s(%d params)", super.getFullName(), getParameterCount());
}
@@ -215,10 +223,14 @@ public final class AnnotatedMethod
* false, otherwise true
*
* @since 2.4
+ *
+ * @deprecated Since 2.12 (related to [databind#2675]), needs to be configurable
*/
+ @Deprecated
public boolean hasReturnType() {
Class<?> rt = getRawReturnType();
- return (rt != Void.TYPE && rt != Void.class);
+ // also, as per [databind#2675], only consider `void` to be real "No return type"
+ return (rt != Void.TYPE);
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java
index 747aeccb5..731102df8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java
@@ -592,6 +592,10 @@ public class JacksonAnnotationIntrospector
ArrayList<NamedType> result = new ArrayList<NamedType>(types.length);
for (JsonSubTypes.Type type : types) {
result.add(new NamedType(type.value(), type.name()));
+ // [databind#2761]: alternative set of names to use
+ for (String name : type.names()) {
+ result.add(new NamedType(type.value(), name));
+ }
}
return result;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java
index cf70f0c3f..2326cefde 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java
@@ -5,7 +5,6 @@ import java.util.*;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.*;
@@ -580,10 +579,15 @@ public class POJOPropertiesCollector
AnnotatedMethod m, AnnotationIntrospector ai)
{
// Very first thing: skip if not returning any value
- if (!m.hasReturnType()) {
- return;
+ // 06-May-2020, tatu: [databind#2675] changes handling slightly...
+ {
+ final Class<?> rt = m.getRawReturnType();
+ if ((rt == Void.TYPE) ||
+ ((rt == Void.class) && !_config.isEnabled(MapperFeature.ALLOW_VOID_VALUED_PROPERTIES))) {
+ return;
+ }
}
-
+
// any getter?
// @JsonAnyGetter?
if (Boolean.TRUE.equals(ai.hasAnyGetter(m))) {
@@ -788,10 +792,9 @@ public class POJOPropertiesCollector
while (it.hasNext()) {
POJOPropertyBuilder prop = it.next();
// 26-Jan-2017, tatu: [databind#935]: need to denote removal of
- JsonProperty.Access acc = prop.removeNonVisible(inferMutators);
- if (acc == JsonProperty.Access.READ_ONLY) {
- _collectIgnorals(prop.getName());
- }
+ // 16-May-2020, tatu: [databind#2719]: need to pass `this` to allow
+ // addition of ignorals wrt explicit name
+ prop.removeNonVisible(inferMutators, _forSerialization ? null : this);
}
}
@@ -800,9 +803,9 @@ public class POJOPropertiesCollector
* of known ignored properties; this helps in proper reporting of
* errors.
*/
- private void _collectIgnorals(String name)
+ protected void _collectIgnorals(String name)
{
- if (!_forSerialization) {
+ if (!_forSerialization && (name != null)) {
if (_ignoredPropertyNames == null) {
_ignoredPropertyNames = new HashSet<String>();
}
@@ -868,12 +871,20 @@ public class POJOPropertiesCollector
old.addAll(prop);
}
// replace the creatorProperty too, if there is one
- _updateCreatorProperty(prop, _creatorProperties);
- // [databind#2001]: New name of property was ignored previously? Remove from ignored
- // 01-May-2018, tatu: I have a feeling this will need to be revisited at some point,
- // to avoid removing some types of removals, possibly. But will do for now.
- if (_ignoredPropertyNames != null) {
- _ignoredPropertyNames.remove(name);
+ if (_updateCreatorProperty(prop, _creatorProperties)) {
+ // [databind#2001]: New name of property was ignored previously? Remove from ignored
+ // 01-May-2018, tatu: I have a feeling this will need to be revisited at some point,
+ // to avoid removing some types of removals, possibly. But will do for now.
+
+ // 16-May-2020, tatu: ... and so the day came, [databind#2118] failed
+ // when explicit rename added to ignorals (for READ_ONLY) was suddenly
+ // removed from ignoral list. So, added a guard statement above so that
+ // ignoral is ONLY removed if there was matching creator property.
+ //
+ // Chances are this is not the last tweak we need but... that bridge then etc
+ if (_ignoredPropertyNames != null) {
+ _ignoredPropertyNames.remove(name);
+ }
}
}
}
@@ -1170,16 +1181,17 @@ public class POJOPropertiesCollector
_config.canOverrideAccessModifiers());
}
- protected void _updateCreatorProperty(POJOPropertyBuilder prop, List<POJOPropertyBuilder> creatorProperties) {
+ protected boolean _updateCreatorProperty(POJOPropertyBuilder prop, List<POJOPropertyBuilder> creatorProperties) {
if (creatorProperties != null) {
final String intName = prop.getInternalName();
for (int i = 0, len = creatorProperties.size(); i < len; ++i) {
if (creatorProperties.get(i).getInternalName().equals(intName)) {
creatorProperties.set(i, prop);
- break;
+ return true;
}
}
}
+ return false;
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java
index a171f157d..bdc49ae0f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java
@@ -216,20 +216,30 @@ public class POJOPropertyBuilder
*/
@Override
- public PropertyMetadata getMetadata() {
+ public PropertyMetadata getMetadata()
+ {
if (_metadata == null) {
- final Boolean b = _findRequired();
- final String desc = _findDescription();
- final Integer idx = _findIndex();
- final String def = _findDefaultValue();
- if (b == null && idx == null && def == null) {
- _metadata = (desc == null) ? PropertyMetadata.STD_REQUIRED_OR_OPTIONAL
- : PropertyMetadata.STD_REQUIRED_OR_OPTIONAL.withDescription(desc);
+ // 20-Jun-2020, tatu: Unfortunately strict checks lead to [databind#2757]
+ // so we will need to try to avoid them at this point
+ final AnnotatedMember prim = getPrimaryMemberUnchecked();
+
+ if (prim == null) {
+ _metadata = PropertyMetadata.STD_REQUIRED_OR_OPTIONAL;
} else {
- _metadata = PropertyMetadata.construct(b, desc, idx, def);
- }
- if (!_forSerialization) {
- _metadata = _getSetterInfo(_metadata);
+ final Boolean b = _annotationIntrospector.hasRequiredMarker(prim);
+ final String desc = _annotationIntrospector.findPropertyDescription(prim);
+ final Integer idx = _annotationIntrospector.findPropertyIndex(prim);
+ final String def = _annotationIntrospector.findPropertyDefaultValue(prim);
+
+ if (b == null && idx == null && def == null) {
+ _metadata = (desc == null) ? PropertyMetadata.STD_REQUIRED_OR_OPTIONAL
+ : PropertyMetadata.STD_REQUIRED_OR_OPTIONAL.withDescription(desc);
+ } else {
+ _metadata = PropertyMetadata.construct(b, desc, idx, def);
+ }
+ if (!_forSerialization) {
+ _metadata = _getSetterInfo(_metadata, prim);
+ }
}
}
return _metadata;
@@ -241,7 +251,8 @@ public class POJOPropertyBuilder
* of property value, and handling of incoming nulls.
* Only called for deserialization purposes.
*/
- protected PropertyMetadata _getSetterInfo(PropertyMetadata metadata)
+ protected PropertyMetadata _getSetterInfo(PropertyMetadata metadata,
+ AnnotatedMember primary)
{
boolean needMerge = true;
Nulls valueNulls = null;
@@ -250,16 +261,13 @@ public class POJOPropertyBuilder
// Slightly confusing: first, annotations should be accessed via primary member
// (mutator); but accessor is needed for actual merge operation. So
- // 20-Jun-2020, tatu: Unfortunately strict checks lead to [databind#2757]
- // so we will need to try to avoid them at this point
- AnnotatedMember prim = getPrimaryMemberUnchecked();
AnnotatedMember acc = getAccessor();
- if (prim != null) {
+ if (primary != null) {
// Ok, first: does property itself have something to say?
if (_annotationIntrospector != null) {
if (acc != null) {
- Boolean b = _annotationIntrospector.findMergeInfo(prim);
+ Boolean b = _annotationIntrospector.findMergeInfo(primary);
if (b != null) {
needMerge = false;
if (b.booleanValue()) {
@@ -267,7 +275,7 @@ public class POJOPropertyBuilder
}
}
}
- JsonSetter.Value setterInfo = _annotationIntrospector.findSetterInfo(prim);
+ JsonSetter.Value setterInfo = _annotationIntrospector.findSetterInfo(primary);
if (setterInfo != null) {
valueNulls = setterInfo.nonDefaultValueNulls();
contentNulls = setterInfo.nonDefaultContentNulls();
@@ -279,7 +287,7 @@ public class POJOPropertyBuilder
// 20-Jun-2020, tatu: Related to [databind#2757], need to find type
// but keeping mind that type for setters is trickier; and that
// generic typing gets tricky as well.
- Class<?> rawType = _rawTypeOf(prim);
+ Class<?> rawType = _rawTypeOf(primary);
ConfigOverride co = _config.getConfigOverride(rawType);
JsonSetter.Value setterInfo = co.getSetterInfo();
if (setterInfo != null) {
@@ -694,42 +702,6 @@ public class POJOPropertyBuilder
return (b != null) && b.booleanValue();
}
- protected Boolean _findRequired() {
- return fromMemberAnnotations(new WithMember<Boolean>() {
- @Override
- public Boolean withMember(AnnotatedMember member) {
- return _annotationIntrospector.hasRequiredMarker(member);
- }
- });
- }
-
- protected String _findDescription() {
- return fromMemberAnnotations(new WithMember<String>() {
- @Override
- public String withMember(AnnotatedMember member) {
- return _annotationIntrospector.findPropertyDescription(member);
- }
- });
- }
-
- protected Integer _findIndex() {
- return fromMemberAnnotations(new WithMember<Integer>() {
- @Override
- public Integer withMember(AnnotatedMember member) {
- return _annotationIntrospector.findPropertyIndex(member);
- }
- });
- }
-
- protected String _findDefaultValue() {
- return fromMemberAnnotations(new WithMember<String>() {
- @Override
- public String withMember(AnnotatedMember member) {
- return _annotationIntrospector.findPropertyDefaultValue(member);
- }
- });
- }
-
@Override
public ObjectIdInfo findObjectIdInfo() {
return fromMemberAnnotations(new WithMember<ObjectIdInfo>() {
@@ -828,11 +800,19 @@ public class POJOPropertyBuilder
_ctorParameters = _removeIgnored(_ctorParameters);
}
+ @Deprecated // since 2.12
+ public JsonProperty.Access removeNonVisible(boolean inferMutators) {
+ return removeNonVisible(inferMutators, null);
+ }
+
/**
* @param inferMutators Whether mutators can be "pulled in" by visible
* accessors or not.
+ *
+ * @since 2.12 (earlier had different signature)
*/
- public JsonProperty.Access removeNonVisible(boolean inferMutators)
+ public JsonProperty.Access removeNonVisible(boolean inferMutators,
+ POJOPropertiesCollector parent)
{
/* 07-Jun-2015, tatu: With 2.6, we will allow optional definition
* of explicit access type for property; if not "AUTO", it will
@@ -844,6 +824,15 @@ public class POJOPropertyBuilder
}
switch (acc) {
case READ_ONLY:
+ // [databind#2719]: Need to add ignorals, first, keeping in mind
+ // we have not yet resolved explicit names, so include implicit
+ // and possible explicit names
+ if (parent != null) {
+ parent._collectIgnorals(getName());
+ for (PropertyName pn : findExplicitNames()) {
+ parent._collectIgnorals(pn.getSimpleName());
+ }
+ }
// Remove setters, creators for sure, but fields too if deserializing
_setters = null;
_ctorParameters = null;
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java
index ea70d4bb1..c8efa858a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java
@@ -22,9 +22,9 @@ public abstract class TypeDeserializerBase
implements java.io.Serializable
{
private static final long serialVersionUID = 1;
-
+
protected final TypeIdResolver _idResolver;
-
+
protected final JavaType _baseType;
/**
@@ -47,9 +47,9 @@ public abstract class TypeDeserializerBase
* in cases where type id is to be exposed as part of JSON.
*/
protected final String _typePropertyName;
-
+
protected final boolean _typeIdVisible;
-
+
/**
* For efficient operation we will lazily build mappings from type ids
* to actual deserializers, once needed.
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java
index 601afb5a2..da4541a5e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java
@@ -164,7 +164,15 @@ public class TypeNameIdResolver extends TypeIdResolverBase
@Override
public String getDescForKnownTypeIds() {
- return new TreeSet<String>(_idToType.keySet()).toString();
+ // 05-May-2020, tatu: As per [databind#1919], only include ids for
+ // non-abstract types
+ final TreeSet<String> ids = new TreeSet<>();
+ for (Map.Entry<String, JavaType> entry : _idToType.entrySet()) {
+ if (entry.getValue().isConcrete()) {
+ ids.add(entry.getKey());
+ }
+ }
+ return ids.toString();
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
index 5de340e5c..9b98f44e6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
+++ b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
@@ -23,7 +23,7 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
*<p>
* NOTE: although it is not expected that sub-types should need to
* override {@link #setupModule(SetupContext)} method, if they choose
- * to do so they MUST call <code>super.setupModule(context);</code>
+ * to do so they MUST call {@code super.setupModule(context);}
* to ensure that registration works as expected.
*<p>
* WARNING: when registering {@link JsonSerializer}s and {@link JsonDeserializer}s,
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/TreeTraversingParser.java b/src/main/java/com/fasterxml/jackson/databind/node/TreeTraversingParser.java
index 904cb8bed..63200214a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/TreeTraversingParser.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/TreeTraversingParser.java
@@ -7,6 +7,7 @@ import java.math.BigInteger;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.JsonNode;
/**
@@ -72,7 +73,13 @@ public class TreeTraversingParser extends ParserMinimalBase
public Version version() {
return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
}
-
+
+ @Override
+ public JacksonFeatureSet<StreamReadCapability> getReadCapabilities() {
+ // Defaults are fine
+ return DEFAULT_READ_CAPABILITIES;
+ }
+
/*
/**********************************************************
/* Closeable implementation
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
index 4e7c398d2..889a72470 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
@@ -462,15 +462,13 @@ public abstract class BasicSerializerFactory
if (Number.class.isAssignableFrom(raw)) {
// 21-May-2014, tatu: Couple of alternatives actually
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
- if (format != null) {
- switch (format.getShape()) {
- case STRING:
- return ToStringSerializer.instance;
- case OBJECT: // need to bail out to let it be serialized as POJO
- case ARRAY: // or, I guess ARRAY; otherwise no point in speculating
- return null;
- default:
- }
+ switch (format.getShape()) {
+ case STRING:
+ return ToStringSerializer.instance;
+ case OBJECT: // need to bail out to let it be serialized as POJO
+ case ARRAY: // or, I guess ARRAY; otherwise no point in speculating
+ return null;
+ default:
}
return NumberSerializer.instance;
}
@@ -706,7 +704,7 @@ public abstract class BasicSerializerFactory
// We may also want to use serialize Collections "as beans", if (and only if)
// this is specified with `@JsonFormat(shape=Object)`
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
- if ((format != null) && format.getShape() == JsonFormat.Shape.OBJECT) {
+ if (format.getShape() == JsonFormat.Shape.OBJECT) {
return null;
}
Class<?> raw = type.getRawClass();
@@ -796,7 +794,7 @@ public abstract class BasicSerializerFactory
// [databind#467]: This is where we could allow serialization "as POJO": But! It's
// nasty to undo, and does not apply on per-property basis. So, hardly optimal
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
- if ((format != null) && format.getShape() == JsonFormat.Shape.OBJECT) {
+ if (format.getShape() == JsonFormat.Shape.OBJECT) {
return null;
}
@@ -1195,7 +1193,7 @@ public abstract class BasicSerializerFactory
* otherwise pass it to EnumSerializer.
*/
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
- if (format != null && format.getShape() == JsonFormat.Shape.OBJECT) {
+ if (format.getShape() == JsonFormat.Shape.OBJECT) {
// one special case: suppress serialization of "getDeclaringClass()"...
((BasicBeanDescription) beanDesc).removeProperty("declaringClass");
// returning null will mean that eventually BeanSerializer gets constructed
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
index 79c9a96f0..07a969208 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
@@ -16,9 +16,11 @@ import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.impl.FilteredBeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
import com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator;
+import com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer;
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
import com.fasterxml.jackson.databind.type.ReferenceType;
+import com.fasterxml.jackson.databind.util.BeanUtil;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
@@ -376,6 +378,12 @@ public class BeanSerializerFactory
return prov.getUnknownTypeSerializer(Object.class);
// throw new IllegalArgumentException("Cannot create bean serializer for Object.class");
}
+
+ JsonSerializer<?> ser = _findUnsupportedTypeSerializer(prov, type, beanDesc);
+ if (ser != null) {
+ return (JsonSerializer<Object>) ser;
+ }
+
final SerializationConfig config = prov.getConfig();
BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
builder.setConfig(config);
@@ -447,9 +455,8 @@ public class BeanSerializerFactory
}
}
- JsonSerializer<Object> ser = null;
try {
- ser = (JsonSerializer<Object>) builder.build();
+ ser = builder.build();
} catch (RuntimeException e) {
return prov.reportBadTypeDefinition(beanDesc, "Failed to construct BeanSerializer for %s: (%s) %s",
beanDesc.getType(), e.getClass().getName(), e.getMessage());
@@ -467,7 +474,7 @@ public class BeanSerializerFactory
}
}
}
- return ser;
+ return (JsonSerializer<Object>) ser;
}
protected ObjectIdWriter constructObjectIdHandler(SerializerProvider prov,
@@ -528,7 +535,7 @@ public class BeanSerializerFactory
{
return FilteredBeanPropertyWriter.constructViewBased(writer, inViews);
}
-
+
protected PropertyBuilder constructPropertyBuilder(SerializationConfig config,
BeanDescription beanDesc)
{
@@ -538,7 +545,7 @@ public class BeanSerializerFactory
protected BeanSerializerBuilder constructBeanSerializerBuilder(BeanDescription beanDesc) {
return new BeanSerializerBuilder(beanDesc);
}
-
+
/*
/**********************************************************
/* Overridable non-public introspection methods
@@ -814,4 +821,15 @@ public class BeanSerializerFactory
return pb.buildWriter(prov, propDef, type, annotatedSerializer,
typeSer, contentTypeSer, accessor, staticTyping);
}
+
+ protected JsonSerializer<?> _findUnsupportedTypeSerializer(SerializerProvider 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'?
+ final String errorMsg = BeanUtil.checkUnsupportedType(type);
+ return (errorMsg == null) ? null
+ : new UnsupportedTypeSerializer(type, errorMsg);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/FailingSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/FailingSerializer.java
index 6021fa89e..e047c6010 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/FailingSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/FailingSerializer.java
@@ -1,14 +1,11 @@
package com.fasterxml.jackson.databind.ser.impl;
import java.io.IOException;
-import java.lang.reflect.Type;
import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.JavaType;
+
import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
/**
@@ -30,19 +27,8 @@ public class FailingSerializer
}
@Override
- public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException
- {
- provider.reportMappingProblem(_msg);
- }
-
- @Override
- public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
- return null;
- }
-
- @Override
- public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
+ public void serialize(Object value, JsonGenerator g, SerializerProvider ctxt) throws IOException
{
- ;
+ ctxt.reportMappingProblem(_msg);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/PropertySerializerMap.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/PropertySerializerMap.java
index e49bf5883..435701880 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/PropertySerializerMap.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/PropertySerializerMap.java
@@ -165,14 +165,6 @@ public abstract class PropertySerializerMap
public abstract PropertySerializerMap newWith(Class<?> type, JsonSerializer<Object> serializer);
/**
- * @deprecated Since 2.5 Use {@link #emptyForProperties} instead
- */
- @Deprecated
- public static PropertySerializerMap emptyMap() {
- return emptyForProperties();
- }
-
- /**
* @since 2.5
*/
public static PropertySerializerMap emptyForProperties() {
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnsupportedTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnsupportedTypeSerializer.java
new file mode 100644
index 000000000..b849ee3d2
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnsupportedTypeSerializer.java
@@ -0,0 +1,37 @@
+package com.fasterxml.jackson.databind.ser.impl;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+/**
+ * Special bogus "serializer" that will throw
+ * {@link com.fasterxml.jackson.databind.exc.InvalidDefinitionException} if its {@link #serialize}
+ * gets invoked. Most commonly registered as handler for unknown types,
+ * as well as for catching unintended usage (like trying to use null
+ * as Map/Object key).
+ */
+public class UnsupportedTypeSerializer
+ extends StdSerializer<Object>
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final JavaType _type;
+
+ protected final String _message;
+
+ public UnsupportedTypeSerializer(JavaType t, String msg) {
+ super(Object.class);
+ _type = t;
+ _message = msg;
+ }
+
+ @Override
+ public void serialize(Object value, JsonGenerator g, SerializerProvider ctxt) throws IOException {
+ ctxt.reportBadDefinition(_type, _message);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java
index 4632e5142..95621c9a5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java
@@ -128,8 +128,8 @@ public abstract class BeanSerializerBase
_anyGetterWriter = builder.getAnyGetter();
_propertyFilterId = builder.getFilterId();
_objectIdWriter = builder.getObjectIdWriter();
- JsonFormat.Value format = builder.getBeanDescription().findExpectedFormat(null);
- _serializationShape = (format == null) ? null : format.getShape();
+ final JsonFormat.Value format = builder.getBeanDescription().findExpectedFormat(null);
+ _serializationShape = format.getShape();
}
}
@@ -265,14 +265,8 @@ public abstract class BeanSerializerBase
*
* @since 2.11.1
*/
- protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties,
- BeanPropertyWriter[] filteredProperties) {
- return this;
- }
-
- // Should be this, will be with 2.12:
-// protected abstract BeanSerializerBase withProperties(BeanPropertyWriter[] properties,
-// BeanPropertyWriter[] filteredProperties);
+ protected abstract BeanSerializerBase withProperties(BeanPropertyWriter[] properties,
+ BeanPropertyWriter[] filteredProperties);
/**
* Copy-constructor that is useful for sub-classes that just want to
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdScalarSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdScalarSerializer.java
index 916a9c980..683a2f40f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdScalarSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdScalarSerializer.java
@@ -28,7 +28,18 @@ public abstract class StdScalarSerializer<T>
protected StdScalarSerializer(Class<?> t, boolean dummy) {
super((Class<T>) t);
}
-
+
+ /**
+ * Basic copy-constructor
+ *
+ * @param src Original instance to copy settings from
+ *
+ * @since 2.12
+ */
+ protected StdScalarSerializer(StdScalarSerializer<?> src) {
+ super(src);
+ }
+
/**
* Default implementation will write type prefix, call regular serialization
* method (since assumption is that value itself does not need JSON
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/ArrayType.java b/src/main/java/com/fasterxml/jackson/databind/type/ArrayType.java
index ef3e6aafb..901b342fe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/ArrayType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/ArrayType.java
@@ -197,7 +197,20 @@ public final class ArrayType
sb.append('[');
return _componentType.getErasedSignature(sb);
}
-
+
+ /*
+ /**********************************************************
+ /* Extended API
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.12
+ */
+ public Object[] getEmptyArray() {
+ return (Object[]) _emptyArray;
+ }
+
/*
/**********************************************************
/* Standard methods
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/LogicalType.java b/src/main/java/com/fasterxml/jackson/databind/type/LogicalType.java
new file mode 100644
index 000000000..a04d21d6d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/type/LogicalType.java
@@ -0,0 +1,136 @@
+package com.fasterxml.jackson.databind.type;
+
+/**
+ * Set of logical types (or type categories, classes of classes), used
+ * for defining applicability of configuration like coercion configuration.
+ * Used instead to allow easier targeting of types than having to enumerate
+ * physical types ({@link java.lang.Class} or {@link com.fasterxml.jackson.databind.JavaType}).
+ *
+ * @since 2.12
+ */
+public enum LogicalType
+{
+ // // // General container types
+
+ /**
+ * Array types of other values.
+ *<p>
+ * Note: excludes binary type {@code byte[]}.
+ */
+ Array,
+
+ /**
+ * {@link java.util.Collection} values (and "Collection-like" for JVM
+ * languages and datatype libraries with semantically similar types)
+ */
+ Collection,
+
+ /**
+ * {@link java.util.Map} values (and "Map-like" for JVM
+ * languages and datatype libraries with semantically similar types)
+ */
+ Map,
+
+ // // // Other structured java types
+
+ /**
+ * Types that are handled by default "set of key/value pairs" serialization,
+ * also known as "Beans".
+ *<p>
+ * In addition to user-defined types, also includes JDK types like:
+ *<ul>
+ * <li>{@link java.lang.Throwable}
+ * </li>
+ * </ul>
+ */
+ POJO,
+
+ /**
+ * "Non-type", Type used to contained untyped, free-form content: maybe
+ * a "Tree" (sometimes called "AST"), or buffer of some kind,
+ * or even just nominal type of {@link java.lang.Object}
+ */
+ Untyped,
+
+ // // // Basic scalar types
+
+ /**
+ * Basic integral numbers types like {@code short}, {@code int}, {@code long}
+ * and matching wrapper types, {@link java.math.BigInteger}.
+ */
+ Integer,
+
+ /**
+ * Basic floating-point numbers types like {@code short}, {@code int}, {@code long}
+ * and matching wrapper types, {@link java.math.BigInteger}.
+ */
+ Float,
+
+ /**
+ * {@link java.lang.Boolean}, {@code boolean}, {@link java.util.concurrent.atomic.AtomicBoolean}.
+ */
+ Boolean,
+
+ /**
+ * Various {@link java.lang.Enum} types.
+ */
+ Enum,
+
+ /**
+ * Purely textual types, {@link java.lang.String} and similar (but not types that
+ * are generally expressed as Strings in input).
+ */
+ Textual,
+
+ /**
+ * Binary data such as {@code byte[]} and {@link java.nio.ByteBuffer}.
+ */
+ Binary,
+
+ /**
+ * Date/time datatypes such as {@link java.util.Date}, {@link java.util.Calendar}.
+ */
+ DateTime,
+
+ /**
+ * Scalar types other than ones listed above: includes types like {@link java.net.URL}
+ * and {@link java.util.UUID}.
+ */
+ OtherScalar
+ ;
+
+ /**
+ * Helper method to use for figuring out logical type from physical type,
+ * in cases where caller wants a guess. Note that introspection is
+ * not exhaustive and mostly covers basic {@link java.util.Collection},
+ * {@link java.util.Map} and {@link java.lang.Enum} cases; but not
+ * more specific types (for example datatype-provided extension types).
+ *
+ * @param raw Type-erased class to classify
+ * @param defaultIfNotRecognized if no type recognized, value to return
+ * (for example, {@code null})
+ */
+ public static LogicalType fromClass(Class<?> raw,
+ LogicalType defaultIfNotRecognized)
+ {
+ if (raw.isEnum()) {
+ return Enum;
+ }
+ if (raw.isArray()) {
+ if (raw == byte[].class) {
+ return Binary;
+ }
+ return Array;
+ }
+ if (java.util.Collection.class.isAssignableFrom(raw)) {
+ return Collection;
+ }
+ if (java.util.Map.class.isAssignableFrom(raw)) {
+ return Map;
+ }
+ if (raw == String.class) {
+ return Textual;
+ }
+ return defaultIfNotRecognized;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
index 147900057..e424690d3 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
@@ -698,6 +698,14 @@ public class TypeFactory // note: was final in 2.9, removed from 2.10
return _fromAny(null, type, EMPTY_BINDINGS);
}
+ /**
+ * Method that you very likely should NOT be using -- you need to know a lot
+ * about internal details of {@link TypeBindings} and even then it will probably
+ * not do what you want.
+ * Usually you would instead want to call one of {@code constructXxxType()}
+ * methods (where {@code Xxx} would be "Array", "Collection[Like]", "Map[Like]"
+ * or "Parametric").
+ */
public JavaType constructType(Type type, TypeBindings bindings) {
// 15-Jun-2020, tatu: To resolve (parts of) [databind#2796], need to
// call _fromClass() directly if we get `Class` argument
@@ -966,7 +974,8 @@ public class TypeFactory // note: was final in 2.9, removed from 2.10
*/
public JavaType constructReferenceType(Class<?> rawType, JavaType referredType)
{
- return ReferenceType.construct(rawType, null, // no bindings
+ return ReferenceType.construct(rawType,
+ TypeBindings.create(rawType, referredType), // [databind#2091]
null, null, // or super-class, interfaces?
referredType);
}
@@ -1052,9 +1061,32 @@ public class TypeFactory // note: was final in 2.9, removed from 2.10
*/
public JavaType constructParametricType(Class<?> rawType, JavaType... parameterTypes)
{
+ return constructParametricType(rawType, TypeBindings.create(rawType, parameterTypes));
+ }
+
+ /**
+ * Factory method for constructing {@link JavaType} that
+ * represents a parameterized type. The type's parameters are
+ * specified as an instance of {@link TypeBindings}. This
+ * is useful if you already have the type's parameters such
+ * as those found on {@link JavaType}. For example, you could
+ * call
+ * <pre>
+ * return TypeFactory.constructParametricType(ArrayList.class, javaType.getBindings());
+ * </pre>
+ * This effectively applies the parameterized types from one
+ * {@link JavaType} to another class.
+ *
+ * @param rawType Actual type-erased type
+ * @param parameterTypes Type bindings for the raw type
+ *
+ * @since 2.12
+ */
+ public JavaType constructParametricType(Class<?> rawType, TypeBindings parameterTypes)
+ {
// 16-Jul-2020, tatu: Since we do not call `_fromAny()`, need to make
// sure `TypeModifier`s are applied:
- JavaType resultType = _fromClass(null, rawType, TypeBindings.create(rawType, parameterTypes));
+ JavaType resultType = _fromClass(null, rawType, parameterTypes);
return _applyModifiers(rawType, resultType);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java b/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
index d381cdbf0..c58930bf6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
@@ -289,4 +289,50 @@ public class BeanUtil
sb.append(basename, offset+1, end);
return sb.toString();
}
+
+ /*
+ /**********************************************************
+ /* Package-specific type detection for error handling
+ /**********************************************************
+ */
+
+ /**
+ * Helper method called by {@link com.fasterxml.jackson.databind.deser.BeanDeserializerFactory}
+ * and {@link com.fasterxml.jackson.databind.ser.BeanSerializerFactory} to check
+ * if given unrecognized type (to be (de)serialized as general POJO) is one of
+ * "well-known" types for which there would be a datatype module; and if so,
+ * return appropriate failure message to give to caller.
+ *
+ * @since 2.12
+ */
+ public static String checkUnsupportedType(JavaType type) {
+ final Class<?> rawType = type.getRawClass();
+ String typeName, moduleName;
+
+ if (isJava8TimeClass(rawType)) {
+ typeName = "Java 8 date/time";
+ moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310";
+ } else if (isJodaTimeClass(rawType)) {
+ typeName = "Joda date/time";
+ moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-joda";
+ } else {
+ return null;
+ }
+ return String.format("%s type %s not supported by default: add Module \"%s\" to enable handling",
+ typeName, ClassUtil.getTypeDescription(type), moduleName);
+ }
+
+ /**
+ * @since 2.12
+ */
+ public static boolean isJava8TimeClass(Class<?> rawType) {
+ return rawType.getName().startsWith("java.time.");
+ }
+
+ /**
+ * @since 2.12
+ */
+ public static boolean isJodaTimeClass(Class<?> rawType) {
+ return rawType.getName().startsWith("org.joda.time.");
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java
index 84a7feee3..3f6577ebe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java
@@ -306,9 +306,8 @@ public class StdDateFormat
@Override
public void setTimeZone(TimeZone tz)
{
- /* DateFormats are timezone-specific (via Calendar contained),
- * so need to reset instances if timezone changes:
- */
+ // DateFormats are timezone-specific (via Calendar contained),
+ // so need to reset instances if timezone changes:
if (!tz.equals(_timezone)) {
_clearFormats();
_timezone = tz;
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java
index e37835f64..8fa86e7df 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
import com.fasterxml.jackson.core.json.JsonWriteContext;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.*;
/**
@@ -639,7 +640,16 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(",");
public boolean canWriteBinaryNatively() {
return true;
}
-
+
+ // 20-May-2020, tatu: This may or may not be enough -- ideally access is
+ // via `DeserializationContext`, not parser, but if latter is needed
+ // then we'll need to pass this from parser contents if which were
+ // buffered.
+ @Override
+ public JacksonFeatureSet<StreamWriteCapability> getWriteCapabilities() {
+ return DEFAULT_WRITE_CAPABILITIES;
+ }
+
/*
/**********************************************************
/* JsonGenerator implementation: low-level output handling
@@ -671,19 +681,11 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(",");
_writeContext = _writeContext.createChildArrayContext();
}
- @Override // since 2.10 (method added in 2.4)
- public final void writeStartArray(int size) throws IOException
- {
- _writeContext.writeValue();
- _appendStartMarker(JsonToken.START_ARRAY);
- _writeContext = _writeContext.createChildArrayContext();
- }
-
@Override // since 2.10.1
public void writeStartArray(Object forValue) throws IOException {
_writeContext.writeValue();
_appendStartMarker(JsonToken.START_ARRAY);
- _writeContext = _writeContext.createChildArrayContext();
+ _writeContext = _writeContext.createChildArrayContext(forValue);
}
@Override // since 2.10.1
@@ -1226,16 +1228,10 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(",");
if (_forceBigDecimal) {
writeNumber(p.getDecimalValue());
} else {
- switch (p.getNumberType()) {
- case BIG_DECIMAL:
- writeNumber(p.getDecimalValue());
- break;
- case FLOAT:
- writeNumber(p.getFloatValue());
- break;
- default:
- writeNumber(p.getDoubleValue());
- }
+ // 09-Jul-2020, tatu: Used to just copy using most optimal method, but
+ // issues like [databind#2644] force to use exact, not optimal type
+ final Number n = p.getNumberValueExact();
+ _appendValue(JsonToken.VALUE_NUMBER_FLOAT, n);
}
break;
case VALUE_TRUE:
@@ -1505,18 +1501,33 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(",");
public void setLocation(JsonLocation l) {
_location = l;
}
-
+
@Override
public ObjectCodec getCodec() { return _codec; }
@Override
public void setCodec(ObjectCodec c) { _codec = c; }
+ /*
+ /**********************************************************
+ /* Public API, config access, capability introspection
+ /**********************************************************
+ */
+
@Override
public Version version() {
return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
}
+ // 20-May-2020, tatu: This may or may not be enough -- ideally access is
+ // via `DeserializationContext`, not parser, but if latter is needed
+ // then we'll need to pass this from parser contents if which were
+ // buffered.
+ @Override
+ public JacksonFeatureSet<StreamReadCapability> getReadCapabilities() {
+ return DEFAULT_READ_CAPABILITIES;
+ }
+
/*
/**********************************************************
/* Extended API beyond JsonParser
diff --git a/src/test-jdk14/java/com/fasterxml/jackson/databind/RecordTest.java b/src/test-jdk14/java/com/fasterxml/jackson/databind/RecordTest.java
new file mode 100644
index 000000000..7e80ddb5f
--- /dev/null
+++ b/src/test-jdk14/java/com/fasterxml/jackson/databind/RecordTest.java
@@ -0,0 +1,105 @@
+package com.fasterxml.jackson.databind;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+
+import java.io.IOException;
+
+public class RecordTest extends BaseMapTest {
+
+ private JsonMapper jsonMapper;
+
+ public void setUp() {
+ jsonMapper = new JsonMapper();
+ }
+
+ record SimpleRecord(int id, String name) {
+ }
+
+ public void testSerializeSimpleRecord() throws JsonProcessingException {
+ SimpleRecord record = new SimpleRecord(123, "Bob");
+
+ String json = jsonMapper.writeValueAsString(record);
+
+ assertEquals("{\"id\":123,\"name\":\"Bob\"}", json);
+ }
+
+ public void testDeserializeSimpleRecord() throws IOException {
+ SimpleRecord value = jsonMapper.readValue("{\"id\":123,\"name\":\"Bob\"}", SimpleRecord.class);
+
+ assertEquals(new SimpleRecord(123, "Bob"), value);
+ }
+
+ public void testSerializeSimpleRecord_DisableAnnotationIntrospector() throws JsonProcessingException {
+ SimpleRecord record = new SimpleRecord(123, "Bob");
+
+ JsonMapper mapper = JsonMapper.builder()
+ .configure(MapperFeature.USE_ANNOTATIONS, false)
+ .build();
+ String json = mapper.writeValueAsString(record);
+
+ assertEquals("{\"id\":123,\"name\":\"Bob\"}", json);
+ }
+
+ public void testDeserializeSimpleRecord_DisableAnnotationIntrospector() throws IOException {
+ JsonMapper mapper = JsonMapper.builder()
+ .configure(MapperFeature.USE_ANNOTATIONS, false)
+ .build();
+ SimpleRecord value = mapper.readValue("{\"id\":123,\"name\":\"Bob\"}", SimpleRecord.class);
+
+ assertEquals(new SimpleRecord(123, "Bob"), value);
+ }
+
+ record RecordOfRecord(SimpleRecord record) {
+ }
+
+ public void testSerializeRecordOfRecord() throws JsonProcessingException {
+ RecordOfRecord record = new RecordOfRecord(new SimpleRecord(123, "Bob"));
+
+ String json = jsonMapper.writeValueAsString(record);
+
+ assertEquals("{\"record\":{\"id\":123,\"name\":\"Bob\"}}", json);
+ }
+
+ record JsonIgnoreRecord(int id, @JsonIgnore String name) {
+ }
+
+ public void testSerializeJsonIgnoreRecord() throws JsonProcessingException {
+ JsonIgnoreRecord record = new JsonIgnoreRecord(123, "Bob");
+
+ String json = jsonMapper.writeValueAsString(record);
+
+ assertEquals("{\"id\":123}", json);
+ }
+
+ record RecordWithConstructor(int id, String name) {
+ public RecordWithConstructor(int id) {
+ this(id, "name");
+ }
+ }
+
+ public void testDeserializeRecordWithConstructor() throws IOException {
+ RecordWithConstructor value = jsonMapper.readValue("{\"id\":123,\"name\":\"Bob\"}", RecordWithConstructor.class);
+
+ assertEquals(new RecordWithConstructor(123, "Bob"), value);
+ }
+
+ record JsonPropertyRenameRecord(int id, @JsonProperty("rename")String name) {
+ }
+
+ public void testSerializeJsonRenameRecord() throws JsonProcessingException {
+ JsonPropertyRenameRecord record = new JsonPropertyRenameRecord(123, "Bob");
+
+ String json = jsonMapper.writeValueAsString(record);
+
+ assertEquals("{\"id\":123,\"rename\":\"Bob\"}", json);
+ }
+
+ public void testDeserializeJsonRenameRecord() throws IOException {
+ JsonPropertyRenameRecord value = jsonMapper.readValue("{\"id\":123,\"rename\":\"Bob\"}", JsonPropertyRenameRecord.class);
+
+ assertEquals(new JsonPropertyRenameRecord(123, "Bob"), value);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java
index 549e96028..13aca9c95 100644
--- a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java
@@ -359,6 +359,10 @@ public abstract class BaseMapTest
return json.replace("'", "\"");
}
+ protected static String a2q(String json) {
+ return json.replace("'", "\"");
+ }
+
protected static String quotesToApos(String json) {
return json.replace("\"", "'");
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java
index 7e39256c5..9d84df29a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java
@@ -272,7 +272,6 @@ public class ObjectMapperTest extends BaseMapTest
assertTrue(dc.shouldSortPropertiesAlphabetically());
}
-
public void testJsonFactoryLinkage()
{
// first, implicit factory, giving implicit linkage
@@ -363,6 +362,13 @@ public class ObjectMapperTest extends BaseMapTest
.canSerialize(EmptyBean.class));
}
+ // for [databind#2749]: just to check there's no NPE; method really not useful
+ public void testCanDeserialize()
+ {
+ assertTrue(MAPPER.canDeserialize(MAPPER.constructType(EmptyBean.class)));
+ assertTrue(MAPPER.canDeserialize(MAPPER.constructType(Object.class)));
+ }
+
// for [databind#898]
public void testSerializerProviderAccess() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceContainersTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceContainersTest.java
new file mode 100644
index 000000000..163588155
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceContainersTest.java
@@ -0,0 +1,175 @@
+package com.fasterxml.jackson.databind.convert;
+
+import java.util.*;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
+
+public class CoerceContainersTest extends BaseMapTest
+{
+ private final String JSON_EMPTY = quote("");
+
+ private final ObjectMapper VANILLA_MAPPER = sharedMapper();
+
+ private final ObjectMapper COERCING_MAPPER = newJsonMapper();
+ {
+ COERCING_MAPPER.coercionConfigDefaults().setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
+ }
+
+ /*
+ /********************************************************
+ /* Tests for collections
+ /********************************************************
+ */
+
+ public void testScalarCollections() throws Exception
+ {
+ final JavaType listType = VANILLA_MAPPER.getTypeFactory()
+ .constructType(new TypeReference<List<Double>>() { });
+ _verifyNoCoercion(listType);
+ List<Double> result = _readWithCoercion(listType);
+ assertNotNull(result);
+ assertEquals(0, result.size());
+ }
+
+ public void testStringCollections() throws Exception
+ {
+ final JavaType listType = VANILLA_MAPPER.getTypeFactory()
+ .constructType(new TypeReference<List<String>>() { });
+ _verifyNoCoercion(listType);
+ List<String> result = _readWithCoercion(listType);
+ assertNotNull(result);
+ assertEquals(0, result.size());
+ }
+
+ /*
+ /********************************************************
+ /* Tests for Maps
+ /********************************************************
+ */
+
+ public void testScalarMap() throws Exception
+ {
+ final JavaType mapType = VANILLA_MAPPER.getTypeFactory()
+ .constructType(new TypeReference<Map<Long, Boolean>>() { });
+ _verifyNoCoercion(mapType);
+ Map<?,?> result = _readWithCoercion(mapType);
+ assertNotNull(result);
+ assertEquals(0, result.size());
+ }
+
+ public void testEnumMap() throws Exception
+ {
+ final JavaType mapType = VANILLA_MAPPER.getTypeFactory()
+ .constructType(new TypeReference<EnumMap<ABC, Boolean>>() { });
+ _verifyNoCoercion(mapType);
+ Map<?,?> result = _readWithCoercion(mapType);
+ assertNotNull(result);
+ assertEquals(0, result.size());
+ }
+
+ /*
+ /********************************************************
+ /* Tests for arrays
+ /********************************************************
+ */
+
+ public void testObjectArray() throws Exception
+ {
+ final JavaType arrayType = VANILLA_MAPPER.getTypeFactory()
+ .constructType(new TypeReference<Object[]>() { });
+ _verifyNoCoercion(arrayType);
+ Object[] result = _readWithCoercion(arrayType);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ public void testStringArray() throws Exception
+ {
+ final JavaType arrayType = VANILLA_MAPPER.getTypeFactory()
+ .constructType(new TypeReference<String[]>() { });
+ _verifyNoCoercion(arrayType);
+ String[] result = _readWithCoercion(arrayType);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ public void testBooleanArray() throws Exception
+ {
+ _verifyNoCoercion(boolean[].class);
+ boolean[] result = _readWithCoercion(boolean[].class);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ public void testIntArray() throws Exception
+ {
+ _verifyNoCoercion(int[].class);
+ int[] result = _readWithCoercion(int[].class);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ public void testLongArray() throws Exception
+ {
+ _verifyNoCoercion(long[].class);
+ long[] result = _readWithCoercion(long[].class);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ public void testFloatArray() throws Exception
+ {
+ _verifyNoCoercion(float[].class);
+ float[] result = _readWithCoercion(float[].class);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ public void testDoubleArray() throws Exception
+ {
+ _verifyNoCoercion(double[].class);
+ double[] result = _readWithCoercion(double[].class);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ public void testPOJOArray() throws Exception
+ {
+ _verifyNoCoercion(StringWrapper[].class);
+ StringWrapper[] result = _readWithCoercion(StringWrapper[].class);
+ assertNotNull(result);
+ assertEquals(0, result.length);
+ }
+
+ /*
+ /********************************************************
+ /* Helper methods
+ /********************************************************
+ */
+
+ private void _verifyNoCoercion(Class<?> targetType) throws Exception {
+ _verifyNoCoercion(VANILLA_MAPPER.constructType(targetType));
+ }
+
+ private void _verifyNoCoercion(JavaType targetType) throws Exception {
+ try {
+ VANILLA_MAPPER.readerFor(targetType).readValue(JSON_EMPTY);
+ fail("Should not pass");
+ } catch (Exception e) {
+ verifyException(e, "Cannot deserialize value of type");
+ verifyException(e, "from empty String");
+ }
+ }
+
+ private <T> T _readWithCoercion(Class<?> targetType) throws Exception {
+ return COERCING_MAPPER.readerFor(targetType).readValue(JSON_EMPTY);
+ }
+
+ private <T> T _readWithCoercion(JavaType targetType) throws Exception {
+ return COERCING_MAPPER.readerFor(targetType).readValue(JSON_EMPTY);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceEmptyArrayTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceEmptyArrayTest.java
new file mode 100644
index 000000000..ff429b8ae
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceEmptyArrayTest.java
@@ -0,0 +1,255 @@
+package com.fasterxml.jackson.databind.convert;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URL;
+import java.util.*;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+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.MismatchedInputException;
+import com.fasterxml.jackson.databind.type.LogicalType;
+
+/**
+ * Tests to verify implementation of [databind#540]; also for
+ * follow up work of:
+ *
+ * - [databind#994]
+ */
+public class CoerceEmptyArrayTest extends BaseMapTest
+{
+ private final ObjectMapper DEFAULT_MAPPER = sharedMapper();
+ private final ObjectReader DEFAULT_READER = DEFAULT_MAPPER.reader();
+ private final ObjectReader READER_WITH_ARRAYS = DEFAULT_READER
+ .with(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
+
+ private final ObjectMapper MAPPER_TO_EMPTY;
+ {
+ MAPPER_TO_EMPTY = newJsonMapper();
+ MAPPER_TO_EMPTY.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.AsEmpty);
+ }
+
+ private final ObjectMapper MAPPER_TRY_CONVERT;
+ {
+ MAPPER_TRY_CONVERT = newJsonMapper();
+ MAPPER_TRY_CONVERT.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.TryConvert);
+ }
+
+ private final ObjectMapper MAPPER_TO_NULL;
+ {
+ MAPPER_TO_NULL = newJsonMapper();
+ MAPPER_TO_NULL.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.AsNull);
+ }
+
+ private final ObjectMapper MAPPER_TO_FAIL;
+ {
+ MAPPER_TO_FAIL = newJsonMapper();
+ MAPPER_TO_FAIL.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.Fail);
+ }
+
+ static class Bean {
+ public String a = "foo";
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof Bean)
+ && a.equals(((Bean) o).a);
+ }
+ }
+
+ final static String EMPTY_ARRAY = " [\n]";
+
+ /*
+ /**********************************************************
+ /* Test methods, settings
+ /**********************************************************
+ */
+
+ public void testSettings() {
+ assertFalse(DEFAULT_MAPPER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
+ assertFalse(DEFAULT_READER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
+ assertTrue(READER_WITH_ARRAYS.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, POJOs
+ /**********************************************************
+ */
+
+ // [databind#540]
+ public void testPOJOFromEmptyArray() throws Exception
+ {
+ final Class<?> targetType = Bean.class;
+
+ _verifyFailForEmptyArray(DEFAULT_READER, targetType);
+ _verifyFailForEmptyArray(MAPPER_TO_FAIL, targetType);
+
+ // Nulls for explicit, "TryConvert"
+ _verifyToNullCoercion(MAPPER_TO_NULL, targetType);
+ _verifyToNullCoercion(MAPPER_TRY_CONVERT, targetType);
+
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, targetType, new Bean());
+
+ // But let's also check precedence: legacy setting allow, but mask for type
+ ObjectMapper mapper = jsonMapperBuilder()
+ .enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)
+ .build();
+ mapper.coercionConfigFor(targetType)
+ .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.Fail);
+ _verifyFailForEmptyArray(mapper, targetType);
+
+ // and conversely
+ mapper = jsonMapperBuilder()
+ .disable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)
+ .build();
+ mapper.coercionConfigFor(LogicalType.POJO)
+ .setCoercion(CoercionInputShape.EmptyArray, CoercionAction.AsEmpty);
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, targetType, new Bean());
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, Maps
+ /**********************************************************
+ */
+
+ public void testMapFromEmptyArray() throws Exception
+ {
+ final Class<?> targetType = Map.class;
+
+ _verifyFailForEmptyArray(DEFAULT_READER, targetType);
+ _verifyFailForEmptyArray(MAPPER_TO_FAIL, targetType);
+
+ // Nulls for explicit, "TryConvert"
+ _verifyToNullCoercion(MAPPER_TO_NULL, targetType);
+ _verifyToNullCoercion(MAPPER_TRY_CONVERT, targetType);
+
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, targetType, new LinkedHashMap<>());
+
+ // assume overrides work ok since POJOs test it
+ }
+
+ public void testEnumMapFromEmptyArray() throws Exception
+ {
+ final JavaType targetType = DEFAULT_READER.getTypeFactory()
+ .constructType(new TypeReference<EnumMap<ABC,String>>() { });
+
+ assertNull(MAPPER_TO_NULL.readerFor(targetType).readValue(EMPTY_ARRAY));
+
+ EnumMap<?,?> result = MAPPER_TO_EMPTY.readerFor(targetType).readValue(EMPTY_ARRAY);
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, scalars
+ /**********************************************************
+ */
+
+ public void testNumbersFromEmptyArray() throws Exception
+ {
+ for (Class<?> targetType : new Class<?>[] {
+ Boolean.class, Character.class,
+ Byte.class, Short.class, Integer.class, Long.class,
+ Float.class, Double.class,
+ BigInteger.class, BigDecimal.class
+ }) {
+ // Default, fail; explicit fail
+ _verifyFailForEmptyArray(DEFAULT_READER, targetType);
+ _verifyFailForEmptyArray(MAPPER_TO_FAIL, targetType);
+
+ // Nulls for explicit, "TryConvert"
+ _verifyToNullCoercion(MAPPER_TO_NULL, targetType);
+ _verifyToNullCoercion(MAPPER_TRY_CONVERT, targetType);
+ }
+
+ // But as-empty needs separate
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Boolean.class, Boolean.FALSE);
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Character.class, Character.valueOf('\0'));
+
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Byte.class, Byte.valueOf((byte) 0));
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Short.class, Short.valueOf((short) 0));
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Integer.class, Integer.valueOf(0));
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Long.class, Long.valueOf(0L));
+
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Float.class, Float.valueOf(0f));
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, Double.class, Double.valueOf(0d));
+
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, BigInteger.class, BigInteger.ZERO);
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, BigDecimal.class, new BigDecimal(BigInteger.ZERO));
+ }
+
+ public void testOtherScalarsFromEmptyArray() throws Exception
+ {
+ for (Class<?> targetType : new Class<?>[] {
+ String.class, StringBuilder.class,
+ UUID.class, URL.class, URI.class,
+ Date.class, Calendar.class
+ }) {
+ _verifyFailForEmptyArray(DEFAULT_READER, targetType);
+ _verifyFailForEmptyArray(MAPPER_TO_FAIL, targetType);
+
+ // Nulls for explicit, "TryConvert"
+ _verifyToNullCoercion(MAPPER_TO_NULL, targetType);
+ _verifyToNullCoercion(MAPPER_TRY_CONVERT, targetType);
+ }
+
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, String.class, "");
+ StringBuilder sb = MAPPER_TO_EMPTY.readerFor(StringBuilder.class)
+ .readValue(EMPTY_ARRAY);
+ assertEquals(0, sb.length());
+
+ _verifyToEmptyCoercion(MAPPER_TO_EMPTY, UUID.class, new UUID(0L, 0L));
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void _verifyToNullCoercion(ObjectMapper mapper, Class<?> cls) throws Exception {
+ _verifyToNullCoercion(mapper.reader(), cls);
+ }
+
+ private void _verifyToNullCoercion(ObjectReader r, Class<?> cls) throws Exception {
+ Object result = r.forType(cls).readValue(EMPTY_ARRAY);
+ if (result != null) {
+ fail("Expect null for "+cls.getName()+", got: "+result);
+ }
+ }
+
+ private void _verifyToEmptyCoercion(ObjectMapper mapper, Class<?> cls, Object exp) throws Exception {
+ _verifyToEmptyCoercion(mapper.reader(), cls, exp);
+ }
+
+ private void _verifyToEmptyCoercion(ObjectReader r, Class<?> cls, Object exp) throws Exception {
+ Object result = r.forType(cls).readValue(EMPTY_ARRAY);
+ if (!exp.equals(result)) {
+ fail("Expect value ["+exp+"] for "+cls.getName()+", got: "+result);
+ }
+ }
+
+ private void _verifyFailForEmptyArray(ObjectMapper mapper, Class<?> targetType) throws Exception {
+ _verifyFailForEmptyArray(mapper.readerFor(targetType), targetType);
+ }
+
+ private void _verifyFailForEmptyArray(ObjectReader r, Class<?> targetType) throws Exception
+ {
+ try {
+ r.forType(targetType).readValue(EMPTY_ARRAY);
+ fail("Should not accept Empty Array for "+targetType.getName()+" by default");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "from Array value (token `JsonToken.START_ARRAY`)");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java
new file mode 100644
index 000000000..5bf27e405
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceFloatToIntTest.java
@@ -0,0 +1,298 @@
+package com.fasterxml.jackson.databind.convert;
+
+import java.math.BigInteger;
+
+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.MismatchedInputException;
+import com.fasterxml.jackson.databind.type.LogicalType;
+
+public class CoerceFloatToIntTest extends BaseMapTest
+{
+ private final ObjectMapper DEFAULT_MAPPER = sharedMapper();
+ private final ObjectReader READER_LEGACY_FAIL = DEFAULT_MAPPER.reader()
+ .without(DeserializationFeature.ACCEPT_FLOAT_AS_INT);
+
+ private final ObjectMapper MAPPER_TO_EMPTY; {
+ MAPPER_TO_EMPTY = newJsonMapper();
+ MAPPER_TO_EMPTY.coercionConfigFor(LogicalType.Integer)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.AsEmpty);
+ }
+
+ private final ObjectMapper MAPPER_TRY_CONVERT; {
+ MAPPER_TRY_CONVERT = newJsonMapper();
+ MAPPER_TRY_CONVERT.coercionConfigFor(LogicalType.Integer)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.TryConvert);
+ }
+
+ private final ObjectMapper MAPPER_TO_NULL; {
+ MAPPER_TO_NULL = newJsonMapper();
+ MAPPER_TO_NULL.coercionConfigFor(LogicalType.Integer)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.AsNull);
+ }
+
+ private final ObjectMapper MAPPER_TO_FAIL; {
+ MAPPER_TO_FAIL = newJsonMapper();
+ MAPPER_TO_FAIL.coercionConfigFor(LogicalType.Integer)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.Fail);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, defaults (legacy)
+ /********************************************************
+ */
+
+ public void testLegacyDoubleToIntCoercion() throws Exception
+ {
+ // by default, should be ok
+ Integer I = DEFAULT_MAPPER.readValue(" 1.25 ", Integer.class);
+ assertEquals(1, I.intValue());
+ {
+ IntWrapper w = DEFAULT_MAPPER.readValue("{\"i\":-2.25 }", IntWrapper.class);
+ assertEquals(-2, w.i);
+ int[] arr = DEFAULT_MAPPER.readValue("[ 1.25 ]", int[].class);
+ assertEquals(1, arr[0]);
+ }
+
+ Long L = DEFAULT_MAPPER.readValue(" 3.33 ", Long.class);
+ assertEquals(3L, L.longValue());
+ {
+ LongWrapper w = DEFAULT_MAPPER.readValue("{\"l\":-2.25 }", LongWrapper.class);
+ assertEquals(-2L, w.l);
+ long[] arr = DEFAULT_MAPPER.readValue("[ 1.25 ]", long[].class);
+ assertEquals(1, arr[0]);
+ }
+
+ Short S = DEFAULT_MAPPER.readValue("42.33", Short.class);
+ assertEquals(42, S.intValue());
+
+ BigInteger biggie = DEFAULT_MAPPER.readValue("95.3", BigInteger.class);
+ assertEquals(95L, biggie.longValue());
+ }
+
+ public void testLegacyFailDoubleToInt() throws Exception
+ {
+ _verifyCoerceFail(READER_LEGACY_FAIL, Integer.class, "1.5", "java.lang.Integer");
+ _verifyCoerceFail(READER_LEGACY_FAIL, Integer.TYPE, "1.5", "int");
+ _verifyCoerceFail(READER_LEGACY_FAIL, IntWrapper.class, "{\"i\":-2.25 }", "int");
+ _verifyCoerceFail(READER_LEGACY_FAIL, int[].class, "[ 2.5 ]", "element of `int[]`");
+ }
+
+ public void testLegacyFailDoubleToLong() throws Exception
+ {
+ _verifyCoerceFail(READER_LEGACY_FAIL, Long.class, "0.5");
+ _verifyCoerceFail(READER_LEGACY_FAIL, Long.TYPE, "-2.5");
+ _verifyCoerceFail(READER_LEGACY_FAIL, LongWrapper.class, "{\"l\": 7.7 }");
+ _verifyCoerceFail(READER_LEGACY_FAIL, long[].class, "[ -1.35 ]", "element of `long[]`");
+ }
+
+ public void testLegacyFailDoubleToOther() throws Exception
+ {
+ _verifyCoerceFail(READER_LEGACY_FAIL, Short.class, "0.5");
+ _verifyCoerceFail(READER_LEGACY_FAIL, Short.TYPE, "-2.5");
+ _verifyCoerceFail(READER_LEGACY_FAIL, short[].class, "[ -1.35 ]", "element of `short[]`");
+
+ _verifyCoerceFail(READER_LEGACY_FAIL, Byte.class, "0.5");
+ _verifyCoerceFail(READER_LEGACY_FAIL, Byte.TYPE, "-2.5");
+ _verifyCoerceFail(READER_LEGACY_FAIL, byte[].class, "[ -1.35 ]", "element of `byte[]`");
+
+ _verifyCoerceFail(READER_LEGACY_FAIL, BigInteger.class, "25236.256");
+
+ // 13-Jun-2020, tatu: No explicit deserializer for `AtomicLong` yet
+// _verifyCoerceFail(READER_LEGACY_FAIL, AtomicLong.class, "25236.256");
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, CoerceConfig, to null
+ /********************************************************
+ */
+
+ public void testCoerceConfigFloatToNull() throws Exception
+ {
+ assertNull(MAPPER_TO_NULL.readValue("1.5", Integer.class));
+ // `null` not possible for primitives, must use empty (aka default) value
+ assertEquals(Integer.valueOf(0), MAPPER_TO_NULL.readValue("1.5", Integer.TYPE));
+ {
+ IntWrapper w = MAPPER_TO_NULL.readValue( "{\"i\":-2.25 }", IntWrapper.class);
+ assertEquals(0, w.i);
+ int[] ints = MAPPER_TO_NULL.readValue("[ 2.5 ]", int[].class);
+ assertEquals(1, ints.length);
+ assertEquals(0, ints[0]);
+ }
+
+ assertNull(MAPPER_TO_NULL.readValue("2.5", Long.class));
+ assertEquals(Long.valueOf(0L), MAPPER_TO_NULL.readValue("-4.25", Long.TYPE));
+ {
+ LongWrapper w = MAPPER_TO_NULL.readValue( "{\"l\":-2.25 }", LongWrapper.class);
+ assertEquals(0L, w.l);
+ long[] l = MAPPER_TO_NULL.readValue("[ 2.5 ]", long[].class);
+ assertEquals(1, l.length);
+ assertEquals(0L, l[0]);
+ }
+
+ assertNull(MAPPER_TO_NULL.readValue("2.5", Short.class));
+ assertEquals(Short.valueOf((short) 0), MAPPER_TO_NULL.readValue("-4.25", Short.TYPE));
+ {
+ short[] s = MAPPER_TO_NULL.readValue("[ 2.5 ]", short[].class);
+ assertEquals(1, s.length);
+ assertEquals((short) 0, s[0]);
+ }
+
+ assertNull(MAPPER_TO_NULL.readValue("2.5", Byte.class));
+ assertEquals(Byte.valueOf((byte) 0), MAPPER_TO_NULL.readValue("-4.25", Byte.TYPE));
+ {
+ byte[] arr = MAPPER_TO_NULL.readValue("[ 2.5 ]", byte[].class);
+ assertEquals(1, arr.length);
+ assertEquals((byte) 0, arr[0]);
+ }
+
+ assertNull(MAPPER_TO_NULL.readValue("2.5", BigInteger.class));
+ {
+ BigInteger[] arr = MAPPER_TO_NULL.readValue("[ 2.5 ]", BigInteger[].class);
+ assertEquals(1, arr.length);
+ assertNull(arr[0]);
+ }
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, CoerceConfig, to empty
+ /********************************************************
+ */
+
+ public void testCoerceConfigFloatToEmpty() throws Exception
+ {
+ assertEquals(Integer.valueOf(0), MAPPER_TO_EMPTY.readValue("1.2", Integer.class));
+ assertEquals(Integer.valueOf(0), MAPPER_TO_EMPTY.readValue("1.5", Integer.TYPE));
+ {
+ IntWrapper w = MAPPER_TO_EMPTY.readValue( "{\"i\":-2.25 }", IntWrapper.class);
+ assertEquals(0, w.i);
+ int[] ints = MAPPER_TO_EMPTY.readValue("[ 2.5 ]", int[].class);
+ assertEquals(1, ints.length);
+ assertEquals(0, ints[0]);
+ }
+
+ assertEquals(Long.valueOf(0), MAPPER_TO_EMPTY.readValue("1.2", Long.class));
+ assertEquals(Long.valueOf(0), MAPPER_TO_EMPTY.readValue("1.5", Long.TYPE));
+ {
+ LongWrapper w = MAPPER_TO_EMPTY.readValue( "{\"l\":-2.25 }", LongWrapper.class);
+ assertEquals(0L, w.l);
+ long[] l = MAPPER_TO_EMPTY.readValue("[ 2.5 ]", long[].class);
+ assertEquals(1, l.length);
+ assertEquals(0L, l[0]);
+ }
+
+ assertEquals(Short.valueOf((short)0), MAPPER_TO_EMPTY.readValue("1.2", Short.class));
+ assertEquals(Short.valueOf((short) 0), MAPPER_TO_EMPTY.readValue("1.5", Short.TYPE));
+
+ assertEquals(Byte.valueOf((byte)0), MAPPER_TO_EMPTY.readValue("1.2", Byte.class));
+ assertEquals(Byte.valueOf((byte) 0), MAPPER_TO_EMPTY.readValue("1.5", Byte.TYPE));
+
+ assertEquals(BigInteger.valueOf(0L), MAPPER_TO_EMPTY.readValue("124.5", BigInteger.class));
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, CoerceConfig, coerce
+ /********************************************************
+ */
+
+ public void testCoerceConfigFloatSuccess() throws Exception
+ {
+ assertEquals(Integer.valueOf(1), MAPPER_TRY_CONVERT.readValue("1.2", Integer.class));
+ assertEquals(Integer.valueOf(3), MAPPER_TRY_CONVERT.readValue("3.4", Integer.TYPE));
+ {
+ IntWrapper w = MAPPER_TRY_CONVERT.readValue( "{\"i\":-2.25 }", IntWrapper.class);
+ assertEquals(-2, w.i);
+ int[] ints = MAPPER_TRY_CONVERT.readValue("[ 22.10 ]", int[].class);
+ assertEquals(1, ints.length);
+ assertEquals(22, ints[0]);
+ }
+
+ assertEquals(Long.valueOf(1), MAPPER_TRY_CONVERT.readValue("1.2", Long.class));
+ assertEquals(Long.valueOf(1), MAPPER_TRY_CONVERT.readValue("1.5", Long.TYPE));
+ {
+ LongWrapper w = MAPPER_TRY_CONVERT.readValue( "{\"l\":-2.25 }", LongWrapper.class);
+ assertEquals(-2L, w.l);
+ long[] l = MAPPER_TRY_CONVERT.readValue("[ 2.2 ]", long[].class);
+ assertEquals(1, l.length);
+ assertEquals(2L, l[0]);
+ }
+
+ assertEquals(Short.valueOf((short)1), MAPPER_TRY_CONVERT.readValue("1.2", Short.class));
+ assertEquals(Short.valueOf((short) 19), MAPPER_TRY_CONVERT.readValue("19.2", Short.TYPE));
+
+ assertEquals(Byte.valueOf((byte)1), MAPPER_TRY_CONVERT.readValue("1.2", Byte.class));
+ assertEquals(Byte.valueOf((byte) 1), MAPPER_TRY_CONVERT.readValue("1.5", Byte.TYPE));
+
+ assertEquals(BigInteger.valueOf(124L), MAPPER_TRY_CONVERT.readValue("124.2", BigInteger.class));
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, CoerceConfig, fail
+ /********************************************************
+ */
+
+ public void testCoerceConfigFailFromFloat() throws Exception
+ {
+ _verifyCoerceFail(MAPPER_TO_FAIL, Integer.class, "1.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, Integer.TYPE, "1.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, IntWrapper.class, "{\"i\":-2.25 }", "int");
+ _verifyCoerceFail(MAPPER_TO_FAIL, int[].class, "[ 2.5 ]", "element of `int[]`");
+
+ _verifyCoerceFail(MAPPER_TO_FAIL, Long.class, "0.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, Long.TYPE, "-2.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, LongWrapper.class, "{\"l\": 7.7 }");
+ _verifyCoerceFail(MAPPER_TO_FAIL, long[].class, "[ -1.35 ]", "element of `long[]`");
+
+ _verifyCoerceFail(MAPPER_TO_FAIL, Short.class, "0.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, Short.TYPE, "-2.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, short[].class, "[ -1.35 ]", "element of `short[]`");
+
+ _verifyCoerceFail(MAPPER_TO_FAIL, Byte.class, "0.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, Byte.TYPE, "-2.5");
+ _verifyCoerceFail(MAPPER_TO_FAIL, byte[].class, "[ -1.35 ]", "element of `byte[]`");
+
+ _verifyCoerceFail(MAPPER_TO_FAIL, BigInteger.class, "25236.256");
+ }
+
+ /*
+ /********************************************************
+ /* Helper methods
+ /********************************************************
+ */
+
+ private void _verifyCoerceFail(ObjectMapper m, Class<?> targetType,
+ String doc) throws Exception
+ {
+ _verifyCoerceFail(m.reader(), targetType, doc, targetType.getName());
+ }
+
+ private void _verifyCoerceFail(ObjectMapper m, Class<?> targetType,
+ String doc, String targetTypeDesc) throws Exception
+ {
+ _verifyCoerceFail(m.reader(), targetType, doc, targetTypeDesc);
+ }
+
+ private void _verifyCoerceFail(ObjectReader r, Class<?> targetType,
+ String doc) throws Exception
+ {
+ _verifyCoerceFail(r, targetType, doc, targetType.getName());
+ }
+
+ private void _verifyCoerceFail(ObjectReader r, Class<?> targetType,
+ String doc, String targetTypeDesc) throws Exception
+ {
+ try {
+ r.forType(targetType).readValue(doc);
+ fail("Should not accept Float for "+targetType.getName()+" by default");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce Floating-point");
+ verifyException(e, targetTypeDesc);
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/ScalarCoercionTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceJDKScalarsTest.java
index 0a20c7626..0fbade77e 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/ScalarCoercionTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceJDKScalarsTest.java
@@ -1,20 +1,22 @@
-package com.fasterxml.jackson.databind.struct;
+package com.fasterxml.jackson.databind.convert;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicBoolean;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
-public class ScalarCoercionTest extends BaseMapTest
+// Tests for "old" coercions (pre-2.12), with `MapperFeature.ALLOW_COERCION_OF_SCALARS`
+public class CoerceJDKScalarsTest extends BaseMapTest
{
static class BooleanPOJO {
- public boolean value;
+ public Boolean value;
}
private final ObjectMapper COERCING_MAPPER = jsonMapperBuilder()
@@ -55,6 +57,7 @@ public class ScalarCoercionTest extends BaseMapTest
_verifyNullOkFromEmpty(BigInteger.class, null);
_verifyNullOkFromEmpty(BigDecimal.class, null);
+ _verifyNullOkFromEmpty(AtomicBoolean.class, null);
}
private void _verifyNullOkFromEmpty(Class<?> type, Object exp) throws IOException
@@ -91,6 +94,7 @@ public class ScalarCoercionTest extends BaseMapTest
_verifyNullFail(BigInteger.class);
_verifyNullFail(BigDecimal.class);
+ _verifyNullFail(AtomicBoolean.class);
}
private void _verifyNullFail(Class<?> type) throws IOException
@@ -100,7 +104,6 @@ public class ScalarCoercionTest extends BaseMapTest
fail("Should have failed for "+type);
} catch (MismatchedInputException e) {
verifyException(e, "Cannot coerce empty String");
- verifyException(e, "Null value for");
}
}
@@ -110,7 +113,7 @@ public class ScalarCoercionTest extends BaseMapTest
/**********************************************************
*/
- public void testStringCoercionOk() throws Exception
+ public void testStringCoercionOkBoolean() throws Exception
{
// first successful coercions. Boolean has a ton...
_verifyCoerceSuccess("1", Boolean.TYPE, Boolean.TRUE);
@@ -119,13 +122,20 @@ public class ScalarCoercionTest extends BaseMapTest
_verifyCoerceSuccess(quote("true"), Boolean.class, Boolean.TRUE);
_verifyCoerceSuccess(quote("True"), Boolean.TYPE, Boolean.TRUE);
_verifyCoerceSuccess(quote("True"), Boolean.class, Boolean.TRUE);
+ _verifyCoerceSuccess(quote("TRUE"), Boolean.TYPE, Boolean.TRUE);
+ _verifyCoerceSuccess(quote("TRUE"), Boolean.class, Boolean.TRUE);
_verifyCoerceSuccess("0", Boolean.TYPE, Boolean.FALSE);
_verifyCoerceSuccess("0", Boolean.class, Boolean.FALSE);
_verifyCoerceSuccess(quote("false"), Boolean.TYPE, Boolean.FALSE);
_verifyCoerceSuccess(quote("false"), Boolean.class, Boolean.FALSE);
_verifyCoerceSuccess(quote("False"), Boolean.TYPE, Boolean.FALSE);
_verifyCoerceSuccess(quote("False"), Boolean.class, Boolean.FALSE);
+ _verifyCoerceSuccess(quote("FALSE"), Boolean.TYPE, Boolean.FALSE);
+ _verifyCoerceSuccess(quote("FALSE"), Boolean.class, Boolean.FALSE);
+ }
+ public void testStringCoercionOkNumbers() throws Exception
+ {
_verifyCoerceSuccess(quote("123"), Byte.TYPE, Byte.valueOf((byte) 123));
_verifyCoerceSuccess(quote("123"), Byte.class, Byte.valueOf((byte) 123));
_verifyCoerceSuccess(quote("123"), Short.TYPE, Short.valueOf((short) 123));
@@ -141,12 +151,27 @@ public class ScalarCoercionTest extends BaseMapTest
_verifyCoerceSuccess(quote("123"), BigInteger.class, BigInteger.valueOf(123));
_verifyCoerceSuccess(quote("123.0"), BigDecimal.class, new BigDecimal("123.0"));
+
+ AtomicBoolean ab = COERCING_MAPPER.readValue(quote("true"), AtomicBoolean.class);
+ assertNotNull(ab);
+ assertTrue(ab.get());
}
- public void testStringCoercionFail() throws Exception
+ public void testStringCoercionFailBoolean() throws Exception
{
_verifyRootStringCoerceFail("true", Boolean.TYPE);
_verifyRootStringCoerceFail("true", Boolean.class);
+ _verifyRootStringCoerceFail("True", Boolean.TYPE);
+ _verifyRootStringCoerceFail("True", Boolean.class);
+ _verifyRootStringCoerceFail("TRUE", Boolean.TYPE);
+ _verifyRootStringCoerceFail("TRUE", Boolean.class);
+
+ _verifyRootStringCoerceFail("false", Boolean.TYPE);
+ _verifyRootStringCoerceFail("false", Boolean.class);
+ }
+
+ public void testStringCoercionFailInteger() throws Exception
+ {
_verifyRootStringCoerceFail("123", Byte.TYPE);
_verifyRootStringCoerceFail("123", Byte.class);
_verifyRootStringCoerceFail("123", Short.TYPE);
@@ -155,6 +180,10 @@ public class ScalarCoercionTest extends BaseMapTest
_verifyRootStringCoerceFail("123", Integer.class);
_verifyRootStringCoerceFail("123", Long.TYPE);
_verifyRootStringCoerceFail("123", Long.class);
+ }
+
+ public void testStringCoercionFailFloat() throws Exception
+ {
_verifyRootStringCoerceFail("123.5", Float.TYPE);
_verifyRootStringCoerceFail("123.5", Float.class);
_verifyRootStringCoerceFail("123.5", Double.TYPE);
@@ -192,8 +221,10 @@ public class ScalarCoercionTest extends BaseMapTest
{
// And then we have coercions from more esoteric types too
- _verifyCoerceFail("65", Character.class);
- _verifyCoerceFail("65", Character.TYPE);
+ _verifyCoerceFail("65", Character.class,
+ "Cannot coerce Integer value (65) to `java.lang.Character` value");
+ _verifyCoerceFail("65", Character.TYPE,
+ "Cannot coerce Integer value (65) to `char` value");
}
/*
@@ -209,16 +240,15 @@ public class ScalarCoercionTest extends BaseMapTest
assertEquals(exp, result);
}
- private void _verifyCoerceFail(String input, Class<?> type) throws IOException
+ private void _verifyCoerceFail(String input, Class<?> type,
+ String... expMatches) throws IOException
{
try {
NOT_COERCING_MAPPER.readerFor(type)
.readValue(input);
fail("Should not have allowed coercion");
} catch (MismatchedInputException e) {
- verifyException(e, "Cannot coerce ");
- verifyException(e, " for type `");
- verifyException(e, "enable `MapperFeature.ALLOW_COERCION_OF_SCALARS` to allow");
+ verifyException(e, expMatches);
}
}
@@ -245,8 +275,8 @@ public class ScalarCoercionTest extends BaseMapTest
fail("Should not have allowed coercion");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot coerce ");
- verifyException(e, " for type `");
- verifyException(e, "enable `MapperFeature.ALLOW_COERCION_OF_SCALARS` to allow");
+ verifyException(e, " to `");
+ verifyException(e, "` value");
assertNotNull(e.getProcessor());
assertSame(p, e.getProcessor());
@@ -282,7 +312,7 @@ public class ScalarCoercionTest extends BaseMapTest
JsonToken tokenType, String tokenValue) throws IOException
{
// 2 different possibilities here
- verifyException(e, "Cannot coerce Number", "Cannot deserialize instance of `");
+ verifyException(e, "Cannot coerce Integer value", "Cannot deserialize value of type `");
JsonParser p = (JsonParser) e.getProcessor();
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java
new file mode 100644
index 000000000..405dd35e3
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java
@@ -0,0 +1,281 @@
+package com.fasterxml.jackson.databind.convert;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Calendar;
+import java.util.Currency;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+public class CoerceMiscScalarsTest extends BaseMapTest
+{
+ private final ObjectMapper DEFAULT_MAPPER = sharedMapper();
+
+ private final ObjectMapper MAPPER_EMPTY_TO_EMPTY;
+ {
+ MAPPER_EMPTY_TO_EMPTY = newJsonMapper();
+ MAPPER_EMPTY_TO_EMPTY.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
+ }
+
+ private final ObjectMapper MAPPER_EMPTY_TO_TRY_CONVERT;
+ {
+ MAPPER_EMPTY_TO_TRY_CONVERT = newJsonMapper();
+ MAPPER_EMPTY_TO_TRY_CONVERT.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyString, CoercionAction.TryConvert);
+ }
+
+ private final ObjectMapper MAPPER_EMPTY_TO_NULL;
+ {
+ MAPPER_EMPTY_TO_NULL = newJsonMapper();
+ MAPPER_EMPTY_TO_NULL.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);
+ }
+
+ private final ObjectMapper MAPPER_EMPTY_TO_FAIL;
+ {
+ MAPPER_EMPTY_TO_FAIL = newJsonMapper();
+ MAPPER_EMPTY_TO_FAIL.coercionConfigDefaults()
+ .setCoercion(CoercionInputShape.EmptyString, CoercionAction.Fail);
+ }
+
+ private final String JSON_EMPTY = quote("");
+
+ /*
+ /********************************************************
+ /* Test methods, defaults (legacy)
+ /********************************************************
+ */
+
+ public void testScalarDefaultsFromEmpty() throws Exception
+ {
+ // mostly as null, with some exceptions
+
+ _testScalarEmptyToNull(DEFAULT_MAPPER, File.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, URL.class);
+
+ _testScalarEmptyToEmpty(DEFAULT_MAPPER, URI.class,
+ URI.create(""));
+
+ _testScalarEmptyToNull(DEFAULT_MAPPER, Class.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, JavaType.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, Currency.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, Pattern.class);
+
+ _testScalarEmptyToEmpty(DEFAULT_MAPPER, Locale.class,
+ Locale.ROOT);
+
+ _testScalarEmptyToNull(DEFAULT_MAPPER, Charset.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, TimeZone.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, InetAddress.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, InetSocketAddress.class);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, successful coercions from empty String
+ /********************************************************
+ */
+
+ public void testScalarEmptyToNull() throws Exception
+ {
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, File.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, URL.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, URI.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, Class.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, JavaType.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, Currency.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, Pattern.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, Locale.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, Charset.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, TimeZone.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, InetAddress.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, InetSocketAddress.class);
+ }
+
+ public void testScalarEmptyToEmpty() throws Exception
+ {
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, File.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, URL.class);
+
+ _testScalarEmptyToEmpty(MAPPER_EMPTY_TO_EMPTY, URI.class,
+ URI.create(""));
+
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, Class.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, JavaType.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, Currency.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, Pattern.class);
+
+ _testScalarEmptyToEmpty(MAPPER_EMPTY_TO_EMPTY, Locale.class,
+ Locale.ROOT);
+
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, Charset.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, TimeZone.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, InetAddress.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, InetSocketAddress.class);
+ }
+
+ public void testScalarEmptyToTryConvert() throws Exception
+ {
+ // Should be same as `AsNull` for most but not all
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, File.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, URL.class);
+
+ _testScalarEmptyToEmpty(MAPPER_EMPTY_TO_TRY_CONVERT, URI.class,
+ URI.create(""));
+
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, Class.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, JavaType.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, Currency.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, Pattern.class);
+
+ _testScalarEmptyToEmpty(MAPPER_EMPTY_TO_TRY_CONVERT, Locale.class,
+ Locale.ROOT);
+
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, Charset.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, TimeZone.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, InetAddress.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, InetSocketAddress.class);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, failed coercions from empty String
+ /********************************************************
+ */
+
+ public void testScalarsFailFromEmpty() throws Exception
+ {
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, File.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, URL.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, URI.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Class.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, JavaType.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Currency.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Pattern.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Locale.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Charset.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, TimeZone.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, InetAddress.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, InetSocketAddress.class);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, (more) special type(s)
+ /********************************************************
+ */
+
+ // UUID is quite compatible, but not exactly due to historical reasons;
+ // also uses custom subtype, so test separately
+
+ public void testUUIDCoercions() throws Exception
+ {
+ // Coerce to `null` both by default, "TryConvert" and explicit
+ _testScalarEmptyToNull(DEFAULT_MAPPER, UUID.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, UUID.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, UUID.class);
+
+ // but allow separate "empty" value is specifically requested
+ _testScalarEmptyToEmpty(MAPPER_EMPTY_TO_EMPTY, UUID.class,
+ new UUID(0L, 0L));
+
+ // allow forcing failure, too
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, UUID.class);
+
+ // and allow failure with specifically configured per-class override, too
+ ObjectMapper failMapper = newJsonMapper();
+ failMapper.coercionConfigFor(UUID.class)
+ .setCoercion(CoercionInputShape.EmptyString, CoercionAction.Fail);
+ _verifyScalarToFail(failMapper, UUID.class);
+ }
+
+ // StringBuilder is its own special type, since it naturally maps
+ // from String values, hence separate testing
+ public void testStringBuilderCoercions() throws Exception
+ {
+ // should result in an "empty" StringBuilder for all valid settings
+ _checkEmptyStringBuilder(DEFAULT_MAPPER.readValue(JSON_EMPTY, StringBuilder.class));
+ _checkEmptyStringBuilder(MAPPER_EMPTY_TO_EMPTY.readValue(JSON_EMPTY, StringBuilder.class));
+ _checkEmptyStringBuilder(MAPPER_EMPTY_TO_TRY_CONVERT.readValue(JSON_EMPTY, StringBuilder.class));
+ _checkEmptyStringBuilder(MAPPER_EMPTY_TO_NULL.readValue(JSON_EMPTY, StringBuilder.class));
+ // and even alleged failure should not result in that since it's not coercion
+ _checkEmptyStringBuilder(MAPPER_EMPTY_TO_FAIL.readValue(JSON_EMPTY, StringBuilder.class));
+ }
+
+ private void _checkEmptyStringBuilder(StringBuilder sb) {
+ assertNotNull(sb);
+ assertEquals(0, sb.length());
+ }
+
+ // Date, Calendar also included here for convenience
+
+ public void testLegacyDateTimeCoercions() throws Exception
+ {
+ // Coerce to `null` both by default, "TryConvert" and explicit
+ _testScalarEmptyToNull(DEFAULT_MAPPER, Calendar.class);
+ _testScalarEmptyToNull(DEFAULT_MAPPER, Date.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, Calendar.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, Date.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, Calendar.class);
+ _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, Date.class);
+
+ // but allow separate "empty" value is specifically requested
+ Calendar emptyCal = new GregorianCalendar();
+ emptyCal.setTimeInMillis(0L);
+// _testScalarEmptyToEmpty(MAPPER_EMPTY_TO_EMPTY, Calendar.class, emptyCal);
+ _testScalarEmptyToEmpty(MAPPER_EMPTY_TO_EMPTY, Date.class, new Date(0L));
+
+ // allow forcing failure, too
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Calendar.class);
+ _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Date.class);
+ }
+
+ /*
+ /********************************************************
+ /* Second-level test helper methods
+ /********************************************************
+ */
+
+ private void _testScalarEmptyToNull(ObjectMapper mapper, Class<?> target) throws Exception
+ {
+ assertNull(mapper.readerFor(target).readValue(JSON_EMPTY));
+ }
+
+ private void _testScalarEmptyToEmpty(ObjectMapper mapper,
+ Class<?> target, Object emptyValue) throws Exception
+ {
+ Object result = mapper.readerFor(target).readValue(JSON_EMPTY);
+ if (result == null) {
+ fail("Expected empty, non-null value for "+target.getName()+", got null");
+ }
+ assertEquals(emptyValue, result);
+ }
+
+ private void _verifyScalarToFail(ObjectMapper mapper, Class<?> target) throws Exception
+ {
+ try {
+ /*Object result =*/ mapper.readerFor(target)
+ .readValue(JSON_EMPTY);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce empty String ");
+ verifyException(e, " to `"+target.getName());
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoercePojosTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoercePojosTest.java
new file mode 100644
index 000000000..79d63c4d8
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoercePojosTest.java
@@ -0,0 +1,188 @@
+package com.fasterxml.jackson.databind.convert;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+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.MismatchedInputException;
+import com.fasterxml.jackson.databind.type.LogicalType;
+
+public class CoercePojosTest extends BaseMapTest
+{
+ static class Bean {
+ public String a;
+ }
+
+ private final ObjectMapper MAPPER = newJsonMapper();
+
+ private final String JSON_EMPTY = quote("");
+ private final String JSON_BLANK = quote(" ");
+
+ /*
+ /********************************************************
+ /* Test methods, from empty String
+ /********************************************************
+ */
+
+ public void testPOJOFromEmptyStringLegacy() throws Exception
+ {
+ // first, verify default settings which do not accept empty String:
+ assertFalse(MAPPER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT));
+
+ // should be ok to enable dynamically
+ _verifyFromEmptyPass(MAPPER.reader()
+ .with(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT),
+ JSON_EMPTY);
+
+ }
+
+ public void testPOJOFromEmptyGlobalConfig() throws Exception
+ {
+ _testPOJOFromEmptyGlobalConfig(CoercionInputShape.EmptyString, JSON_EMPTY, null);
+ }
+
+ public void testPOJOFromEmptyLogicalTypeConfig() throws Exception
+ {
+ _testPOJOFromEmptyLogicalTypeConfig(CoercionInputShape.EmptyString, JSON_EMPTY, null);
+ }
+
+ public void testPOJOFromEmptyPhysicalTypeConfig() throws Exception
+ {
+ _testPOJOFromEmptyPhysicalTypeConfig(CoercionInputShape.EmptyString, JSON_EMPTY, null);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, from blank String
+ /********************************************************
+ */
+
+ public void testPOJOFromBlankGlobalConfig() throws Exception
+ {
+ _testPOJOFromEmptyGlobalConfig(CoercionInputShape.EmptyString, JSON_BLANK, Boolean.TRUE);
+ }
+
+ public void testPOJOFromBlankLogicalTypeConfig() throws Exception
+ {
+ _testPOJOFromEmptyLogicalTypeConfig(CoercionInputShape.EmptyString, JSON_BLANK, Boolean.TRUE);
+ }
+
+ public void testPOJOFromBlankPhysicalTypeConfig() throws Exception
+ {
+ _testPOJOFromEmptyPhysicalTypeConfig(CoercionInputShape.EmptyString, JSON_BLANK, Boolean.TRUE);
+ }
+
+ /*
+ /********************************************************
+ /* Second-level helper methods
+ /********************************************************
+ */
+
+ private void _testPOJOFromEmptyGlobalConfig(final CoercionInputShape shape, final String json,
+ Boolean allowEmpty)
+ throws Exception
+ {
+ ObjectMapper mapper;
+
+ // First, coerce to null
+ mapper = newJsonMapper();
+ mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.AsNull)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ assertNull(_verifyFromEmptyPass(mapper, json));
+
+ // Then coerce as empty
+ mapper = newJsonMapper();
+ mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.AsEmpty)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ Bean b = _verifyFromEmptyPass(mapper, json);
+ assertNotNull(b);
+
+ // and finally, "try convert", which aliases to 'null'
+ mapper = newJsonMapper();
+ mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.TryConvert)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ assertNull(_verifyFromEmptyPass(mapper, json));
+ }
+
+ private void _testPOJOFromEmptyLogicalTypeConfig(final CoercionInputShape shape, final String json,
+ Boolean allowEmpty)
+ throws Exception
+ {
+ ObjectMapper mapper;
+
+ // First, coerce to null
+ mapper = newJsonMapper();
+ mapper.coercionConfigFor(LogicalType.POJO).setCoercion(shape, CoercionAction.AsNull)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ assertNull(_verifyFromEmptyPass(mapper, json));
+
+ // Then coerce as empty
+ mapper = newJsonMapper();
+ mapper.coercionConfigFor(LogicalType.POJO).setCoercion(shape, CoercionAction.AsEmpty)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ Bean b = _verifyFromEmptyPass(mapper, json);
+ assertNotNull(b);
+
+ // But also make fail again with 2-level settings
+ mapper = newJsonMapper();
+ mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.AsNull)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ mapper.coercionConfigFor(LogicalType.POJO).setCoercion(shape,
+ CoercionAction.Fail);
+ _verifyFromEmptyFail(mapper, json);
+ }
+
+ private void _testPOJOFromEmptyPhysicalTypeConfig(final CoercionInputShape shape, final String json,
+ Boolean allowEmpty)
+ throws Exception
+ {
+ ObjectMapper mapper;
+
+ // First, coerce to null
+ mapper = newJsonMapper();
+ mapper.coercionConfigFor(Bean.class).setCoercion(shape, CoercionAction.AsNull)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ assertNull(_verifyFromEmptyPass(mapper, json));
+
+ // Then coerce as empty
+ mapper = newJsonMapper();
+ mapper.coercionConfigFor(Bean.class).setCoercion(shape, CoercionAction.AsEmpty)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ Bean b = _verifyFromEmptyPass(mapper, json);
+ assertNotNull(b);
+
+ // But also make fail again with 2-level settings, with physical having precedence
+ mapper = newJsonMapper();
+ mapper.coercionConfigFor(LogicalType.POJO).setCoercion(shape, CoercionAction.AsEmpty)
+ .setAcceptBlankAsEmpty(allowEmpty);
+ mapper.coercionConfigFor(Bean.class).setCoercion(shape, CoercionAction.Fail);
+ _verifyFromEmptyFail(mapper, json);
+ }
+
+ private Bean _verifyFromEmptyPass(ObjectMapper m, String json) throws Exception {
+ return _verifyFromEmptyPass(m.reader(), json);
+ }
+
+ private Bean _verifyFromEmptyPass(ObjectReader r, String json) throws Exception
+ {
+ return r.forType(Bean.class)
+ .readValue(json);
+ }
+
+ private void _verifyFromEmptyFail(ObjectMapper m, String json) throws Exception
+ {
+ try {
+ m.readValue(json, Bean.class);
+ fail("Should not accept Empty/Blank String for POJO with passed settings");
+ } catch (MismatchedInputException e) {
+ _verifyFailMessage(e);
+ }
+ }
+
+ private void _verifyFailMessage(JsonProcessingException e)
+ {
+ verifyException(e, "Cannot deserialize value of type ");
+ verifyException(e, " from empty String ", " from blank String ");
+ assertValidLocation(e.getLocation());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceToBooleanTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceToBooleanTest.java
new file mode 100644
index 000000000..f78c14db1
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceToBooleanTest.java
@@ -0,0 +1,273 @@
+package com.fasterxml.jackson.databind.convert;
+
+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.BaseMapTest;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.cfg.CoercionAction;
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.fasterxml.jackson.databind.type.LogicalType;
+
+public class CoerceToBooleanTest extends BaseMapTest
+{
+ static class BooleanPOJO {
+ public boolean value;
+ }
+
+ private final ObjectMapper DEFAULT_MAPPER = sharedMapper();
+
+ private final ObjectMapper LEGACY_NONCOERCING_MAPPER = jsonMapperBuilder()
+ .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
+ .build();
+
+ private final ObjectMapper MAPPER_TO_EMPTY; {
+ MAPPER_TO_EMPTY = newJsonMapper();
+ MAPPER_TO_EMPTY.coercionConfigFor(LogicalType.Boolean)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.AsEmpty);
+ }
+
+ private final ObjectMapper MAPPER_TRY_CONVERT; {
+ MAPPER_TRY_CONVERT = newJsonMapper();
+ MAPPER_TRY_CONVERT.coercionConfigFor(LogicalType.Boolean)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.TryConvert);
+ }
+
+ private final ObjectMapper MAPPER_TO_NULL; {
+ MAPPER_TO_NULL = newJsonMapper();
+ MAPPER_TO_NULL.coercionConfigFor(LogicalType.Boolean)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.AsNull);
+ }
+
+ private final ObjectMapper MAPPER_TO_FAIL; {
+ MAPPER_TO_FAIL = newJsonMapper();
+ MAPPER_TO_FAIL.coercionConfigFor(LogicalType.Boolean)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail);
+ }
+
+ private final static String DOC_WITH_0 = aposToQuotes("{'value':0}");
+ private final static String DOC_WITH_1 = aposToQuotes("{'value':1}");
+
+ /*
+ /**********************************************************
+ /* Unit tests: default, legacy configuration
+ /**********************************************************
+ */
+
+ public void testToBooleanCoercionSuccessPojo() throws Exception
+ {
+ BooleanPOJO p;
+ final ObjectReader r = DEFAULT_MAPPER.readerFor(BooleanPOJO.class);
+
+ p = r.readValue(DOC_WITH_0);
+ assertEquals(false, p.value);
+ p = r.readValue(utf8Bytes(DOC_WITH_0));
+ assertEquals(false, p.value);
+
+ p = r.readValue(DOC_WITH_1);
+ assertEquals(true, p.value);
+ p = r.readValue(utf8Bytes(DOC_WITH_1));
+ assertEquals(true, p.value);
+ }
+
+ public void testToBooleanCoercionSuccessRoot() throws Exception
+ {
+ final ObjectReader br = DEFAULT_MAPPER.readerFor(Boolean.class);
+
+ assertEquals(Boolean.FALSE, br.readValue(" 0"));
+ assertEquals(Boolean.FALSE, br.readValue(utf8Bytes(" 0")));
+ assertEquals(Boolean.TRUE, br.readValue(" -1"));
+ assertEquals(Boolean.TRUE, br.readValue(utf8Bytes(" -1")));
+
+ final ObjectReader atomicR = DEFAULT_MAPPER.readerFor(AtomicBoolean.class);
+
+ AtomicBoolean ab;
+
+ ab = atomicR.readValue(" 0");
+ ab = atomicR.readValue(utf8Bytes(" 0"));
+ assertEquals(false, ab.get());
+
+ ab = atomicR.readValue(" 111");
+ assertEquals(true, ab.get());
+ ab = atomicR.readValue(utf8Bytes(" 111"));
+ assertEquals(true, ab.get());
+ }
+
+ public void testToBooleanCoercionFailBytes() throws Exception
+ {
+ _verifyBooleanCoerceFail(aposToQuotes("{'value':1}"), true, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);
+
+ _verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
+ _verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
+ }
+
+ public void testToBooleanCoercionFailChars() throws Exception
+ {
+ _verifyBooleanCoerceFail(aposToQuotes("{'value':1}"), false, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);
+
+ _verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
+ _verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests: new CoercionConfig, as-null, as-empty, try-coerce
+ /**********************************************************
+ */
+
+ public void testIntToNullCoercion() throws Exception
+ {
+ assertNull(MAPPER_TO_NULL.readValue("0", Boolean.class));
+ assertNull(MAPPER_TO_NULL.readValue("1", Boolean.class));
+
+ // but due to coercion to `boolean`, can not return null here -- however,
+ // goes "1 -> false (no null for primitive) -> Boolean.FALSE
+ assertEquals(Boolean.FALSE, MAPPER_TO_NULL.readValue("0", Boolean.TYPE));
+ assertEquals(Boolean.FALSE, MAPPER_TO_NULL.readValue("1", Boolean.TYPE));
+
+ // As to AtomicBoolean: that type itself IS nullable since it's of LogicalType.Boolean so
+ assertNull(MAPPER_TO_NULL.readValue("0", AtomicBoolean.class));
+ assertNull(MAPPER_TO_NULL.readValue("1", AtomicBoolean.class));
+
+ BooleanPOJO p;
+ p = MAPPER_TO_NULL.readValue(DOC_WITH_0, BooleanPOJO.class);
+ assertFalse(p.value);
+ p = MAPPER_TO_NULL.readValue(DOC_WITH_1, BooleanPOJO.class);
+ assertFalse(p.value);
+ }
+
+ public void testIntToEmptyCoercion() throws Exception
+ {
+ // "empty" value for Boolean/boolean is False/false
+
+ assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("0", Boolean.class));
+ assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("1", Boolean.class));
+
+ assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("0", Boolean.TYPE));
+ assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("1", Boolean.TYPE));
+
+ AtomicBoolean ab;
+ ab = MAPPER_TO_EMPTY.readValue("0", AtomicBoolean.class);
+ assertFalse(ab.get());
+ ab = MAPPER_TO_EMPTY.readValue("1", AtomicBoolean.class);
+ assertFalse(ab.get());
+
+ BooleanPOJO p;
+ p = MAPPER_TO_EMPTY.readValue(DOC_WITH_0, BooleanPOJO.class);
+ assertFalse(p.value);
+ p = MAPPER_TO_EMPTY.readValue(DOC_WITH_1, BooleanPOJO.class);
+ assertFalse(p.value);
+ }
+
+ public void testIntToTryCoercion() throws Exception
+ {
+ // And "TryCoerce" should do what would be typically expected
+
+ assertEquals(Boolean.FALSE, MAPPER_TRY_CONVERT.readValue("0", Boolean.class));
+ assertEquals(Boolean.TRUE, MAPPER_TRY_CONVERT.readValue("1", Boolean.class));
+
+ assertEquals(Boolean.FALSE, MAPPER_TRY_CONVERT.readValue("0", Boolean.TYPE));
+ assertEquals(Boolean.TRUE, MAPPER_TRY_CONVERT.readValue("1", Boolean.TYPE));
+
+ AtomicBoolean ab;
+ ab = MAPPER_TRY_CONVERT.readValue("0", AtomicBoolean.class);
+ assertFalse(ab.get());
+ ab = MAPPER_TRY_CONVERT.readValue("1", AtomicBoolean.class);
+ assertTrue(ab.get());
+
+ BooleanPOJO p;
+ p = MAPPER_TRY_CONVERT.readValue(DOC_WITH_0, BooleanPOJO.class);
+ assertFalse(p.value);
+ p = MAPPER_TRY_CONVERT.readValue(DOC_WITH_1, BooleanPOJO.class);
+ assertTrue(p.value);
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests: new CoercionConfig, failing
+ /**********************************************************
+ */
+
+ public void testFailFromInteger() throws Exception
+ {
+ _verifyFailFromInteger(MAPPER_TO_FAIL, BooleanPOJO.class, DOC_WITH_0, Boolean.TYPE);
+ _verifyFailFromInteger(MAPPER_TO_FAIL, BooleanPOJO.class, DOC_WITH_1, Boolean.TYPE);
+
+ _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.class, "0");
+ _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.class, "42");
+
+ _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.TYPE, "0");
+ _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.TYPE, "999");
+
+ _verifyFailFromInteger(MAPPER_TO_FAIL, AtomicBoolean.class, "0");
+ _verifyFailFromInteger(MAPPER_TO_FAIL, AtomicBoolean.class, "-123");
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void _verifyBooleanCoerceFail(String doc, boolean useBytes,
+ JsonToken tokenType, String tokenValue, Class<?> targetType) throws IOException
+ {
+ // Test failure for root value: for both byte- and char-backed sources.
+
+ // [databind#2635]: important, need to use `readValue()` that takes content and NOT
+ // JsonParser, as this forces closing of underlying parser and exposes more issues.
+
+ final ObjectReader r = LEGACY_NONCOERCING_MAPPER.readerFor(targetType);
+ try {
+ if (useBytes) {
+ r.readValue(utf8Bytes(doc));
+ } else {
+ r.readValue(doc);
+ }
+ fail("Should not have allowed coercion");
+ } catch (MismatchedInputException e) {
+ _verifyBooleanCoerceFailReason(e, tokenType, tokenValue);
+ }
+ }
+
+ @SuppressWarnings("resource")
+ private void _verifyBooleanCoerceFailReason(MismatchedInputException e,
+ JsonToken tokenType, String tokenValue) throws IOException
+ {
+ verifyException(e, "Cannot coerce ");
+ verifyException(e, " to `");
+
+ JsonParser p = (JsonParser) e.getProcessor();
+
+ assertToken(tokenType, p.currentToken());
+
+ final String text = p.getText();
+ if (!tokenValue.equals(text)) {
+ String textDesc = (text == null) ? "NULL" : quote(text);
+ fail("Token text ("+textDesc+") via parser of type "+p.getClass().getName()
+ +" not as expected ("+quote(tokenValue)+")");
+ }
+ }
+
+ private void _verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc) throws Exception {
+ _verifyFailFromInteger(m, targetType, doc, targetType);
+ }
+
+ private void _verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc,
+ Class<?> valueType) throws Exception
+ {
+ try {
+ m.readerFor(targetType).readValue(doc);
+ fail("Should not accept Integer for "+targetType.getName()+" by default");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce Integer value");
+ verifyException(e, "to `"+valueType.getName()+"`");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java
deleted file mode 100644
index 03f79cfe0..000000000
--- a/src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.fasterxml.jackson.databind.convert;
-
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.exc.MismatchedInputException;
-
-public class NumericConversionTest extends BaseMapTest
-{
- private final ObjectMapper MAPPER = sharedMapper();
- private final ObjectReader R = MAPPER.reader().without(DeserializationFeature.ACCEPT_FLOAT_AS_INT);
-
- public void testDoubleToInt() throws Exception
- {
- // by default, should be ok
- Integer I = MAPPER.readValue(" 1.25 ", Integer.class);
- assertEquals(1, I.intValue());
- IntWrapper w = MAPPER.readValue("{\"i\":-2.25 }", IntWrapper.class);
- assertEquals(-2, w.i);
- int[] arr = MAPPER.readValue("[ 1.25 ]", int[].class);
- assertEquals(1, arr[0]);
-
- try {
- R.forType(Integer.class).readValue("1.5");
- fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
- try {
- R.forType(Integer.TYPE).readValue("1.5");
- fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
- try {
- R.forType(IntWrapper.class).readValue("{\"i\":-2.25 }");
- fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
- try {
- R.forType(int[].class).readValue("[ 2.5 ]");
- fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
- }
-
- public void testDoubleToLong() throws Exception
- {
- // by default, should be ok
- Long L = MAPPER.readValue(" 3.33 ", Long.class);
- assertEquals(3L, L.longValue());
- LongWrapper w = MAPPER.readValue("{\"l\":-2.25 }", LongWrapper.class);
- assertEquals(-2L, w.l);
- long[] arr = MAPPER.readValue("[ 1.25 ]", long[].class);
- assertEquals(1, arr[0]);
-
- try {
- R.forType(Long.class).readValue("1.5");
- fail("Should not pass");
- } catch (MismatchedInputException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
-
- try {
- R.forType(Long.TYPE).readValue("1.5");
- fail("Should not pass");
- } catch (MismatchedInputException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
-
- try {
- R.forType(LongWrapper.class).readValue("{\"l\": 7.7 }");
- fail("Should not pass");
- } catch (MismatchedInputException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
- try {
- R.forType(long[].class).readValue("[ 2.5 ]");
- fail("Should not pass");
- } catch (MismatchedInputException e) {
- verifyException(e, "Cannot coerce a floating-point");
- }
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestArrayConversions.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestArrayConversions.java
index fbc4751bb..33117d4aa 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestArrayConversions.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestArrayConversions.java
@@ -12,7 +12,7 @@ public class TestArrayConversions
extends com.fasterxml.jackson.databind.BaseMapTest
{
final static String OVERFLOW_MSG_BYTE = "out of range of Java byte";
- final static String OVERFLOW_MSG = "overflow";
+ final static String OVERFLOW_MSG_SHORT = "out of range of Java short";
final static String OVERFLOW_MSG_INT = "out of range of int";
final static String OVERFLOW_MSG_LONG = "out of range of long";
@@ -103,7 +103,7 @@ public class TestArrayConversions
try {
MAPPER.convertValue(new int[] { -99999 }, short[].class);
} catch (IllegalArgumentException e) {
- verifyException(e, OVERFLOW_MSG);
+ verifyException(e, OVERFLOW_MSG_SHORT);
}
// Int overflow
try {
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestMapConversions.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestMapConversions.java
index bfed9dffe..aabe0eaa3 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestMapConversions.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestMapConversions.java
@@ -70,11 +70,11 @@ public class TestMapConversions
public void testMapToBean()
{
EnumMap<AB,String> map = new EnumMap<AB,String>(AB.class);
- map.put(AB.A, " 17");
- map.put(AB.B, " -1");
+ map.put(AB.A, "17");
+ map.put(AB.B, "-1");
Bean bean = MAPPER.convertValue(map, Bean.class);
assertEquals(Integer.valueOf(17), bean.A);
- assertEquals(" -1", bean.B);
+ assertEquals("-1", bean.B);
}
public void testBeanToMap()
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestStringConversions.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestStringConversions.java
index 93a9d67b1..5f874ce93 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestStringConversions.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestStringConversions.java
@@ -35,7 +35,7 @@ public class TestStringConversions
public void testSimple()
{
assertEquals(Boolean.TRUE, MAPPER.convertValue("true", Boolean.class));
- assertEquals(Integer.valueOf(-3), MAPPER.convertValue(" -3 ", Integer.class));
+ assertEquals(Integer.valueOf(-3), MAPPER.convertValue("-3", Integer.class));
assertEquals(Long.valueOf(77), MAPPER.convertValue("77", Long.class));
int[] ints = { 1, 2, 3 };
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeser1805Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeser1805Test.java
deleted file mode 100644
index 7741337eb..000000000
--- a/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeser1805Test.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.fasterxml.jackson.databind.deser;
-
-import java.util.*;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.*;
-
-public class ReadOnlyDeser1805Test extends BaseMapTest
-{
- static class Foo {
- @JsonProperty(access = JsonProperty.Access.READ_ONLY)
- private List<Long> list = new ArrayList<>();
-
- List<Long> getList() {
- return list;
- }
-
- public Foo setList(List<Long> list) {
- this.list = list;
- return this;
- }
- }
-
- // [databind#1805]
- static class UserWithReadOnly {
- public String name;
-
- @JsonProperty(access = JsonProperty.Access.READ_ONLY)
- public List<String> getRoles() {
- return Arrays.asList("admin", "monitor");
- }
- }
-
- // [databind#1805]
- @JsonIgnoreProperties(value={ "roles" }, allowGetters=true)
- static class UserAllowGetters {
- public String name;
-
- public List<String> getRoles() {
- return Arrays.asList("admin", "monitor");
- }
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- private final ObjectMapper MAPPER = newJsonMapper();
-
- public void testReadOnly1382() throws Exception
- {
- String payload = "{\"list\":[1,2,3,4]}";
- Foo foo = MAPPER.readValue(payload, Foo.class);
- assertTrue("List should be empty", foo.getList().isEmpty());
- }
-
- // [databind#1805]
- public void testViaReadOnly() throws Exception {
- UserWithReadOnly user = new UserWithReadOnly();
- user.name = "foo";
- String json = MAPPER.writeValueAsString(user);
- UserWithReadOnly result = MAPPER.readValue(json, UserWithReadOnly.class);
- assertNotNull(result);
- }
-
- // [databind#1805]
- public void testUsingAllowGetters() throws Exception {
- UserAllowGetters user = new UserAllowGetters();
- user.name = "foo";
- String json = MAPPER.writeValueAsString(user);
- assertTrue(json.contains("roles"));
- UserAllowGetters result = MAPPER.readValue(json, UserAllowGetters.class);
- assertNotNull(result);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeserFailOnUnknown2719Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeserFailOnUnknown2719Test.java
new file mode 100644
index 000000000..7e1f6ea51
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeserFailOnUnknown2719Test.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.databind.deser;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+
+public class ReadOnlyDeserFailOnUnknown2719Test extends BaseMapTest
+{
+ // [databind#2719]
+ static class UserWithReadOnly {
+ @JsonProperty(value = "username", access = JsonProperty.Access.READ_ONLY)
+ public String name;
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ public String password;
+ public String login;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newJsonMapper();
+
+ public void testFailOnIgnore() throws Exception
+ {
+ ObjectReader r = MAPPER.readerFor(UserWithReadOnly.class);
+
+ // First, fine to get 'login'
+ UserWithReadOnly result = r.readValue(aposToQuotes("{'login':'foo'}"));
+ assertEquals("foo", result.login);
+
+ // but not 'password'
+ r = r.with(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
+ try {
+ r.readValue(aposToQuotes("{'login':'foo', 'password':'bar'}"));
+ fail("Should fail");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Ignored field");
+ }
+
+ // or 'username'
+ r = r.with(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
+ try {
+ r.readValue(aposToQuotes("{'login':'foo', 'username':'bar'}"));
+ fail("Should fail");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Ignored field");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/PropertyAccessReadOnly2118Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyListDeser2118Test.java
index fa7860885..0fb1e5cb9 100644
--- a/src/test/java/com/fasterxml/jackson/failing/PropertyAccessReadOnly2118Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyListDeser2118Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.deser;
import java.util.*;
@@ -6,12 +6,11 @@ import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
-public class PropertyAccessReadOnly2118Test extends BaseMapTest
+public class ReadOnlyListDeser2118Test extends BaseMapTest
{
// [databind#2118]
static class SecurityGroup {
-
- private List<SecurityGroupRule> securityGroupRules;
+ List<SecurityGroupRule> securityGroupRules;
public SecurityGroup() {
this.securityGroupRules = new ArrayList<>();
@@ -40,15 +39,21 @@ public class PropertyAccessReadOnly2118Test extends BaseMapTest
public void setId(String id) {
this.id = id;
}
+
+ @Override
+ public String toString() {
+ return "{SecurityGroupRule '"+id+"'}";
+ }
}
+ private final ObjectMapper mapper = newJsonMapper();
+
// [databind#2118]
public void testAccessReadOnly() throws Exception {
- String data ="{\"security_group_rules\": [{\"id\": \"id1\"}, {\"id\": \"id2\"}, {\"id\": \"id3\"}, {\"id\": \"id4\"}]}";
- ObjectMapper mapper = new ObjectMapper();
-// This would work around the issue:
+ String data ="{\"security_group_rules\": [{\"id\": \"id1\"}]}";
+// This would work around the issue:
// mapper.disable(MapperFeature.USE_GETTERS_AS_SETTERS);
SecurityGroup sg = mapper.readValue(data, SecurityGroup.class);
- System.out.println(mapper.writeValueAsString(sg));
+ assertEquals(Collections.emptyList(), sg.securityGroupRules);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/ReadOnlyList2283Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyListDeser2283Test.java
index 533265fc4..66e0fc527 100644
--- a/src/test/java/com/fasterxml/jackson/failing/ReadOnlyList2283Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyListDeser2283Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.deser;
import java.util.*;
@@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.*;
// [databind#2283]: ignore read-only Lists even if "getter-as-setter" enabled
-public class ReadOnlyList2283Test
+public class ReadOnlyListDeser2283Test
extends BaseMapTest
{
static class RenamedToSameOnGetter {
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/ReadOrWriteOnlyTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOrWriteOnlyTest.java
index 0afd3ffb9..21f35c356 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/ReadOrWriteOnlyTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOrWriteOnlyTest.java
@@ -1,8 +1,13 @@
package com.fasterxml.jackson.databind.deser;
import java.beans.ConstructorProperties;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
+
import com.fasterxml.jackson.databind.*;
public class ReadOrWriteOnlyTest extends BaseMapTest
@@ -66,6 +71,59 @@ public class ReadOrWriteOnlyTest extends BaseMapTest
protected Foo1345() { }
}
+ // [databind#1382]
+ static class Foo1382 {
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private List<Long> list = new ArrayList<>();
+
+ List<Long> getList() {
+ return list;
+ }
+
+ public Foo1382 setList(List<Long> list) {
+ this.list = list;
+ return this;
+ }
+ }
+
+ // [databind#1805]
+ static class UserWithReadOnly1805 {
+ public String name;
+
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ public List<String> getRoles() {
+ return Arrays.asList("admin", "monitor");
+ }
+ }
+
+ // [databind#1805]
+ @JsonIgnoreProperties(value={ "roles" }, allowGetters=true)
+ static class UserAllowGetters1805 {
+ public String name;
+
+ public List<String> getRoles() {
+ return Arrays.asList("admin", "monitor");
+ }
+ }
+
+ // [databind#2779]: ignorable property renaming
+ static class Bean2779 {
+ String works;
+
+ @JsonProperty(value = "t", access = JsonProperty.Access.READ_ONLY)
+ public String getDoesntWork() {
+ return "pleaseFixThisBug";
+ }
+
+ public String getWorks() {
+ return works;
+ }
+
+ public void setWorks(String works) {
+ this.works = works;
+ }
+ }
+
/*
/**********************************************************
/* Test methods
@@ -93,6 +151,7 @@ public class ReadOrWriteOnlyTest extends BaseMapTest
assertNotNull(result);
}
+ // [databind#1345]
public void testReadOnly1345() throws Exception
{
Foo1345 result = MAPPER.readValue("{\"name\":\"test\"}", Foo1345.class);
@@ -100,4 +159,42 @@ public class ReadOrWriteOnlyTest extends BaseMapTest
assertEquals("test", result.name);
assertNull(result.id);
}
+
+ // [databind#1382]
+ public void testReadOnly1382() throws Exception
+ {
+ String payload = "{\"list\":[1,2,3,4]}";
+ Foo1382 foo = MAPPER.readValue(payload, Foo1382.class);
+ assertTrue("List should be empty", foo.getList().isEmpty());
+ }
+
+ // [databind#1805]
+ public void testViaReadOnly() throws Exception {
+ UserWithReadOnly1805 user = new UserWithReadOnly1805();
+ user.name = "foo";
+ String json = MAPPER.writeValueAsString(user);
+ UserWithReadOnly1805 result = MAPPER.readValue(json, UserWithReadOnly1805.class);
+ assertNotNull(result);
+ }
+
+ // [databind#1805]
+ public void testUsingAllowGetters() throws Exception {
+ UserAllowGetters1805 user = new UserAllowGetters1805();
+ user.name = "foo";
+ String json = MAPPER.writeValueAsString(user);
+ assertTrue(json.contains("roles"));
+ UserAllowGetters1805 result = MAPPER.readValue(json, UserAllowGetters1805.class);
+ assertNotNull(result);
+ }
+
+ // [databind#2779]: ignorable property renaming
+ public void testIssue2779() throws Exception
+ {
+ Bean2779 bean = new Bean2779();
+ bean.setWorks("works");
+
+ String json = MAPPER.writeValueAsString(bean);
+ Bean2779 newBean = MAPPER.readValue(json, Bean2779.class);
+ assertNotNull(newBean);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java
index 988ece38d..05850407c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java
@@ -322,7 +322,7 @@ public class TestBeanDeserializer extends BaseMapTest
/********************************************************
*/
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newJsonMapper();
/**
* Test to verify details of how trying to deserialize into
@@ -370,24 +370,6 @@ public class TestBeanDeserializer extends BaseMapTest
assertEquals(2, Issue476Deserializer.propCount);
}
- public void testPOJOFromEmptyString() throws Exception
- {
- // first, verify default settings which do not accept empty String:
- assertFalse(MAPPER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT));
- try {
- MAPPER.readValue(quote(""), Bean.class);
- fail("Should not accept Empty String for POJO");
- } catch (JsonProcessingException e) {
- verifyException(e, "from String value");
- assertValidLocation(e.getLocation());
- }
- // should be ok to enable dynamically
- ObjectReader r = MAPPER.readerFor(Bean.class)
- .with(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
- Bean result = r.readValue(quote(""));
- assertNull(result);
- }
-
// [databind#120]
public void testModifyArrayDeserializer() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderErrorHandling.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderErrorHandling.java
index f7f67b0c5..b71c1306a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderErrorHandling.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderErrorHandling.java
@@ -54,7 +54,7 @@ public class BuilderErrorHandling extends BaseMapTest
MAPPER.readValue(json, ValueClassXY.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
- verifyException(e, "unrecognized field");
+ verifyException(e, "Unrecognized field");
}
// but pass if ok to ignore
ValueClassXY result = MAPPER.readerFor(ValueClassXY.class)
@@ -63,4 +63,16 @@ public class BuilderErrorHandling extends BaseMapTest
assertEquals(2, result._x);
assertEquals(5, result._y);
}
+
+ public void testWrongShape() throws Exception
+ {
+ try {
+ MAPPER.readValue("123", ValueClassXY.class);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot construct instance of ");
+ // should report Builder class, not value here, right?
+ verifyException(e, "$SimpleBuilderXY");
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderFailTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderFailTest.java
index 55f9feb92..743d52db6 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderFailTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderFailTest.java
@@ -70,7 +70,8 @@ public class BuilderFailTest extends BaseMapTest
MAPPER.readValue(json, ValueClassWrongBuildType.class);
fail("Missing expected InvalidDefinitionException exception");
} catch (InvalidDefinitionException e) {
- verifyException(e, "Build method");
+ verifyException(e, "Build method ");
+ verifyException(e, "#build()");
verifyException(e, "has wrong return type");
}
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithTypeParametersTest.java
index 79beaee6f..7b8ddf6fa 100644
--- a/src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithTypeParametersTest.java
@@ -1,13 +1,17 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.deser.builder;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import java.util.LinkedHashMap;
+import java.util.List;
-public class BuilderDeserializationTest921
+// [databind#921]: support infering type parameters from Builder
+public class BuilderWithTypeParametersTest
extends BaseMapTest
{
public static class MyPOJO {
@@ -33,6 +37,12 @@ public class BuilderDeserializationTest921
return data;
}
+ // 28-Apr-2020, tatu: Note that as per [databind#921] the NAME of
+ // type variable here MUST match that of enclosing class. This has
+ // no semantic meaning to JDK or javac, but internally
+ // `MapperFeature.INFER_BUILDER_TYPE_BINDINGS` relies on this -- but
+ // can not really validate it. So user just has to rely on bit of
+ // black magic to use generic types with builders.
public static class Builder<T> {
private List<T> data;
@@ -77,15 +87,30 @@ public class BuilderDeserializationTest921
}
}
- public void testWithBuilder() throws Exception {
- final ObjectMapper mapper = new ObjectMapper();
+ public void testWithBuilderInferringBindings() throws Exception {
+ final ObjectMapper mapper = jsonMapperBuilder()
+ .enable(MapperFeature.INFER_BUILDER_TYPE_BINDINGS)
+ .build();
+ final String json = aposToQuotes("{ 'data': [ { 'x': 'x', 'y': 'y' } ] }");
+ final MyGenericPOJO<MyPOJO> deserialized =
+ mapper.readValue(json, new TypeReference<MyGenericPOJO<MyPOJO>>() {});
+ assertEquals(1, deserialized.data.size());
+ Object ob = deserialized.data.get(0);
+ assertNotNull(ob);
+ assertEquals(MyPOJO.class, ob.getClass());
+ }
+
+ public void testWithBuilderWithoutInferringBindings() throws Exception {
+ final ObjectMapper mapper = jsonMapperBuilder()
+ .disable(MapperFeature.INFER_BUILDER_TYPE_BINDINGS)
+ .build();
final String json = aposToQuotes("{ 'data': [ { 'x': 'x', 'y': 'y' } ] }");
final MyGenericPOJO<MyPOJO> deserialized =
mapper.readValue(json, new TypeReference<MyGenericPOJO<MyPOJO>>() {});
assertEquals(1, deserialized.data.size());
Object ob = deserialized.data.get(0);
assertNotNull(ob);
- assertEquals(MyPOJO.class, ob.getClass());
+ assertEquals(LinkedHashMap.class, ob.getClass());
}
public void testWithCreator() throws Exception {
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreatorsTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreatorsTest.java
index c0ae77ca6..e33897e47 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreatorsTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreatorsTest.java
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
public class DelegatingArrayCreatorsTest extends BaseMapTest
{
@@ -74,6 +75,20 @@ public class DelegatingArrayCreatorsTest extends BaseMapTest
private Bag2324<Value2324> bagOfValues;
}
+ static class MultipleArrayDelegators {
+ @JsonCreator(mode=JsonCreator.Mode.DELEGATING)
+ MultipleArrayDelegators(List<Integer> a) { }
+
+ @JsonCreator(mode=JsonCreator.Mode.DELEGATING)
+ MultipleArrayDelegators(Set<Integer> a) { }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
private final ObjectMapper MAPPER = sharedMapper();
// [databind#1804]
@@ -96,4 +111,15 @@ public class DelegatingArrayCreatorsTest extends BaseMapTest
assertEquals(3, result.getValues().size());
assertEquals(new Value2324("a"), result.getValues().iterator().next());
}
+
+ public void testInvalidTwoArrayDelegating() throws Exception {
+ try {
+ /*MultipleArrayDelegators result =*/ MAPPER.readerFor(MultipleArrayDelegators.class)
+ .readValue("[ ]");
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Conflicting array-delegate creators");
+ verifyException(e, "already had explicitly marked");
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java
index 065172064..edde73955 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java
@@ -26,7 +26,30 @@ public class TestCreatorsDelegating extends BaseMapTest
}
}
- // for [JACKSON-711]; should allow delegate-based one(s) too
+ static class IntegerBean
+ {
+ protected Integer value;
+
+ public IntegerBean(Integer v) { value = v; }
+
+ @JsonCreator
+ protected static IntegerBean create(Integer value) {
+ return new IntegerBean(value);
+ }
+ }
+
+ static class LongBean
+ {
+ protected Long value;
+
+ public LongBean(Long v) { value = v; }
+
+ @JsonCreator
+ protected static LongBean create(Long value) {
+ return new LongBean(value);
+ }
+ }
+
static class CtorBean711
{
protected String name;
@@ -40,7 +63,6 @@ public class TestCreatorsDelegating extends BaseMapTest
}
}
- // for [JACKSON-711]; should allow delegate-based one(s) too
static class FactoryBean711
{
protected String name1;
@@ -116,7 +138,7 @@ public class TestCreatorsDelegating extends BaseMapTest
*/
private final ObjectMapper MAPPER = newJsonMapper();
-
+
public void testBooleanDelegate() throws Exception
{
// should obviously work with booleans...
@@ -127,8 +149,28 @@ public class TestCreatorsDelegating extends BaseMapTest
bb = MAPPER.readValue(quote("true"), BooleanBean.class);
assertEquals(Boolean.TRUE, bb.value);
}
-
- // As per [JACKSON-711]: should also work with delegate model (single non-annotated arg)
+
+ public void testIntegerDelegate() throws Exception
+ {
+ IntegerBean bb = MAPPER.readValue("-13", IntegerBean.class);
+ assertEquals(Integer.valueOf(-13), bb.value);
+
+ // but also with value conversion from String (unless blocked)
+ bb = MAPPER.readValue(quote("127"), IntegerBean.class);
+ assertEquals(Integer.valueOf(127), bb.value);
+ }
+
+ public void testLongDelegate() throws Exception
+ {
+ LongBean bb = MAPPER.readValue("11", LongBean.class);
+ assertEquals(Long.valueOf(11L), bb.value);
+
+ // but also with value conversion from String (unless blocked)
+ bb = MAPPER.readValue(quote("-99"), LongBean.class);
+ assertEquals(Long.valueOf(-99L), bb.value);
+ }
+
+ // should also work with delegate model (single non-annotated arg)
public void testWithCtorAndDelegate() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java
index 953ea8678..a9589daf7 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java
@@ -10,6 +10,33 @@ import com.fasterxml.jackson.databind.ObjectMapper;
public class IgnorePropertyOnDeserTest extends BaseMapTest
{
+ // [databind#426]
+ @JsonIgnoreProperties({ "userId" })
+ static class User {
+ public String firstName;
+ Integer userId;
+
+ public Integer getUserId() {
+ return userId;
+ }
+
+ public void setUserId(CharSequence id) {
+ userId = Integer.valueOf(id.toString());
+ }
+
+ public void setUserId(Integer v) {
+ this.userId = v;
+ }
+
+ public void setUserId(User u) {
+ // bogus
+ }
+
+ public void setUserId(boolean b) {
+ // bogus
+ }
+ }
+
// [databind#1217]
static class IgnoreObject {
public int x = 1;
@@ -62,6 +89,16 @@ public class IgnorePropertyOnDeserTest extends BaseMapTest
private final ObjectMapper MAPPER = newJsonMapper();
+ // [databind#426]
+ public void testIssue426() throws Exception
+ {
+ final String JSON = aposToQuotes("{'userId': 9, 'firstName': 'Mike' }");
+ User result = MAPPER.readerFor(User.class).readValue(JSON);
+ assertNotNull(result);
+ assertEquals("Mike", result.firstName);
+ assertNull(result.userId);
+ }
+
// [databind#1217]
public void testIgnoreOnProperty1217() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java
index 85beb40b3..815029a0c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java
@@ -362,7 +362,7 @@ public class ProblemHandlerTest extends BaseMapTest
mapper.readValue("true", Integer.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
- verifyException(e, "out of VALUE_TRUE token");
+ verifyException(e, "from Boolean value (token `JsonToken.VALUE_TRUE`)");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestArrayDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/ArrayDeserializationTest.java
index b0fd99694..ac8838bd8 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestArrayDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/ArrayDeserializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.*;
import java.util.*;
@@ -16,7 +16,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
* This unit test suite tries to verify that the "Native" java type
* mapper can properly re-construct Java array objects from Json arrays.
*/
-public class TestArrayDeserialization
+public class ArrayDeserializationTest
extends BaseMapTest
{
public final static class Bean1
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java
index 2feba30b6..40a9cb958 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java
@@ -702,7 +702,7 @@ public class DateDeserializationTest
fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled");
} catch (MismatchedInputException exp) {
verifyException(exp, "Cannot deserialize");
- verifyException(exp, "out of START_ARRAY");
+ verifyException(exp, "from Array value (token `JsonToken.START_ARRAY`)");
}
reader = reader.with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java
index e31636741..4b551193a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java
@@ -7,7 +7,10 @@ import java.math.BigInteger;
import java.util.List;
import java.util.Map;
+import com.fasterxml.jackson.annotation.*;
+
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
@@ -37,6 +40,42 @@ public class JDKNumberDeserTest extends BaseMapTest
public MyBeanValue(BigDecimal d) { this.decimal = d; }
}
+ // [databind#2644]
+ static class NodeRoot2644 {
+ public String type;
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
+ @JsonSubTypes(value = {
+ @JsonSubTypes.Type(value = NodeParent2644.class, name = "NodeParent")
+ })
+ public Node2644 node;
+ }
+
+ public static class NodeParent2644 extends Node2644 { }
+
+ public static abstract class Node2644 {
+ @JsonProperty("amount")
+ BigDecimal val;
+
+ public BigDecimal getVal() {
+ return val;
+ }
+
+ public void setVal(BigDecimal val) {
+ this.val = val;
+ }
+ }
+
+ // [databind#2784]
+ static class BigDecimalHolder2784 {
+ public BigDecimal value;
+ }
+
+ static class NestedBigDecimalHolder2784 {
+ @JsonUnwrapped
+ public BigDecimalHolder2784 holder;
+ }
+
/*
/**********************************************************************
/* Helper classes, serializers/deserializers/resolvers
@@ -138,7 +177,7 @@ public class JDKNumberDeserTest extends BaseMapTest
noCoerceMapper.readValue(NULL_JSON, Integer.TYPE);
fail("Should not have passed");
} catch (MismatchedInputException e) {
- verifyException(e, "Cannot coerce String \"null\"");
+ verifyException(e, "Cannot coerce String value");
}
}
@@ -299,4 +338,28 @@ public class JDKNumberDeserTest extends BaseMapTest
}
assertEquals(42, node.asInt());
}
+
+ // [databind#2644]
+ public void testBigDecimalSubtypes() throws Exception
+ {
+ ObjectMapper mapper = jsonMapperBuilder()
+ .registerSubtypes(NodeParent2644.class)
+ .build();
+ NodeRoot2644 root = mapper.readValue(
+ "{\"type\": \"NodeParent\",\"node\": {\"amount\": 9999999999999999.99} }",
+ NodeRoot2644.class
+ );
+
+ assertEquals(new BigDecimal("9999999999999999.99"), root.node.getVal());
+ }
+
+ // [databind#2784]
+ public void testBigDecimalUnwrapped() throws Exception
+ {
+ final ObjectMapper mapper = newJsonMapper();
+ // mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+ final String JSON = "{\"value\": 5.00}";
+ NestedBigDecimalHolder2784 result = mapper.readValue(JSON, NestedBigDecimalHolder2784.class);
+ assertEquals(new BigDecimal("5.00"), result.holder.value);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberLeniencyTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberLeniencyTest.java
index b7269cbf7..47d8bb410 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberLeniencyTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberLeniencyTest.java
@@ -32,7 +32,7 @@ public class JDKNumberLeniencyTest extends BaseMapTest
fail("Should not allow read in strict mode");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot coerce");
- verifyException(e, "for type `java.lang.Boolean`");
+ verifyException(e, "to `java.lang.Boolean` value");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java
index cdf9ec1ac..7e244a0e3 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java
@@ -2,6 +2,8 @@ package com.fasterxml.jackson.databind.deser.jdk;
import java.io.*;
import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.List;
import org.junit.Assert;
@@ -12,6 +14,7 @@ import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.JsonMappingException.Reference;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Unit tests for verifying handling of simple basic non-structured
@@ -120,6 +123,10 @@ public class JDKScalarsTest
private final ObjectMapper MAPPER = newJsonMapper();
+ private final ObjectMapper MAPPER_NO_COERCION = jsonMapperBuilder()
+ .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
+ .build();
+
/*
/**********************************************************
/* Scalar tests for boolean
@@ -226,12 +233,15 @@ public class JDKScalarsTest
public void testCharacterWrapper() throws Exception
{
// First: canonical value is 1-char string
- Character result = MAPPER.readValue("\"a\"", Character.class);
- assertEquals(Character.valueOf('a'), result);
+ assertEquals(Character.valueOf('a'), MAPPER.readValue(quote("a"), Character.class));
// But can also pass in ascii code
- result = MAPPER.readValue(" "+((int) 'X'), Character.class);
+ Character result = MAPPER.readValue(" "+((int) 'X'), Character.class);
assertEquals(Character.valueOf('X'), result);
+
+ // 22-Jun-2020, tatu: one special case turns out to be white space;
+ // need to avoid considering it "blank" value
+ assertEquals(Character.valueOf(' '), MAPPER.readValue(quote(" "), Character.class));
final CharacterWrapperBean wrapper = MAPPER.readValue("{\"v\":null}", CharacterWrapperBean.class);
assertNotNull(wrapper);
@@ -431,30 +441,6 @@ public class JDKScalarsTest
/**********************************************************
*/
- public void testEmptyToNullCoercionForPrimitives() throws Exception {
- _testEmptyToNullCoercion(int.class, Integer.valueOf(0));
- _testEmptyToNullCoercion(long.class, Long.valueOf(0));
- _testEmptyToNullCoercion(double.class, Double.valueOf(0.0));
- _testEmptyToNullCoercion(float.class, Float.valueOf(0.0f));
- }
-
- private void _testEmptyToNullCoercion(Class<?> primType, Object emptyValue) throws Exception
- {
- final String EMPTY = "\"\"";
-
- // as per [databind#1095] should only allow coercion from empty String,
- // if `null` is acceptable
- ObjectReader intR = MAPPER.readerFor(primType);
- assertEquals(emptyValue, intR.readValue(EMPTY));
- try {
- intR.with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
- .readValue("\"\"");
- fail("Should not have passed");
- } catch (MismatchedInputException e) {
- verifyException(e, "Cannot coerce empty String");
- }
- }
-
public void testBase64Variants() throws Exception
{
final byte[] INPUT = "abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890X".getBytes("UTF-8");
@@ -515,13 +501,15 @@ public class JDKScalarsTest
*/
// by default, should return nulls, n'est pas?
- public void testEmptyStringForWrappers() throws IOException
+ public void testEmptyStringForBooleanWrapper() throws IOException
{
- WrappersBean bean;
-
- bean = MAPPER.readValue("{\"booleanValue\":\"\"}", WrappersBean.class);
+ WrappersBean bean = MAPPER.readValue("{\"booleanValue\":\"\"}", WrappersBean.class);
assertNull(bean.booleanValue);
- bean = MAPPER.readValue("{\"byteValue\":\"\"}", WrappersBean.class);
+ }
+
+ public void testEmptyStringForIntegerWrappers() throws IOException
+ {
+ WrappersBean bean = MAPPER.readValue("{\"byteValue\":\"\"}", WrappersBean.class);
assertNull(bean.byteValue);
// char/Character is different... not sure if this should work or not:
@@ -534,18 +522,25 @@ public class JDKScalarsTest
assertNull(bean.intValue);
bean = MAPPER.readValue("{\"longValue\":\"\"}", WrappersBean.class);
assertNull(bean.longValue);
- bean = MAPPER.readValue("{\"floatValue\":\"\"}", WrappersBean.class);
+ }
+
+ public void testEmptyStringForFloatWrappers() throws IOException
+ {
+ WrappersBean bean = MAPPER.readValue("{\"floatValue\":\"\"}", WrappersBean.class);
assertNull(bean.floatValue);
bean = MAPPER.readValue("{\"doubleValue\":\"\"}", WrappersBean.class);
assertNull(bean.doubleValue);
}
- public void testEmptyStringForPrimitives() throws IOException
+ public void testEmptyStringForBooleanPrimitive() throws IOException
{
- PrimitivesBean bean;
- bean = MAPPER.readValue("{\"booleanValue\":\"\"}", PrimitivesBean.class);
+ PrimitivesBean bean = MAPPER.readValue("{\"booleanValue\":\"\"}", PrimitivesBean.class);
assertFalse(bean.booleanValue);
- bean = MAPPER.readValue("{\"byteValue\":\"\"}", PrimitivesBean.class);
+ }
+
+ public void testEmptyStringForIntegerPrimitives() throws IOException
+ {
+ PrimitivesBean bean = MAPPER.readValue("{\"byteValue\":\"\"}", PrimitivesBean.class);
assertEquals((byte) 0, bean.byteValue);
bean = MAPPER.readValue("{\"charValue\":\"\"}", PrimitivesBean.class);
assertEquals((char) 0, bean.charValue);
@@ -555,38 +550,30 @@ public class JDKScalarsTest
assertEquals(0, bean.intValue);
bean = MAPPER.readValue("{\"longValue\":\"\"}", PrimitivesBean.class);
assertEquals(0L, bean.longValue);
- bean = MAPPER.readValue("{\"floatValue\":\"\"}", PrimitivesBean.class);
+ }
+
+ public void testEmptyStringForFloatPrimitives() throws IOException
+ {
+ PrimitivesBean bean = MAPPER.readValue("{\"floatValue\":\"\"}", PrimitivesBean.class);
assertEquals(0.0f, bean.floatValue);
bean = MAPPER.readValue("{\"doubleValue\":\"\"}", PrimitivesBean.class);
assertEquals(0.0, bean.doubleValue);
}
- private void _verifyEmptyStringFailForPrimitives(String propName) throws IOException
+ // for [databind#403]
+ public void testEmptyStringFailForBooleanPrimitive() throws IOException
{
final ObjectReader reader = MAPPER
.readerFor(PrimitivesBean.class)
.with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
try {
- reader.readValue(aposToQuotes("{'"+propName+"':''}"));
+ reader.readValue(aposToQuotes("{'booleanValue':''}"));
fail("Expected failure for boolean + empty String");
} catch (JsonMappingException e) {
- verifyException(e, "Cannot coerce empty String (\"\")");
- verifyException(e, "to Null value");
+ verifyException(e, "Cannot coerce `null` to `boolean`");
+ verifyException(e, "FAIL_ON_NULL_FOR_PRIMITIVES");
}
}
-
- // for [databind#403]
- public void testEmptyStringFailForPrimitives() throws IOException
- {
- _verifyEmptyStringFailForPrimitives("booleanValue");
- _verifyEmptyStringFailForPrimitives("byteValue");
- _verifyEmptyStringFailForPrimitives("charValue");
- _verifyEmptyStringFailForPrimitives("shortValue");
- _verifyEmptyStringFailForPrimitives("intValue");
- _verifyEmptyStringFailForPrimitives("longValue");
- _verifyEmptyStringFailForPrimitives("floatValue");
- _verifyEmptyStringFailForPrimitives("doubleValue");
- }
/*
/**********************************************************
@@ -722,18 +709,18 @@ public class JDKScalarsTest
final String JSON_WITH_NULL = "[ null ]";
final String SIMPLE_NAME = "`"+cls.getSimpleName()+"`";
final ObjectReader readerCoerceOk = MAPPER.readerFor(cls);
- final ObjectReader readerNoCoerce = readerCoerceOk
+ final ObjectReader readerNoNulls = readerCoerceOk
.with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
Object ob = readerCoerceOk.forType(cls).readValue(JSON_WITH_NULL);
assertEquals(1, Array.getLength(ob));
assertEquals(defValue, Array.get(ob, 0));
try {
- readerNoCoerce.readValue(JSON_WITH_NULL);
+ readerNoNulls.readValue(JSON_WITH_NULL);
fail("Should not pass");
} catch (JsonMappingException e) {
verifyException(e, "Cannot coerce `null`");
- verifyException(e, "as content of type "+SIMPLE_NAME);
+ verifyException(e, "to element of "+SIMPLE_NAME);
}
if (testEmptyString) {
@@ -741,12 +728,14 @@ public class JDKScalarsTest
assertEquals(1, Array.getLength(ob));
assertEquals(defValue, Array.get(ob, 0));
+ final ObjectReader readerNoEmpty = MAPPER_NO_COERCION.readerFor(cls);
try {
- readerNoCoerce.readValue(EMPTY_STRING_JSON);
+ readerNoEmpty.readValue(EMPTY_STRING_JSON);
fail("Should not pass");
} catch (JsonMappingException e) {
- verifyException(e, "Cannot coerce empty String (\"\")");
- verifyException(e, "as content of type "+SIMPLE_NAME);
+ // 07-Jun-2020, tatu: during transition, two acceptable alternatives
+ verifyException(e, "Cannot coerce `null` to", "Cannot coerce empty String (\"\")");
+ verifyException(e, "element of "+SIMPLE_NAME);
}
}
}
@@ -782,11 +771,11 @@ public class JDKScalarsTest
// char[] is special, cannot use generalized test here
// _testInvalidStringCoercionFail(char[].class);
- _testInvalidStringCoercionFail(short[].class);
- _testInvalidStringCoercionFail(int[].class);
- _testInvalidStringCoercionFail(long[].class);
- _testInvalidStringCoercionFail(float[].class);
- _testInvalidStringCoercionFail(double[].class);
+ _testInvalidStringCoercionFail(short[].class, "short");
+ _testInvalidStringCoercionFail(int[].class, "int");
+ _testInvalidStringCoercionFail(long[].class, "long");
+ _testInvalidStringCoercionFail(float[].class, "float");
+ _testInvalidStringCoercionFail(double[].class, "double");
}
private void _testInvalidStringCoercionFail(Class<?> cls) throws IOException
@@ -801,9 +790,38 @@ public class JDKScalarsTest
try {
MAPPER.readerFor(cls).readValue(JSON);
- fail("Should not pass");
+ fail("Should MismatchedInputException pass");
} catch (JsonMappingException e) {
verifyException(e, "Cannot deserialize value of type `"+targetTypeName+"` from String \"foobar\"");
}
}
+
+ /*
+ /**********************************************************
+ /* Tests for mismatch: JSON Object for scalars (not supported
+ /* for JSON
+ /**********************************************************
+ */
+
+ public void testFailForScalarFromObject() throws Exception
+ {
+ _testFailForNumberFromObject(Byte.TYPE);
+ _testFailForNumberFromObject(Short.TYPE);
+ _testFailForNumberFromObject(Long.TYPE);
+ _testFailForNumberFromObject(Float.TYPE);
+ _testFailForNumberFromObject(Double.TYPE);
+ _testFailForNumberFromObject(BigInteger.class);
+ _testFailForNumberFromObject(BigDecimal.class);
+ }
+
+ private void _testFailForNumberFromObject(Class<?> targetType) throws Exception
+ {
+ try {
+ MAPPER.readValue(a2q("{'value':12}"), targetType);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "from Object value");
+ verifyException(e, ClassUtil.getClassDescription(targetType));
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java
index b4e5892bd..9af210d3c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java
@@ -138,10 +138,6 @@ public class JDKStringLikeTypesTest extends BaseMapTest
MAPPER.readValue(quote("fi_FI_savo"), Locale.class));
assertEquals(new Locale("en", "US"),
MAPPER.readValue(quote("en-US"), Locale.class));
-
- // [databind#1123]
- Locale loc = MAPPER.readValue(quote(""), Locale.class);
- assertSame(Locale.ROOT, loc);
}
public void testCharSequence() throws IOException
@@ -261,14 +257,9 @@ public class JDKStringLikeTypesTest extends BaseMapTest
final URI value = new URI("http://foo.com");
assertEquals(value, reader.readValue("\""+value.toString()+"\""));
- // Also: empty String should be handled properly
- URI result = reader.readValue(quote(""));
- assertNotNull(result);
- assertEquals(URI.create(""), result);
-
// and finally: broken URI should give proper failure
try {
- result = reader.readValue(quote("a b"));
+ URI result = reader.readValue(quote("a b"));
fail("Should not accept malformed URI, instead got: "+result);
} catch (InvalidFormatException e) {
verifyException(e, "not a valid textual representation");
@@ -303,9 +294,9 @@ public class JDKStringLikeTypesTest extends BaseMapTest
public void testUUID() throws Exception
{
- final ObjectMapper mapper = objectMapper();
-
final String NULL_UUID = "00000000-0000-0000-0000-000000000000";
+ final ObjectReader r = MAPPER.readerFor(UUID.class);
+
// first, couple of generated UUIDs:
for (String value : new String[] {
"76e6d183-5f68-4afa-b94a-922c1fdb83f8",
@@ -315,12 +306,10 @@ public class JDKStringLikeTypesTest extends BaseMapTest
"82994ac2-7b23-49f2-8cc5-e24cf6ed77be",
"00000007-0000-0000-0000-000000000000"
}) {
-
- mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
UUID uuid = UUID.fromString(value);
assertEquals(uuid,
- mapper.readValue(quote(value), UUID.class));
+ r.without(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ .readValue(quote(value)));
}
// then use templating; note that these are not exactly valid UUIDs
// wrt spec (type bits etc), but JDK UUID should deal ok
@@ -330,13 +319,13 @@ public class JDKStringLikeTypesTest extends BaseMapTest
for (int i = 0; i < chars.length(); ++i) {
String value = TEMPL.replace('0', chars.charAt(i));
assertEquals(UUID.fromString(value).toString(),
- mapper.readValue(quote(value), UUID.class).toString());
+ r.readValue(quote(value)).toString());
}
// also: see if base64 encoding works as expected
String base64 = Base64Variants.getDefaultVariant().encode(new byte[16]);
assertEquals(UUID.fromString(NULL_UUID),
- mapper.readValue(quote(base64), UUID.class));
+ r.readValue(quote(base64)));
}
public void testUUIDInvalid() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestGenericMapDeser.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapWithGenericValuesDeserTest.java
index e18dd2575..db6ad7700 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestGenericMapDeser.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapWithGenericValuesDeserTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.util.*;
@@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.TypeFactory;
@SuppressWarnings("serial")
-public class TestGenericMapDeser
+public class MapWithGenericValuesDeserTest
extends BaseMapTest
{
/*
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/VoidProperties2675Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/VoidProperties2675Test.java
new file mode 100644
index 000000000..1aae71209
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/VoidProperties2675Test.java
@@ -0,0 +1,61 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
+
+// [databind#2675]: Void-valued "properties"
+public class VoidProperties2675Test extends BaseMapTest
+{
+ static class VoidBean {
+ protected Void value;
+
+ public Void getValue() { return null; }
+
+// public void setValue(Void v) { }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ private final ObjectMapper DEFAULT_MAPPER = sharedMapper();
+
+ private final ObjectMapper VOID_MAPPER = jsonMapperBuilder()
+ .enable(MapperFeature.ALLOW_VOID_VALUED_PROPERTIES)
+ .build();
+
+ public void testVoidBeanSerialization() throws Exception
+ {
+ // By default (2.x), not enabled:
+ try {
+ DEFAULT_MAPPER.writeValueAsString(new VoidBean());
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "no properties discovered");
+ }
+
+ // but when enabled
+ assertEquals("{\"value\":null}", VOID_MAPPER.writeValueAsString(new VoidBean()));
+ }
+
+ public void testVoidBeanDeserialization() throws Exception {
+ final String DOC = "{\"value\":null}";
+ VoidBean result;
+
+ // By default (2.x), not enabled:
+ try {
+ result = DEFAULT_MAPPER.readValue(DOC, VoidBean.class);
+ fail("Should not pass");
+ } catch (UnrecognizedPropertyException e) {
+ verifyException(e, "Unrecognized field \"value\"");
+ }
+
+ // but when enabled
+ result = VOID_MAPPER.readValue(DOC, VoidBean.class);
+ assertNotNull(result);
+ assertNull(result.getValue());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java
index 536a5b428..7e19c9a4c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java
@@ -157,7 +157,7 @@ public class ExceptionDeserializationTest
mapper.readValue(value, IOException.class);
fail("Exception not thrown when attempting to deserialize an IOException wrapped in a single value array with UNWRAP_SINGLE_VALUE_ARRAYS disabled");
} catch (JsonMappingException exp2) {
- verifyException(exp2, "out of START_ARRAY");
+ verifyException(exp2, "from Array value (token `JsonToken.START_ARRAY`)");
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/interop/DateJava8FallbacksTest.java b/src/test/java/com/fasterxml/jackson/databind/interop/DateJava8FallbacksTest.java
new file mode 100644
index 000000000..37fd4e79c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/interop/DateJava8FallbacksTest.java
@@ -0,0 +1,45 @@
+package com.fasterxml.jackson.databind.interop;
+
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+
+// [databind#2683]: add fallback handling for Java 8 date/time types, to
+// prevent accidental serialization as POJOs, as well as give more information
+// on deserialization attempts
+//
+// @since 2.12
+public class DateJava8FallbacksTest extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = newJsonMapper();
+
+ private final OffsetDateTime DATETIME_EPOCH = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L),
+ ZoneOffset.of("Z"));
+
+ // Test to prevent serialization as POJO, without Java 8 date/time module:
+ public void testPreventSerialization() throws Exception
+ {
+ try {
+ String json = MAPPER.writerWithDefaultPrettyPrinter()
+ .writeValueAsString(DATETIME_EPOCH);
+ fail("Should not pass, wrote out as\n: "+json);
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Java 8 date/time type `java.time.OffsetDateTime` not supported by default");
+ verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\"");
+ }
+ }
+
+ public void testBetterDeserializationError() throws Exception
+ {
+ try {
+ OffsetDateTime result = MAPPER.readValue(" 0 ", OffsetDateTime.class);
+ fail("Not expecting to pass, resulted in: "+result);
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Java 8 date/time type `java.time.OffsetDateTime` not supported by default");
+ verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\"");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestMultipleTypeNames.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestMultipleTypeNames.java
new file mode 100644
index 000000000..021dd1cb2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestMultipleTypeNames.java
@@ -0,0 +1,136 @@
+package com.fasterxml.jackson.databind.jsontype;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
+
+import java.util.List;
+
+// Tests for [databind#2761] (and [annotations#171]
+public class TestMultipleTypeNames extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = newJsonMapper();
+
+ // common classes
+ static class MultiTypeName { }
+
+ static class A extends MultiTypeName {
+ private long x;
+ public long getX() { return x; }
+ }
+
+ static class B extends MultiTypeName {
+ private float y;
+ public float getY() { return y; }
+ }
+
+ // data for test 1
+ static class WrapperForNamesTest {
+ private List<BaseForNamesTest> base;
+ public List<BaseForNamesTest> getBase() { return base; }
+ }
+
+ static class BaseForNamesTest {
+ private String type;
+ public String getType() { return type; }
+
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+ property = "type"
+ )
+ @JsonSubTypes(value = {
+ @JsonSubTypes.Type(value = A.class, names = "a"),
+ @JsonSubTypes.Type(value = B.class, names = {"b","c"}),
+ })
+ private MultiTypeName data;
+ public MultiTypeName getData() { return data; }
+ }
+
+ static class WrapperForNameAndNamesTest {
+ private List<BaseForNameAndNamesTest> base;
+ public List<BaseForNameAndNamesTest> getBase() { return base; }
+ }
+
+ static class BaseForNameAndNamesTest {
+ private String type;
+ public String getType() { return type; }
+
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+ property = "type"
+ )
+ @JsonSubTypes(value = {
+ @JsonSubTypes.Type(value = A.class, name = "a"),
+ @JsonSubTypes.Type(value = B.class, names = {"b","c"}),
+ })
+ private MultiTypeName data;
+ public MultiTypeName getData() { return data; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testOnlyNames() throws Exception
+ {
+ String json;
+ WrapperForNamesTest w;
+
+ // TC 1 : all KV serialisation
+ json = "{\"base\": [{\"type\":\"a\", \"data\": {\"x\": 5}}, {\"type\":\"b\", \"data\": {\"y\": 3.1}}, {\"type\":\"c\", \"data\": {\"y\": 33.8}}]}";
+ w = MAPPER.readValue(json, WrapperForNamesTest.class);
+ assertNotNull(w);
+ assertEquals(3, w.base.size());
+ assertTrue(w.base.get(0).data instanceof A);
+ assertEquals(5l, ((A) w.base.get(0).data).x);
+ assertTrue(w.base.get(1).data instanceof B);
+ assertEquals(3.1f, ((B) w.base.get(1).data).y, 0);
+ assertTrue(w.base.get(2).data instanceof B);
+ assertEquals(33.8f, ((B) w.base.get(2).data).y, 0);
+
+
+ // TC 2 : incorrect serialisation
+ json = "{\"data\": [{\"type\":\"a\", \"data\": {\"x\": 2.2}}, {\"type\":\"b\", \"data\": {\"y\": 5.3}}, {\"type\":\"c\", \"data\": {\"y\": 9.8}}]}";
+ try {
+ MAPPER.readValue(json, WrapperForNamesTest.class);
+ fail("This serialisation should fail 'coz of x being float");
+ } catch (UnrecognizedPropertyException e) {
+ verifyException(e, "Unrecognized field \"data\"");
+ }
+ }
+
+ public void testNameAndNames() throws Exception
+ {
+ String json;
+ WrapperForNameAndNamesTest w;
+
+ // TC 1 : all KV serialisation
+ json = "{\"base\": [{\"type\":\"a\", \"data\": {\"x\": 5}}, {\"type\":\"b\", \"data\": {\"y\": 3.1}}, {\"type\":\"c\", \"data\": {\"y\": 33.8}}]}";
+ w = MAPPER.readValue(json, WrapperForNameAndNamesTest.class);
+ assertNotNull(w);
+ assertEquals(3, w.base.size());
+ assertTrue(w.base.get(0).data instanceof A);
+ assertEquals(5l, ((A) w.base.get(0).data).x);
+ assertTrue(w.base.get(1).data instanceof B);
+ assertEquals(3.1f, ((B) w.base.get(1).data).y, 0);
+ assertTrue(w.base.get(2).data instanceof B);
+ assertEquals(33.8f, ((B) w.base.get(2).data).y, 0);
+
+
+ // TC 2 : incorrect serialisation
+ json = "{\"data\": [{\"type\":\"a\", \"data\": {\"x\": 2.2}}, {\"type\":\"b\", \"data\": {\"y\": 5.3}}, {\"type\":\"c\", \"data\": {\"y\": 9.8}}]}";
+ try {
+ MAPPER.readValue(json, WrapperForNameAndNamesTest.class);
+ fail("This serialisation should fail 'coz of x being float");
+ } catch (UnrecognizedPropertyException e) {
+ verifyException(e, "Unrecognized field \"data\"");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
index c6fa2bb35..4557e0840 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
@@ -4,12 +4,9 @@ import com.fasterxml.jackson.core.Version;
import java.util.*;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonSubTypes;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
-import com.fasterxml.jackson.databind.JsonMappingException;
+
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
@@ -95,7 +92,9 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.PROPERTY, property="type")
@JsonSubTypes({ @JsonSubTypes.Type(ImplX.class),
- @JsonSubTypes.Type(ImplY.class) })
+ @JsonSubTypes.Type(ImplY.class),
+ @JsonSubTypes.Type(ImplAbs.class)
+ })
static abstract class BaseX { }
@JsonTypeName("x")
@@ -111,6 +110,11 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
public int y;
}
+ // for [databind#919] testing
+ @JsonTypeName("abs")
+ abstract static class ImplAbs extends BaseX {
+ }
+
// [databind#663]
static class AtomicWrapper {
public BaseX value;
@@ -372,16 +376,16 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
bean = mapper.readValue("{\"#type\":\"foobar\"}", SuperTypeWithoutDefault.class);
assertEquals(DefaultImpl505.class, bean.getClass());
assertEquals(0, ((DefaultImpl505) bean).a);
-
}
-
+
public void testErrorMessage() throws Exception {
ObjectMapper mapper = new ObjectMapper();
try {
mapper.readValue("{ \"type\": \"z\"}", BaseX.class);
fail("Should have failed");
- } catch (JsonMappingException e) {
- verifyException(e, "known type ids =");
+ } catch (InvalidTypeIdException e) {
+ verifyException(e, "Could not resolve type id 'z' as a subtype of");
+ verifyException(e, "known type ids = [x, y]");
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java
index 15d95f82d..8a066f6d2 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java
@@ -126,7 +126,7 @@ public class TestWithGenerics extends BaseMapTest
otherAnimal = a2;
}
}
-
+
/*
/**********************************************************
/* Unit tests
diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitiveDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitiveDeserTest.java
index 362039391..dbb738ca7 100644
--- a/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitiveDeserTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitiveDeserTest.java
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
public class CaseInsensitiveDeserTest extends BaseMapTest
{
@@ -93,13 +94,31 @@ public class CaseInsensitiveDeserTest extends BaseMapTest
}
}
+ // [databind#1886]: allow case-insensitivity by default on a class
+ @JsonFormat(with={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
+ static class CaseInsensitiveRole {
+ public String ID;
+ public String Name;
+ }
+
+ // [databind#1886]: allow case-insensitivity by default on a class
+ static class CaseInsensitiveRoleContainer {
+ public CaseInsensitiveRole role;
+ }
+
+ // [databind#1886]: ... but also overrides
+ static class CaseSensitiveRoleContainer {
+ @JsonFormat(without={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
+ public CaseInsensitiveRole role;
+ }
+
/*
/********************************************************
/* Test methods
/********************************************************
*/
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newJsonMapper();
private final ObjectMapper INSENSITIVE_MAPPER = jsonMapperBuilder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.build();
@@ -158,7 +177,7 @@ public class CaseInsensitiveDeserTest extends BaseMapTest
}
// And allow config overrides too
- public void testCaseInsensitiveWithClassFormat() throws Exception
+ public void testCaseInsensitiveViaConfigOverride() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.configOverride(Role.class)
@@ -181,4 +200,34 @@ public class CaseInsensitiveDeserTest extends BaseMapTest
assertNotNull(result.getItems());
assertEquals(1, result.getItems().size());
}
+
+
+ // [databind#1886]: allow case-insensitivity by default on a class
+ public void testCaseInsensitiveViaClassAnnotation() throws Exception
+ {
+ final String CONTAINED = aposToQuotes("{'role': {'id':'3','name':'Bob'}}");
+
+ // First: via wrapper/container:
+ CaseInsensitiveRoleContainer cont = MAPPER.readValue(CONTAINED,
+ CaseInsensitiveRoleContainer.class);
+ assertEquals("3", cont.role.ID);
+ assertEquals("Bob", cont.role.Name);
+
+ // second: directly as root value
+ CaseInsensitiveRole role = MAPPER.readValue
+ (aposToQuotes("{'id':'12','name':'Billy'}"),
+ CaseInsensitiveRole.class);
+ assertEquals("12", role.ID);
+ assertEquals("Billy", role.Name);
+
+ // and finally, more complicated; should be possible to force sensitivity:
+ try {
+ /*CaseSensitiveRoleContainer r =*/ MAPPER.readValue(CONTAINED,
+ CaseSensitiveRoleContainer.class);
+ fail("Should not pass");
+ } catch (UnrecognizedPropertyException e) {
+ verifyException(e, "Unrecognized ");
+ verifyException(e, "\"id\"");
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java
index e129dcf56..08bd47d98 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java
@@ -359,7 +359,7 @@ public class ArrayNodeTest
mapper.readValue(" 123 ", ArrayNode.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
- verifyException(e, "out of VALUE_NUMBER_INT token");
+ verifyException(e, "from Integer value (token `JsonToken.VALUE_NUMBER_INT`)");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
index 260e93ecc..cd15ee687 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
@@ -463,7 +463,7 @@ public class ObjectNodeTest
mapper.readValue("[ 1, 2, 3 ]", ObjectNode.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
- verifyException(e, "out of START_ARRAY token");
+ verifyException(e, "from Array value (token `JsonToken.START_ARRAY`)");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java
index 972a5fa8f..91eda5e44 100644
--- a/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java
@@ -63,7 +63,7 @@ public class ObjectId687Test extends BaseMapTest
/*****************************************************
*/
- private final ObjectMapper MAPPER = objectMapper();
+ private final ObjectMapper MAPPER = newJsonMapper();
// for [databind#687]
public void testSerializeDeserializeWithCreator() throws IOException {
diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId825BTest.java b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId825BTest.java
index af173eede..171f1ded1 100644
--- a/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId825BTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId825BTest.java
@@ -136,10 +136,15 @@ public class ObjectId825BTest extends BaseMapTest
private static final long serialVersionUID = 1L;
}
+ /*
+ /*****************************************************
+ /* Test methods
+ /*****************************************************
+ */
+
public void testFull825() throws Exception
{
final ObjectMapper mapper = jsonMapperBuilder()
- .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
.activateDefaultTyping(NoCheckSubTypeValidator.instance,
ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE)
.build();
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestArraySerialization.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestArraySerialization.java
index 10baa8ef6..e1cd38d69 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestArraySerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestArraySerialization.java
@@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.*;
public class TestArraySerialization
extends BaseMapTest
{
- private final ObjectMapper MAPPER = newJsonMapper();
+ private final ObjectMapper MAPPER = sharedMapper();
public void testLongStringArray() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java
deleted file mode 100644
index 71aba3253..000000000
--- a/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package com.fasterxml.jackson.databind.struct;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-import java.util.*;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.*;
-
-/**
- * Tests to verify implementation of [databind#540]; also for
- * follow up work of:
- *
- * - [databind#994]
- */
-public class EmptyArrayAsNullTest extends BaseMapTest
-{
- private final ObjectMapper MAPPER = new ObjectMapper();
- private final ObjectReader DEFAULT_READER = MAPPER.reader();
- private final ObjectReader READER_WITH_ARRAYS = DEFAULT_READER
- .with(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
-
- static class Bean {
- public String a = "foo";
- }
-
- final static String EMPTY_ARRAY = " [\n]";
-
- /*
- /**********************************************************
- /* Test methods, settings
- /**********************************************************
- */
-
- public void testSettings() {
- assertFalse(MAPPER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
- assertFalse(DEFAULT_READER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
- assertTrue(READER_WITH_ARRAYS.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
- }
-
- /*
- /**********************************************************
- /* Test methods, POJOs
- /**********************************************************
- */
-
- // [databind#540]
- public void testPOJOFromEmptyArray() throws Exception
- {
- // first, verify default settings which do not accept empty Array
- try {
- DEFAULT_READER.forType(Bean.class)
- .readValue(EMPTY_ARRAY);
- fail("Should not accept Empty Array for POJO by default");
- } catch (JsonMappingException e) {
- verifyException(e, "START_ARRAY token");
- assertValidLocation(e.getLocation());
- }
-
- // should be ok to enable dynamically:
- Bean result = READER_WITH_ARRAYS.forType(Bean.class)
- .readValue(EMPTY_ARRAY);
- assertNull(result);
- }
-
- /*
- /**********************************************************
- /* Test methods, Maps
- /**********************************************************
- */
-
- public void testMapFromEmptyArray() throws Exception
- {
- // first, verify default settings which do not accept empty Array
- try {
- DEFAULT_READER.forType(Map.class)
- .readValue(EMPTY_ARRAY);
- fail("Should not accept Empty Array for Map by default");
- } catch (JsonMappingException e) {
- verifyException(e, "START_ARRAY token");
- }
- // should be ok to enable dynamically:
- Map<?,?> result = READER_WITH_ARRAYS.forType(Map.class)
- .readValue(EMPTY_ARRAY);
- assertNull(result);
- }
-
- public void testEnumMapFromEmptyArray() throws Exception
- {
-
- EnumMap<?,?> result2 = READER_WITH_ARRAYS.forType(new TypeReference<EnumMap<ABC,String>>() { })
- .readValue(EMPTY_ARRAY);
- assertNull(result2);
- }
-
- /*
- /**********************************************************
- /* Test methods, primitives/wrappers
- /**********************************************************
- */
-
- public void testWrapperFromEmptyArray() throws Exception
- {
- _testNullWrapper(Boolean.class);
- _testNullWrapper(Byte.class);
- _testNullWrapper(Character.class);
- _testNullWrapper(Short.class);
- _testNullWrapper(Integer.class);
- _testNullWrapper(Long.class);
- _testNullWrapper(Float.class);
- _testNullWrapper(Double.class);
- }
-
- /*
- /**********************************************************
- /* Test methods, other
- /**********************************************************
- */
-
- public void testNullStringFromEmptyArray() throws Exception {
- _testNullWrapper(String.class);
- }
-
- public void testNullEnumFromEmptyArray() throws Exception {
- _testNullWrapper(ABC.class);
- }
-
- public void testStdJdkTypesFromEmptyArray() throws Exception
- {
- _testNullWrapper(BigInteger.class);
- _testNullWrapper(BigDecimal.class);
-
- _testNullWrapper(UUID.class);
-
- _testNullWrapper(Date.class);
- _testNullWrapper(Calendar.class);
- }
-
- /*
- /**********************************************************
- /* Helper methods
- /**********************************************************
- */
-
- private void _testNullWrapper(Class<?> cls) throws Exception
- {
- Object result = READER_WITH_ARRAYS.forType(cls).readValue(EMPTY_ARRAY);
- assertNull(result);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java
index a13988e90..cb06cd559 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java
@@ -80,7 +80,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
.readValue("{\"v\":[3]}");
fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled");
} catch (MismatchedInputException e) {
- verifyException(e, "Cannot deserialize instance of `int`");
+ verifyException(e, "Cannot deserialize value of type `int`");
}
ObjectReader r = UNWRAPPING_READER.forType(IntBean.class);
@@ -117,7 +117,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
noUnwrapR.readValue("{\"v\":[3]}");
fail("Did not throw exception when reading a value from a single value array");
} catch (MismatchedInputException e) {
- verifyException(e, "Cannot deserialize instance of `long`");
+ verifyException(e, "Cannot deserialize value of type `long`");
}
LongBean result = unwrapR.readValue("{\"v\":[3]}");
@@ -154,7 +154,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
noUnwrapR.readValue("{\"v\":[" + value + "]}");
fail("Did not throw exception when reading a value from a single value array");
} catch (MismatchedInputException e) {
- verifyException(e, "Cannot deserialize instance of `double`");
+ verifyException(e, "Cannot deserialize value of type `double`");
}
DoubleBean result = unwrapR.readValue("{\"v\":[" + value + "]}");
@@ -359,8 +359,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
NO_UNWRAPPING_READER.readValue("[\""+value+"\"]", String.class);
fail("Exception not thrown when attempting to unwrap a single value 'String' array into a simple String");
} catch (MismatchedInputException exp) {
- verifyException(exp, "Cannot deserialize");
- verifyException(exp, "out of START_ARRAY");
+ _verifyNoDeserFromArray(exp);
}
try {
@@ -385,8 +384,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
r.readValue("[" + value.toString() + "]");
fail("Exception was not thrown when attempting to read a single value array of BigDecimal when UNWRAP_SINGLE_VALUE_ARRAYS feature is disabled");
} catch (MismatchedInputException exp) {
- verifyException(exp, "Cannot deserialize");
- verifyException(exp, "out of START_ARRAY");
+ _verifyNoDeserFromArray(exp);
}
r = UNWRAPPING_READER.forType(BigDecimal.class);
@@ -411,8 +409,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
NO_UNWRAPPING_READER.readValue("[" + value.toString() + "]", BigInteger.class);
fail("Exception was not thrown when attempting to read a single value array of BigInteger when UNWRAP_SINGLE_VALUE_ARRAYS feature is disabled");
} catch (MismatchedInputException exp) {
- verifyException(exp, "Cannot deserialize");
- verifyException(exp, "out of START_ARRAY");
+ _verifyNoDeserFromArray(exp);
}
result = UNWRAPPING_READER.readValue("[" + value.toString() + "]", BigInteger.class);
@@ -438,7 +435,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
.readValue("[" + quote(String.class.getName()) + "]");
fail("Did not throw exception when UNWRAP_SINGLE_VALUE_ARRAYS feature was disabled and attempted to read a Class array containing one element");
} catch (MismatchedInputException e) {
- verifyException(e, "out of START_ARRAY token");
+ _verifyNoDeserFromArray(e);
}
_verifyMultiValueArrayFail("[" + quote(Object.class.getName()) + "," + quote(Object.class.getName()) +"]",
@@ -456,7 +453,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
.readValue("[\""+value.toString()+"\"]");
fail("Did not throw exception for single value array when UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
} catch (MismatchedInputException e) {
- verifyException(e, "out of START_ARRAY token");
+ _verifyNoDeserFromArray(e);
}
_verifyMultiValueArrayFail("[\""+value.toString()+"\",\""+value.toString()+"\"]", URI.class);
@@ -471,7 +468,7 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
.readValue("[" + quote(uuidStr) + "]");
fail("Exception was not thrown as expected");
} catch (MismatchedInputException e) {
- verifyException(e, "out of START_ARRAY token");
+ _verifyNoDeserFromArray(e);
}
assertEquals(uuid,
UNWRAPPING_READER.forType(UUID.class)
@@ -485,6 +482,12 @@ public class UnwrapSingleArrayScalarsTest extends BaseMapTest
/**********************************************************
*/
+ private void _verifyNoDeserFromArray(Exception e) {
+ verifyException(e, "Cannot deserialize");
+ verifyException(e, "from Array value");
+ verifyException(e, "JsonToken.START_ARRAY");
+ }
+
private void _verifyMultiValueArrayFail(String input, Class<?> type) throws IOException {
try {
UNWRAPPING_READER.forType(type).readValue(input);
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java
index def53ce11..5487a4c7f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java
@@ -280,7 +280,12 @@ public class TestJavaType
assertTrue(t.hasContentType());
assertEquals(Long.class, t.getContentType().getRawClass());
- // 26-Mar-2020, tatu: [databind#2019] suggest this should be 1...
-// assertEquals(1, t.containedTypeCount());
+ // 26-Mar-2020, tatu: [databind#2019] made this work
+ assertEquals(1, t.containedTypeCount());
+ TypeBindings bindings = t.getBindings();
+ assertEquals(1, bindings.size());
+ assertEquals(refdType, bindings.getBoundType(0));
+ // Should we even verify this or not?
+ assertEquals("V", bindings.getBoundName(0));
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
index e78c15d1a..dc326d5d1 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
@@ -160,7 +160,7 @@ public class TestTypeFactory
{
TypeFactory tf = TypeFactory.defaultInstance();
// first, simple class based
- JavaType t = tf.constructParametrizedType(ArrayList.class, Collection.class, String.class); // ArrayList<String>
+ final JavaType t = tf.constructParametrizedType(ArrayList.class, Collection.class, String.class); // ArrayList<String>
assertEquals(CollectionType.class, t.getClass());
JavaType strC = tf.constructType(String.class);
assertEquals(1, t.containedTypeCount());
@@ -176,6 +176,13 @@ public class TestTypeFactory
assertEquals(t, t2.containedType(1));
assertNull(t2.containedType(2));
+ // [databind#921]: using type bindings
+ JavaType t3 = tf.constructParametricType(HashSet.class, t.getBindings()); // HashSet<String>
+ assertEquals(CollectionType.class, t3.getClass());
+ assertEquals(1, t3.containedTypeCount());
+ assertEquals(strC, t3.containedType(0));
+ assertNull(t3.containedType(1));
+
// and then custom generic type as well
JavaType custom = tf.constructParametrizedType(SingleArgGeneric.class, SingleArgGeneric.class,
String.class);
@@ -184,10 +191,24 @@ public class TestTypeFactory
assertEquals(strC, custom.containedType(0));
assertNull(custom.containedType(1));
+ // and then custom generic type from TypeBindings ([databind#921])
+ JavaType custom2 = tf.constructParametricType(SingleArgGeneric.class, t.getBindings());
+ assertEquals(SimpleType.class, custom2.getClass());
+ assertEquals(1, custom2.containedTypeCount());
+ assertEquals(strC, custom2.containedType(0));
+ assertNull(custom2.containedType(1));
+
// should also be able to access variable name:
assertEquals("X", custom.containedTypeName(0));
+ }
- // And finally, ensure that we can't create invalid combinations
+ @SuppressWarnings("deprecation")
+ public void testInvalidParametricTypes()
+ {
+ final TypeFactory tf = TypeFactory.defaultInstance();
+ final JavaType strC = tf.constructType(String.class);
+
+ // ensure that we can't create invalid combinations
try {
// Maps must take 2 type parameters, not just one
tf.constructParametrizedType(Map.class, Map.class, strC);
@@ -202,7 +223,7 @@ public class TestTypeFactory
verifyException(e, "Cannot create TypeBindings for class ");
}
}
-
+
/**
* Test for checking that canonical name handling works ok
*/
diff --git a/src/test/java/com/fasterxml/jackson/failing/BuilderWithIgnored1214Test.java b/src/test/java/com/fasterxml/jackson/failing/BuilderWithIgnored1214Test.java
deleted file mode 100644
index 54c6d6a76..000000000
--- a/src/test/java/com/fasterxml/jackson/failing/BuilderWithIgnored1214Test.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-
-public class BuilderWithIgnored1214Test extends BaseMapTest
-{
- @JsonDeserialize(builder = TestObject1214.Builder.class)
- @JsonIgnoreProperties(ignoreUnknown = true)
- static class TestObject1214 {
- final String property1;
-
- private TestObject1214(Builder builder) {
- property1 = builder.property1;
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- public String getProperty1() {
- return property1;
- }
-
- static class Builder {
-
- private String property1;
-
- public Builder withProperty1(String p1) {
- property1 = p1;
- return this;
- }
-
- public TestObject1214 build() {
- return new TestObject1214(this);
- }
- }
- }
-
- public void testUnknown1214() throws Exception
- {
- ObjectMapper mapper = new ObjectMapper();
- TestObject1214 value = mapper.readValue(aposToQuotes
- ("{'property1':'a', 'property2':'b'}"),
- TestObject1214.class);
- assertEquals("a", value.property1);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/JDKNumberDeser2644Test.java b/src/test/java/com/fasterxml/jackson/failing/JDKNumberDeser2644Test.java
deleted file mode 100644
index d92300a35..000000000
--- a/src/test/java/com/fasterxml/jackson/failing/JDKNumberDeser2644Test.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import java.math.BigDecimal;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonSubTypes;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonUnwrapped;
-import com.fasterxml.jackson.databind.*;
-
-// Tests for
-//
-// [databind#2644]
-// [databind#2785]
-public class JDKNumberDeser2644Test extends BaseMapTest
-{
- // [databind#2644]
- static class NodeRoot2644 {
- public String type;
-
- @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
- @JsonSubTypes(value = {
- @JsonSubTypes.Type(value = NodeParent2644.class, name = "NodeParent")
- })
- public Node2644 node;
- }
-
- public static class NodeParent2644 extends Node2644 { }
-
- public static abstract class Node2644 {
- @JsonProperty("amount")
- BigDecimal val;
-
- public BigDecimal getVal() {
- return val;
- }
-
- public void setVal(BigDecimal val) {
- this.val = val;
- }
- }
-
- // [databind#2785]
- static class BigDecimalHolder2785 {
- public BigDecimal value;
- }
-
- static class NestedBigDecimalHolder2785 {
- @JsonUnwrapped
- public BigDecimalHolder2785 holder;
- }
-
- // [databind#2644]
- public void testBigDecimalSubtypes() throws Exception
- {
- ObjectMapper mapper = newJsonMapper();
-
- // NOTE: uncommenting this does work around the issue:
-// mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
- mapper.registerSubtypes(NodeParent2644.class);
-
- NodeRoot2644 root = mapper.readValue(
- "{\"type\": \"NodeParent\",\"node\": {\"amount\": 9999999999999999.99} }",
- NodeRoot2644.class
- );
-
- assertEquals(new BigDecimal("9999999999999999.99"), root.node.getVal());
- }
-
- // [databind#2785]
-
- public void testBigDecimalUnwrapped() throws Exception
- {
- final ObjectMapper mapper = newJsonMapper();
- // mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
- final String JSON = "{\"value\": 5.00}";
-
- // first simple working case:
- BigDecimalHolder2785 holder = mapper.readValue(JSON, BigDecimalHolder2785.class);
- assertEquals(new BigDecimal("5.00"), holder.value);
-
- // and then one that doesn't
- NestedBigDecimalHolder2785 result = mapper.readValue(JSON, NestedBigDecimalHolder2785.class);
- assertEquals(new BigDecimal("5.00"), result.holder.value);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java b/src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java
index 192feb4ec..a2e18f0ff 100644
--- a/src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java
+++ b/src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java
@@ -19,7 +19,7 @@ public class NumberNodes1770Test extends BaseMapTest
final JsonNode jsonNode = MAPPER.reader()
.with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.readTree("7976931348623157e309");
- assertTrue(jsonNode.isBigDecimal());
+ assertTrue("Expected DecimalNode, got: "+jsonNode.getClass().getName()+": "+jsonNode, jsonNode.isBigDecimal());
// the following fails with NumberFormatException, because jsonNode is a DoubleNode with a value of POSITIVE_INFINITY
// Assert.assertTrue(jsonNode.decimalValue().compareTo(new BigDecimal("7976931348623157e309")) == 0);
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/ParsingContextExtTypeId2747Test.java b/src/test/java/com/fasterxml/jackson/failing/ParsingContextExtTypeId2747Test.java
new file mode 100644
index 000000000..3bc900f5a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/ParsingContextExtTypeId2747Test.java
@@ -0,0 +1,65 @@
+package com.fasterxml.jackson.failing;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+public class ParsingContextExtTypeId2747Test extends BaseMapTest
+{
+ static class Wrapper {
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type",
+ include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
+ public Tag wrapped;
+
+ public String type;
+ }
+
+ @JsonSubTypes(@JsonSubTypes.Type(Location.class))
+ interface Tag {}
+
+ @JsonTypeName("location")
+ @JsonDeserialize(using = LocationDeserializer.class)
+ static class Location implements Tag
+ {
+ String value;
+
+ protected Location() { }
+ Location(String v) { value = v; }
+ }
+
+ static class LocationDeserializer extends JsonDeserializer<Location>
+ {
+ @Override
+ public Location deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
+ {
+ p.skipChildren();
+ return new Location(getCurrentLocationAsString(p));
+ }
+ }
+
+ static String getCurrentLocationAsString(JsonParser p)
+ {
+ // This suffices to give actual path
+ return p.getParsingContext().pathAsPointer().toString();
+ }
+
+ // [databind#2747]
+ public void testLocationAccessWithExtTypeId() throws Exception
+ {
+ ObjectReader objectReader = newJsonMapper().readerFor(Wrapper.class);
+
+ Wrapper wrapper = objectReader.readValue("{" +
+ "\"type\":\"location\"," +
+ "\"wrapped\": 1" +
+ "}");
+ // expecting wrapper.wrapped.value == "wrapped" but is "wrapped[1]"
+ // due to way `ExternalTypeHandler` exposes value as if "wrapper-array" was used for
+ // type id, value
+ assertEquals("/wrapped", ((Location) wrapper.wrapped).value);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestUnknownProperty426.java b/src/test/java/com/fasterxml/jackson/failing/TestUnknownProperty426.java
deleted file mode 100644
index 09b3226b7..000000000
--- a/src/test/java/com/fasterxml/jackson/failing/TestUnknownProperty426.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.annotation.*;
-
-import com.fasterxml.jackson.databind.*;
-
-/**
- * Unit tests for checking handling of unknown properties
- */
-public class TestUnknownProperty426 extends BaseMapTest
-{
- // For [databind#426]
- @JsonIgnoreProperties({ "userId" })
- static class User {
- public String firstName;
- Integer userId;
-
- public void setUserId(CharSequence id) {
- // 21-Dec-2015, tatu: With a fix in 2.7, use of String would not
- // trigger the problem, so use CharSequence...
- setUserId(Integer.valueOf(id.toString()));
- }
-
- public Integer getUserId() {
- return userId;
- }
-
- public void setUserId(Integer v) {
- this.userId = v;
- }
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- private final ObjectMapper MAPPER = new ObjectMapper();
-
- // Aside from [databind#426], also relevant: [databind#1044]?
- public void testIssue426() throws Exception
- {
- final String JSON = aposToQuotes("{'userId': 9, 'firstName': 'Mike' }");
- User result = MAPPER.readerFor(User.class).readValue(JSON);
- assertNotNull(result);
- assertEquals("Mike", result.firstName);
- }
-}
-
diff --git a/src/test/java/com/fasterxml/jackson/failing/VoidProperties2675Test.java b/src/test/java/com/fasterxml/jackson/failing/VoidProperties2675Test.java
deleted file mode 100644
index c7727eb48..000000000
--- a/src/test/java/com/fasterxml/jackson/failing/VoidProperties2675Test.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.databind.*;
-
-public class VoidProperties2675Test extends BaseMapTest
-{
- static class VoidBean {
- public Void getValue() { return null; }
- }
-
- /*
- /**********************************************************************
- /* Test methods
- /**********************************************************************
- */
-
- private final ObjectMapper MAPPER = new ObjectMapper();
-
- public void testVoidBean() throws Exception {
- final String EXP = "{\"value\":null}";
- assertEquals(EXP, MAPPER.writeValueAsString(new VoidBean()));
- VoidBean result = MAPPER.readValue(EXP, VoidBean.class);
- assertNotNull(result);
- }
-}