Merge branch '2.8' into 2.9
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 0874104..8451799 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,6 +1,7 @@
We appreciate issues as very valuable contributions, but just to make sure here are things that are important to do before filing an issue:
-* Only report issues, and ask Usage Questions on [Jackson-users](https://groups.google.com/forum/#!search/jackson-users) list -- ypu are more likely to get help that way
+* Only report issues (and perhaps request new features, FEATURE):,Usage Questions should be asked on [Jackson-users](https://groups.google.com/forum/#!search/jackson-users) list -- you are more likely to get help that way (and we will promptly close questions-as-issues)
* Check to see if this issue has already been reported (quick glance at existing issues): no deep search necessary, just quick sanity check
* Include version information for Jackson version you use
* (optional but highly recommended) Verify that the problem occurs with the latest patch of same minor version; and even better, if possible, try using the latest stable patch version
+ * For example: if you observe an issue with version `2.4.1`, first upgrade to `2.4.6` to ensure problem has not already been fixed.
diff --git a/.gitignore b/.gitignore
index 1271c27..97155eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
# use glob syntax.
syntax: glob
+
*.class
*~
*.bak
diff --git a/.travis.yml b/.travis.yml
index bdd126d..15025d8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,19 +1,21 @@
language: java
-# While running only requires JDK 6, compilation JDK 7, Travis has neither
+# JDK 7 no longer works, just use JDK 8
jdk:
- openjdk8
# Below this line is configuration for deploying to the Sonatype OSS repo
# http://blog.xeiam.com/2013/05/configure-travis-ci-to-deploy-snapshots.html
before_install: "git clone -b travis `git config --get remote.origin.url` target/travis"
-after_success: "mvn source:jar javadoc:jar deploy --settings target/travis/settings.xml"
+after_success:
+ - "mvn source:jar javadoc:jar deploy --settings target/travis/settings.xml"
+ - "mvn -B cobertura:cobertura coveralls:report"
# whitelist
branches:
only:
- master
- - "2.8"
+ - "2.9"
env:
global:
diff --git a/README.md b/README.md
index 0bff2e5..7076dd2 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@
<properties>
...
<!-- Use the latest version whenever possible. -->
- <jackson.version>2.7.0</jackson.version>
+ <jackson.version>2.9.0</jackson.version>
...
</properties>
diff --git a/pom.xml b/pom.xml
index ab72fc4..5267fe1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,13 +4,13 @@
<parent>
<groupId>com.fasterxml.jackson</groupId>
- <artifactId>jackson-parent</artifactId>
- <version>2.8</version>
+ <artifactId>jackson-base</artifactId>
+ <version>2.9.10.20200411</version>
</parent>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
- <version>2.8.11.7-SNAPSHOT</version>
+ <version>2.9.10.5-SNAPSHOT</version>
<name>jackson-databind</name>
<packaging>bundle</packaging>
<description>General data-binding functionality for Jackson: works on core streaming API</description>
@@ -25,20 +25,28 @@
</scm>
<properties>
- <!-- With Jackson 2.8 we will require JDK 7 (except for annotations/streaming),
+ <!-- With Jackson 2.9 we will require JDK 7 (except for annotations/streaming),
and new language features (diamond pattern) may be used.
- Whether JDK dependencies are directly used is an open question as of Feb-2016
+ JDK classes are still loaded dynamically since there isn't much downside
+ (small number of types); this allows use on JDK 6 platforms still (including
+ Android)
-->
<javac.src.version>1.7</javac.src.version>
<javac.target.version>1.7</javac.target.version>
<!-- Can not use default, since group id != Java package name here -->
<osgi.export>com.fasterxml.jackson.databind.*;version=${project.version}</osgi.export>
- <!-- but imports should work fine with defaults -->
+ <osgi.import> <!-- fix for databind#2299: using jackson-databind in an OSGi environment under Android -->
+ org.w3c.dom.bootstrap;resolution:=optional,
+ *
+ </osgi.import>
<!-- Generate PackageVersion.java into this directory. -->
<packageVersion.dir>com/fasterxml/jackson/databind/cfg</packageVersion.dir>
<packageVersion.package>com.fasterxml.jackson.databind.cfg</packageVersion.package>
+
+ <!-- since 2.9.1: NOTE! can not use packageVersion.package as is -->
+ <jdk.module.name>com.fasterxml.jackson.databind</jdk.module.name>
</properties>
<dependencies>
@@ -46,11 +54,16 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
+ <!-- 06-Mar-2017, tatu: Although bom provides for dependencies, some legacy
+ usage seems to benefit from actually specifying version here in case
+ it is dependent on transitively
+ -->
+ <version>${jackson.version.annotations}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
- <version>2.8.10</version>
+ <version>${jackson.version.core}</version>
</dependency>
<!-- and for testing we need a few libraries
@@ -58,20 +71,15 @@
-->
<dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
- <version>1.6.5</version>
+ <version>1.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
- <version>1.6.5</version>
+ <version>1.7.4</version>
<scope>test</scope>
</dependency>
<!-- For testing TestNoClassDefFoundDeserializer -->
@@ -81,16 +89,34 @@
<version>1.0.0</version>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>com.google.jimfs</groupId>
- <artifactId>jimfs</artifactId>
- <version>1.1</version>
- <scope>test</scope>
- </dependency>
</dependencies>
+ <!-- Alas, need to include snapshot reference since otherwise can not find
+ snapshot of parent... -->
+ <repositories>
+ <repository>
+ <id>sonatype-nexus-snapshots</id>
+ <name>Sonatype Nexus Snapshots</name>
+ <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
<build>
<plugins>
+ <!-- Important: enable enforcer plug-in: -->
+ <plugin>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <executions> <!-- or? combine.children="merge"> -->
+ <execution>
+ <id>enforce-properties</id>
+ <phase>validate</phase>
+ <goals><goal>enforce</goal></goals>
+ </execution>
+ </executions>
+ </plugin>
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<version>${version.plugin.surefire}</version>
@@ -105,35 +131,29 @@
</configuration>
</plugin>
+ <!-- parent definitions should be ok, but need to add more links -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
- <version>${version.plugin.javadoc}</version>
<configuration>
-<!-- Only works on Java 8:
- <additionalparam>-Xdoclint:none</additionalparam>
--->
-<!-- so with Java 7, use this: -->
- <failOnError>false</failOnError>
- <links>
- <link>http://docs.oracle.com/javase/7/docs/api/</link>
- <link>http://fasterxml.github.com/jackson-annotations/javadoc/2.7</link>
- <link>http://fasterxml.github.com/jackson-core/javadoc/2.7</link>
+ <links combine.children="append">
+ <link>http://fasterxml.github.com/jackson-annotations/javadoc/2.9</link>
+ <link>http://fasterxml.github.com/jackson-core/javadoc/2.9</link>
</links>
</configuration>
</plugin>
- <!-- May want to configure debug info -->
+ <!-- settings are fine, but needed to trigger execution! -->
<plugin>
- <!-- Inherited from oss-base. Generate PackageVersion.java.-->
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
- <executions>
- <execution>
- <id>process-packageVersion</id>
- <phase>process-sources</phase>
- </execution>
- </executions>
+ </plugin>
+
+ <!-- 18-Oct-2016, tatu: Try to make coveralls work -->
+ <plugin>
+ <groupId>org.eluder.coveralls</groupId>
+ <artifactId>coveralls-maven-plugin</artifactId>
+ <version>4.3.0</version>
</plugin>
</plugins>
</build>
diff --git a/release-notes/CREDITS b/release-notes/CREDITS-2.x
similarity index 73%
rename from release-notes/CREDITS
rename to release-notes/CREDITS-2.x
index bd809e3..5e92884 100644
--- a/release-notes/CREDITS
+++ b/release-notes/CREDITS-2.x
@@ -452,6 +452,8 @@
(2.7.4)
* Reported #1231: `@JsonSerialize(as=superType)` behavior disallowed in 2.7.4
(2.7.5)
+ * Suggested #507: Support for default `@JsonView` for a class
+ (2.9.0)
Tom Mack (tommack@github)
* Reported #1208: treeToValue doesn't handle POJONodes that contain exactly
@@ -466,6 +468,8 @@
Nick Babcock (nickbabcock)
* Reported #1225: `JsonMappingException` should override getProcessor()
(2.7.5)
+ * Suggested #1356: Differentiate between input and code exceptions on deserialization
+ (2.9.0)
Andrew Joseph (apjoseph@github)
* Reported #1248: `Annotated` returns raw type in place of Generic Type in 2.7.x
@@ -632,11 +636,22 @@
(2.8.9)
* Reported #1651: `StdDateFormat` fails to parse 'zulu' date when TimeZone other than UTC
(2.8.9)
+ * Suggested #1745: StdDateFormat: accept and truncate millis larger than 3 digits
+ (2.9.1)
+ * Contributed #1749: StdDateFormat: performance improvement of '_format(..)' method
+ (2.9.1)
+ * Contributed #1759: Reuse `Calendar` instance during parsing by `StdDateFormat`
+ (2.9.1)
Kevin Gallardo (newkek@github)
* Reported #1658: Infinite recursion when deserializing a class extending a Map,
with a recursive value type
(2.8.10)
+ * Reported #1729: Integer bounds verification when calling `TokenBuffer.getIntValue()`
+ (2.9.4)
+
+Lukas Euler
+ * Reported #1735: Missing type checks when using polymorphic type ids
Guixiong Wu (吴桂雄)
* Reported #2032: Blacklist another serialization gadget (ibatis)
@@ -646,10 +661,214 @@
* Reported #2109, suggested fix: Canonical string for reference type is built incorrectly
(2.8.11.3 / 2.9.7)
-Kaki King (kingkk9279@g)
- * Reported #2449: Block one more gadget type (cve CVE-2019-14540)
- (2.9.10)
-
Connor Kuhn (ckuhn@github)
* Contributed #1341: FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY
(2.9.0)
+
+Jan Lolling (jlolling@github)
+ * Contributed #1319: Add `ObjectNode.put(String, BigInteger)`
+ (2.9.0)
+
+Michael R Fairhurst (MichaelRFairhurst@github)
+ * Reported #1035: `@JsonAnySetter` assumes key of `String`, does not consider declared type.
+ (2.9.0)
+
+Fabrizio Cucci (fabriziocucci@github)
+ * Reported #1406: `ObjectMapper.readTree()` methods do not return `null` on end-of-input
+ (2.9.0)
+
+Emiliano Clariá (emilianogc@github)
+ * Contributed #1434: Explicitly pass null on invoke calls with no arguments
+ (2.9.0)
+
+Ana Eliza Barbosa (AnaEliza@github)
+ * Contributed #1520: Case insensitive enum deserialization feature.
+ (2.9.0)
+
+Lyor Goldstein (lgoldstein@github)
+ * Reported #1544: `EnumMapDeserializer` assumes a pure `EnumMap` and does not support
+ derived classes
+ (2.9.0)
+
+Harleen Sahni (harleensahni@github)
+ * Reported #403: Make FAIL_ON_NULL_FOR_PRIMITIVES apply to primitive arrays and other
+ types that wrap primitives
+ (2.9.0)
+
+Jared Jacobs (2is10@github)
+ * Requested #1605: Allow serialization of `InetAddress` as simple numeric host address
+ (2.9.0)
+
+Patrick Gunia (pgunia@github)
+ * Reported #1440: Wrong `JsonStreamContext` in `DeserializationProblemHandler` when reading
+ `TokenBuffer` content
+ (2.9.0)
+
+Carsten Wickner (CarstenWickner@github)
+ * Contributed #1522: Global `@JsonInclude(Include.NON_NULL)` for all properties with a specific type
+ (2.9.0)
+
+Chris Plummer (strmer15@github)
+ * Reported #1637: `ObjectReader.at()` with `JsonPointer` stops after first collection
+ (2.9.0)
+
+Christian Basler (Dissem@github)
+ * Reported #1688: Deserialization fails for `java.nio.file.Path` implementations when
+ default typing enabled
+ (2.9.0)
+
+Tim Bartley (tbartley@github)
+ * Reported, suggested fix for #1705: Non-generic interface method hides type resolution info
+ from generic base class
+ (2.9.1)
+
+Luís Cleto (luiscleto@github)
+ * Suggested 1768: Improve `TypeFactory.constructFromCanonical()` to work with
+ `java.lang.reflect.Type.getTypeName()` format
+ (2.9.2)
+
+Vincent Demay (vdemay@github)
+ * Reported #1793: `java.lang.NullPointerException` in `ObjectArraySerializer.acceptJsonFormatVisitor()`
+ for array value with `@JsonValue`
+ (2.9.2)
+
+Peter Jurkovic (peterjurkovic@github)
+ * Reported #1823: ClassNameIdResolver doesn't handle resolve Collections$SingletonMap,
+ Collections$SingletonSet
+ (2.9.3)
+
+alinakovalenko@github:
+ * Reported #1844: Map "deep" merge only adds new items, but not override existing values
+ (2.9.3)
+
+Pier-Luc Whissell (pwhissell@github):
+ * Reported #1673: Serialising generic value classes via Reference Types (like Optional) fails
+ to include type information
+ (2.9.4)
+
+Alexander Skvortcov (askvortcov@github)
+ * Reported #1853: Deserialise from Object (using Creator methods) returns field name
+ instead of value
+ (2.9.4)
+
+Joe Schafer (jschaf@github)
+ * Reported #1906: Add string format specifier for error message in `PropertyValueBuffer`
+ (2.9.4)
+ * Reported #1907: Remove `getClass()` from `_valueType` argument for error reporting
+ (2.9.4)
+
+Deblock Thomas (deblockt@github)
+ * Reported, contributed fix for #1912: `BeanDeserializerModifier.updateBuilder()` does not
+ work to set custom deserializer on a property (since 2.9.0)
+ (contributed by Deblock T)
+
+lilei@venusgroup.com.cn:
+ * Reported #1931: Two more `c3p0` gadgets to exploit default typing issue
+ (2.9.5)
+
+Aniruddha Maru (maroux@github)
+ * Reported #1940: `Float` values with integer value beyond `int` lose precision if
+ bound to `long`
+ (2.9.5)
+
+Timur Shakurov (saladinkzn@github)
+ * Reported #1947: `MapperFeature.AUTO_DETECT_XXX` do not work if all disabled
+ (2.9.5)
+
+roeltje25@github
+ * Reported #1978: Using @JsonUnwrapped annotation in builderdeserializer hangs in
+ infinite loop
+ (2.9.5)
+
+Freddy Boucher (freddyboucher@github)
+ * Reported #1990: MixIn `@JsonProperty` for `Object.hashCode()` is ignored
+ (2.9.6)
+
+Ondrej Zizka (OndraZizk@github)
+ * Reported #1999: "Duplicate property" issue should mention which class it complains about
+ (2.9.6)
+
+Jakub Skierbiszewski (jskierbi@github)
+ * Reported, contributed fix for #2001: Deserialization issue with `@JsonIgnore` and
+ `@JsonCreator` + `@JsonProperty` for same property name
+ (2.9.6)
+
+Carter Kozak (cakofony@github)
+ * Reported #2016: Delegating JsonCreator disregards JsonDeserialize info
+ (2.9.6)
+
+Reinhard Prechtl (dnno@github)
+ * Reported #2034: Serialization problem with type specialization of nested generic types
+ (2.9.6)
+
+Chetan Narsude (243826@github)
+ * Reported #2038: JDK Serializing and using Deserialized `ObjectMapper` loses linkage
+ back from `JsonParser.getCodec()`
+ (2.9.6)
+
+Petar Tahchiev (ptahchiev@github)
+ * Reported #2060: `UnwrappingBeanPropertyWriter` incorrectly assumes the found
+ serializer is of type `UnwrappingBeanSerializer`
+ (2.9.6)
+
+Brandon Krieger (bkrieger@github)
+ * Reported #2064: Cannot set custom format for `SqlDateSerializer` globally
+ (2.9.7)
+
+Semyon Levin (remal@github)
+ * Contributed #2120: `NioPathDeserializer` improvement
+ (2.9.7)
+
+Pavel Nikitin (morj@github)
+ * Requested #2181: Don't re-use dynamic serializers for property-updating copy constructors
+ (2.9.8)
+
+Thomas Krieger (ThomasKrieger@github)
+ * Reported #1408: Call to `TypeVariable.getBounds()` without synchronization unsafe on
+ some platforms
+ (2.9.9)
+
+René Kschamer (flawi@github)
+ * Reported #2197: Illegal reflective access operation warning when using `java.lang.Void`
+ as value type
+ (2.9.8)
+
+Joffrey Bion (joffrey-bion@github)
+ * Reported #2265: Inconsistent handling of Collections$UnmodifiableList vs
+ Collections$UnmodifiableRandomAccessList
+ (2.9.9)
+
+Christoph (cfiehe@github.com)
+ * Contributed #2299: Fix for using jackson-databind in an OSGi environment under Android
+ (2.9.9)
+
+Cyril Martin (mcoolive@github.com)
+ * Reported #2303: Deserialize null, when java type is "TypeRef of TypeRef of T",
+ does not provide "Type(Type(null))"
+ (2.9.9)
+
+Daniil Barvitsky (dbarvitsky@github)
+ * Reported #2324: `StringCollectionDeserializer` fails with custom collection
+ (2.9.9)
+
+Edgar Asatryan (nstdio@github)
+ * Reported #2374: `ObjectMapper. getRegisteredModuleIds()` throws NPE if no modules registered
+ (2.9.9.1)
+
+Michael Simons (michael-simons@github)
+ * Reported #2395: `NullPointerException` from `ResolvedRecursiveType` (regression due to
+ fix for #2331)
+ (2.9.9.3)
+
+Joe Barnett (josephlbarnett@github)
+ * Reported, contributed fix for #2404: FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY setting
+ ignored when creator properties are buffered
+ (2.9.10)
+
+Kaki King (kingkk9279@g)
+ * Reported #2449: Block one more gadget type (cve CVE-2019-14540)
+ (2.9.10)
+
+Jon Anderson (Jon901@github)
+ * Reported #2544: java.lang.NoClassDefFoundError Thrown for compact profile1
+ (2.9.10.2)
diff --git a/release-notes/VERSION b/release-notes/VERSION-2.x
similarity index 78%
rename from release-notes/VERSION
rename to release-notes/VERSION-2.x
index 5f8a75b..dff1317 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION-2.x
@@ -1,35 +1,414 @@
Project: jackson-databind
+
------------------------------------------------------------------------
-=== Releases ===
+=== Releases ===
------------------------------------------------------------------------
-2.8.11.6 (10-Mar-2020)
+2.9.10.5 (not yet released)
+
+#2688: Block one more gadget type (apache-drill)
+ (reported by Topsec(tcc))
+#2698: Block one more gadget type (weblogic/oracle-aqjms)
+ (reported by Fangrun Li)
+
+2.9.10.4 (11-Apr-2020)
#2631: Block one more gadget type (shaded-hikari-config, CVE-2020-9546)
(reported by threedr3am & LFY)
#2634: Block two more gadget types (ibatis-sqlmap, anteros-core; CVE-2020-9547 / CVE-2020-9548)
(reported by threedr3am & V1ZkRA)
-#2642: Block one more gadget type (javax.swing, CVE-to-be-allocated)
+#2642: Block one more gadget type (javax.swing, CVE-2020-10969)
(reported by threedr3am)
-#2648: Block one more gadget type (shiro-core, CVE-to-be-allocated)
+#2648: Block one more gadget type (shiro-core)
+#2653: Block one more gadget type (shiro-core)
+#2658: Block one more gadget type (ignite-jta, CVE-2020-10650)
+ (reported by Srikanth Ramu, threedr3am'follower)
+#2659: Block one more gadget type (aries.transaction.jms, CVE-2020-10672)
+ (reported by Srikanth Ramu)
+#2660: Block one more gadget type (caucho-quercus, CVE-2020-10673)
+ (reported by threedr3am'follower)
+#2662: Block one more gadget type (bus-proxy, CVE-2020-10968)
+ (reported by XuYuanzhen)
+#2664: Block one more gadget type (activemq-pool[-jms], CVE-2020-11111)
+ (reported by Srikanth Ramu)
+#2666: Block one more gadget type (apache/commons-proxy, CVE-2020-11112)
+ (reported by Yiting Fan)
+#2670: Block one more gadget type (openjpa, CVE-2020-11113)
+ (reported by XuYuanzhen)
+#2680: Block one more gadget type (SSRF, spring-aop, CVE-2020-11619)
+#2682: Block one more gadget type (commons-jelly, CVE-2020-11620)
-2.8.11.5 (10-Feb-2020)
+2.9.10.3 (23-Feb-2020)
+#2620: Block one more gadget type (xbean-reflect/JNDI - CVE-2020-8840)
+ (reported by threedr3am@github)
+
+2.9.10.2 (03-Jan-2020)
+
+#2526: Block two more gadget types (ehcache/JNDI - CVE-2019-20330)
+ (repoerted by UltramanGaia)
+#2544: java.lang.NoClassDefFoundError Thrown for compact profile1
+ (reported by Jon A)
+
+2.9.10.1 (20-Oct-2019)
+
+#2478: Block two more gadget types (commons-dbcp, p6spy,
+ CVE-2019-16942 / CVE-2019-16943)
+ (reported by b5mali4 / root@codersec.net)
+#2498: Block one more gadget type (log4j-extras/1.2, CVE-2019-17531)
+
+2.9.10 (21-Sep-2019)
+
+#2331: `JsonMappingException` through nested getter with generic wildcard return type
+#2334: Block one more gadget type (CVE-2019-12384)
+#2341: Block one more gadget type (CVE-2019-12814)
+#2374: `ObjectMapper. getRegisteredModuleIds()` throws NPE if no modules registered
+#2387: Block yet another deserialization gadget (CVE-2019-14379)
+#2389: Block yet another deserialization gadget (CVE-2019-14439)
+ (reported by xiexq)
+#2404: FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY setting ignored when
+ creator properties are buffered
+ (contributed by Joe B)
#2410: Block one more gadget type (CVE-2019-14540)
(reported by iSafeBlue@github / blue@ixsec.org)
#2420: Block one more gadget type (no CVE allocated yet)
(reported by crazylirui@gmail.com)
-#2449: Block one more gadget type (cve CVE-2019-14540)
+#2449: Block one more gadget type (CVE-2019-14540)
(reported by kingkk)
#2460: Block one more gadget type (ehcache, CVE-2019-17267)
+ (reported by Fei Lu)
#2462: Block two more gadget types (commons-configuration)
#2469: Block one more gadget type (xalan2)
-#2478: Block two more gadget types (commons-dbcp, p6spy, CVE-2019-16942 / CVE-2019-16943)
-#2498: Block one more gadget type (log4j-extras/1.2, CVE-2019-17531)
-#2526: Block two more gadget types (ehcache/JNDI, CVE-2019-20330)
- (reported by UltramanGaia)
-#2620: Block one more gadget type (xbean-reflect/JNDI - CVE-2020-8840)
- (reported by threedr3am)
+
+2.9.9 (16-May-2019)
+
+#1408: Call to `TypeVariable.getBounds()` without synchronization unsafe on some platforms
+ (reported by Thomas K)
+#2221: `DeserializationProblemHandler.handleUnknownTypeId()` returning `Void.class`,
+ enableDefaultTyping causing NPE
+ (reported by MeyerNils@github)
+#2251: Getter that returns an abstract collection breaks a delegating `@JsonCreator`
+#2265: Inconsistent handling of Collections$UnmodifiableList vs Collections$UnmodifiableRandomAccessList
+ (reported by Joffrey B)
+#2299: Fix for using jackson-databind in an OSGi environment under Android
+ (contributed by Christoph F)
+#2303: Deserialize null, when java type is "TypeRef of TypeRef of T", does not provide "Type(Type(null))"
+ (reported by Cyril M)
+#2324: `StringCollectionDeserializer` fails with custom collection
+ (reported byb Daniil B)
+#2326: Block one more gadget type (CVE-2019-12086)
+- Prevent String coercion of `null` in `WritableObjectId` when calling `JsonGenerator.writeObjectId()`,
+ mostly relevant for formats like YAML that have native Object Ids
+
+2.9.8 (15-Dec-2018)
+
+#1662: `ByteBuffer` serialization is broken if offset is not 0
+ (reported by j-baker@github)
+#2155: Type parameters are checked for equality while isAssignableFrom expected
+ (reported by frankfiedler@github)
+#2167: Large ISO-8601 Dates are formatted/serialized incorrectly
+#2181: Don't re-use dynamic serializers for property-updating copy constructors
+ (suggested by Pavel N)
+#2183: Base64 JsonMappingException: Unexpected end-of-input
+ (reported by ViToni@github)
+#2186: Block more classes from polymorphic deserialization (CVE-2018-19360,
+ CVE-2018-19361, CVE-2018-19362)
+ (reported by Guixiong Wu)
+#2197: Illegal reflective access operation warning when using `java.lang.Void`
+ as value type
+ (reported by René K)
+#2202: StdKeyDeserializer Class method _getToStringResolver is slow causing Thread Block
+ (reported by sushobhitrajan@github)
+
+2.9.7 (19-Sep-2018)
+
+#2060: `UnwrappingBeanPropertyWriter` incorrectly assumes the found serializer is
+ of type `UnwrappingBeanSerializer`
+ (reported by Petar T)
+#2064: Cannot set custom format for `SqlDateSerializer` globally
+ (reported by Brandon K)
+#2079: NPE when visiting StaticListSerializerBase
+ (reported by WorldSEnder@github)
+#2082: `FactoryBasedEnumDeserializer` should be cachable
+#2088: `@JsonUnwrapped` fields are skipped when using `PropertyBasedCreator` if
+ they appear after the last creator property
+ (reported, fix contributed by 6bangs@github)
+#2096: `TreeTraversingParser` does not take base64 variant into account
+ (reported by tangiel@github)
+#2097: Block more classes from polymorphic deserialization (CVE-2018-14718
+ - CVE-2018-14721)
+#2109: Canonical string for reference type is built incorrectly
+ (reported by svarzee@github)
+#2120: `NioPathDeserializer` improvement
+ (contributed by Semyon L)
+#2128: Location information included twice for some `JsonMappingException`s
+
+2.9.6 (12-Jun-2018)
+
+#955: Add `MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL` to use declared base type
+ as `defaultImpl` for polymorphic deserialization
+ (contributed by mikeldpl@github)
+#1328: External property polymorphic deserialization does not work with enums
+#1565: Deserialization failure with Polymorphism using JsonTypeInfo `defaultImpl`,
+ subtype as target
+#1964: Failed to specialize `Map` type during serialization where key type
+ incompatibility overidden via "raw" types
+ (reported by ptirador@github)
+#1990: MixIn `@JsonProperty` for `Object.hashCode()` is ignored
+ (reported by Freddy B)
+#1991: Context attributes are not passed/available to custom serializer if object is in POJO
+ (reported by dletin@github)
+#1998: Removing "type" attribute with Mixin not taken in account if
+ using ObjectMapper.copy()
+ (reported by SBKila@github)
+#1999: "Duplicate property" issue should mention which class it complains about
+ (reported by Ondrej Z)
+#2001: Deserialization issue with `@JsonIgnore` and `@JsonCreator` + `@JsonProperty`
+ for same property name
+ (reported, fix contributed by Jakub S)
+#2015: `@Jsonsetter with Nulls.SKIP` collides with
+ `DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL` when parsing enum
+ (reported by ndori@github)
+#2016: Delegating JsonCreator disregards JsonDeserialize info
+ (reported by Carter K)
+#2019: Abstract Type mapping in 2.9 fails when multiple modules are registered
+ (reported by asger82@github)
+#2021: Delegating JsonCreator disregards `JsonDeserialize.using` annotation
+#2023: `JsonFormat.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT` not working
+ with `null` coercion with `@JsonSetter`
+#2027: Concurrency error causes `IllegalStateException` on `BeanPropertyMap`
+ (reported by franboragina@github)
+#2032: CVE-2018-11307: Potential information exfiltration with default typing, serialization gadget from MyBatis
+ (reported by Guixiong Wu)
+#2034: Serialization problem with type specialization of nested generic types
+ (reported by Reinhard P)
+#2038: JDK Serializing and using Deserialized `ObjectMapper` loses linkage
+ back from `JsonParser.getCodec()`
+ (reported by Chetan N)
+#2051: Implicit constructor property names are not renamed properly with
+ `PropertyNamingStrategy`
+#2052: CVE-2018-12022: Block polymorphic deserialization of types from Jodd-db library
+ (reported by Guixiong Wu)
+#2058: CVE-2018-12023: Block polymorphic deserialization of types from Oracle JDBC driver
+ (reported by Guixiong Wu)
+
+2.9.5 (26-Mar-2018)
+
+#1911: Allow serialization of `BigDecimal` as String, using
+ `@JsonFormat(shape=Shape.String)`, config overrides
+ (suggested by cen1@github)
+#1912: `BeanDeserializerModifier.updateBuilder()` not work to set custom
+ deserializer on a property (since 2.9.0)
+ (contributed by Deblock T)
+#1931: Two more `c3p0` gadgets to exploit default typing issue
+ (reported by lilei@venusgroup.com.cn)
+#1932: `EnumMap` cannot deserialize with type inclusion as property
+#1940: `Float` values with integer value beyond `int` lose precision if
+ bound to `long`
+ (reported by Aniruddha M)
+#1941: `TypeFactory.constructFromCanonical()` throws NPE for Unparameterized
+ generic canonical strings
+ (reported by ayushgp@github)
+#1947: `MapperFeature.AUTO_DETECT_XXX` do not work if all disabled
+ (reported by Timur S)
+#1977: Serializing an Iterator with multiple sub-types fails after upgrading to 2.9.x
+ (reported by ssivanand@github)
+#1978: Using @JsonUnwrapped annotation in builderdeserializer hangs in infinite loop
+ (reported by roeltje25@github)
+
+2.9.4 (24-Jan-2018)
+
+#1382: `@JsonProperty(access=READ_ONLY)` unxepected behaviour with `Collections`
+ (reported by hexfaker@github)
+#1673: Serialising generic value classes via Reference Types (like Optional) fails
+ to include type information
+ (reported by Pier-Luc W)
+#1729: Integer bounds verification when calling `TokenBuffer.getIntValue()`
+ (reported by Kevin G)
+#1853: Deserialise from Object (using Creator methods) returns field name instead of value
+ (reported by Alexander S)
+#1854: NPE deserializing collection with `@JsonCreator` and `ACCEPT_CASE_INSENSITIVE_PROPERTIES`
+ (reported by rue-jw@github)
+#1855: Blacklist for more serialization gadgets (dbcp/tomcat, spring, CVE-2017-17485)
+#1859: Issue handling unknown/unmapped Enum keys
+ (reported by remya11@github)
+#1868: Class name handling for JDK unmodifiable Collection types changed
+ (reported by Rob W)
+#1870: Remove `final` on inherited methods in `BuilderBasedDeserializer` to allow
+ overriding by subclasses
+ (requested by Ville K)
+#1878: `@JsonBackReference` property is always ignored when deserializing since 2.9.0
+ (reported by reda-alaoui@github)
+#1895: Per-type config override "JsonFormat.Shape.OBJECT" for Map.Entry not working
+ (reported by mcortella@github)
+#1899: Another two gadgets to exploit default typing issue in jackson-databind
+ (reported by OneSourceCat@github)
+#1906: Add string format specifier for error message in `PropertyValueBuffer`
+ (reported by Joe S)
+#1907: Remove `getClass()` from `_valueType` argument for error reporting
+ (reported by Joe S)
+
+2.9.3 (09-Dec-2017)
+
+#1604: Nested type arguments doesn't work with polymorphic types
+#1794: `StackTraceElementDeserializer` not working if field visibility changed
+ (reported by dsingley@github)
+#1799: Allow creation of custom sub-types of `NullNode`, `BooleanNode`, `MissingNode`
+#1804: `ValueInstantiator.canInstantiate()` ignores `canCreateUsingArrayDelegate()`
+ (reported byb henryptung@github)
+#1807: Jackson-databind caches plain map deserializer and use it even map has `@JsonDeserializer`
+ (reported by lexas2509@github)
+#1823: ClassNameIdResolver doesn't handle resolve Collections$SingletonMap & Collections$SingletonSet
+ (reported by Peter J)
+#1831: `ObjectReader.readValue(JsonNode)` does not work correctly with polymorphic types,
+ value to update
+ (reported by basmastr@github)
+#1835: ValueInjector break from 2.8.x to 2.9.x
+ (repoted by kinigitbyday@github)
+#1842: `null` String for `Exception`s deserialized as String "null" instead of `null`
+ (reported by ZeleniJure@github)
+#1843: Include name of unsettable property in exception from `SetterlessProperty.set()`
+ (suggested by andreh7@github)
+#1844: Map "deep" merge only adds new items, but not override existing values
+ (reported by alinakovalenko@github)
+
+2.9.2 (14-Oct-2017)
+
+(possibly) #1756: Deserialization error with custom `AnnotationIntrospector`
+ (reported by Daniel N)
+#1705: Non-generic interface method hides type resolution info from generic base class
+ (reported by Tim B)
+ NOTE: was originally reported fixed in 2.9.1 -- turns out it wasn't.
+#1767: Allow `DeserializationProblemHandler` to respond to primitive types
+ (reported by nhtzr@github)
+#1768: Improve `TypeFactory.constructFromCanonical()` to work with
+ `java.lang.reflect.Type.getTypeName()' format
+ (suggested by Luís C)
+#1771: Pass missing argument for string formatting in `ObjectMapper`
+ (reported by Nils B)
+#1788: `StdDateFormat._parseAsISO8601()` does not parse "fractional" timezone correctly
+#1793: `java.lang.NullPointerException` in `ObjectArraySerializer.acceptJsonFormatVisitor()`
+ for array value with `@JsonValue`
+ (reported by Vincent D)
+
+2.9.1 (07-Sep-2017)
+
+#1725: `NPE` In `TypeFactory. constructParametricType(...)`
+ (reported by ctytgat@github)
+#1730: InvalidFormatException` for `JsonToken.VALUE_EMBEDDED_OBJECT`
+ (reported by zigzago@github)
+#1744: StdDateFormat: add option to serialize timezone offset with a colon
+ (contributed by Bertrand R)
+#1745: StdDateFormat: accept and truncate millis larger than 3 digits
+ (suggested by Bertrand R)
+#1749: StdDateFormat: performance improvement of '_format(..)' method
+ (contributed by Bertrand R)
+#1759: Reuse `Calendar` instance during parsing by `StdDateFormat`
+ (contributed by Bertrand R)
+- Fix `DelegatingDeserializer` constructor to pass `handledType()` (and
+ not type of deserializer being delegated to!)
+- Add `Automatic-Module-Name` ("com.fasterxml.jackson.databind") for JDK 9 module system
+
+2.9.0 (30-Jul-2017)
+
+#219: SqlDateSerializer does not obey SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS
+ (reported by BrentDouglas@github)
+#265: Add descriptive exception for attempts to use `@JsonWrapped` via Creator parameter
+#291: @JsonTypeInfo with As.EXTERNAL_PROPERTY doesn't work if external type property
+ is referenced more than once
+ (reported by Starkom@github)
+#357: StackOverflowError with contentConverter that returns array type
+ (reported by Florian S)
+#383: Recursive `@JsonUnwrapped` (`child` with same type) fail: "No _valueDeserializer assigned"
+ (reported by tdavis@github)
+#403: Make FAIL_ON_NULL_FOR_PRIMITIVES apply to primitive arrays and other types that wrap primitives
+ (reported by Harleen S)
+#476: Allow "Serialize as POJO" using `@JsonFormat(shape=Shape.OBJECT)` class annotation
+#507: Support for default `@JsonView` for a class
+ (suggested by Mark W)
+#687: Exception deserializing a collection @JsonIdentityInfo and a property based creator
+#865: `JsonFormat.Shape.OBJECT` ignored when class implements `Map.Entry`
+#888: Allow specifying custom exclusion comparator via `@JsonInclude`,
+ using `JsonInclude.Include.CUSTOM`
+#994: `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS` only works for POJOs, Maps
+#1029: Add a way to define property name aliases
+#1035: `@JsonAnySetter` assumes key of `String`, does not consider declared type.
+ (reported by Michael F)
+#1060: Allow use of `@JsonIgnoreProperties` for POJO-valued arrays, `Collection`s
+#1106: Add `MapperFeature.ALLOW_COERCION_OF_SCALARS` for enabling/disabling coercions
+#1284: Make `StdKeySerializers` use new `JsonGenerator.writeFieldId()` for `int`/`long` keys
+#1320: Add `ObjectNode.put(String, BigInteger)`
+ (proposed by Jan L)
+#1341: `DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY`
+ (contributed by Connor K)
+#1347: Extend `ObjectMapper.configOverrides()` to allow changing visibility rules
+#1356: Differentiate between input and code exceptions on deserialization
+ (suggested by Nick B)
+#1369: Improve `@JsonCreator` detection via `AnnotationIntrospector`
+ by passing `MappingConfig`
+#1371: Add `MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES` to allow
+ disabling use of `@CreatorProperties` as explicit `@JsonCreator` equivalent
+#1376: Add ability to disable JsonAnySetter/JsonAnyGetter via mixin
+ (suggested by brentryan@github)
+#1399: Add support for `@JsonMerge` to allow "deep update"
+#1402: Use `@JsonSetter(nulls=...)` to specify handling of `null` values during deserialization
+#1406: `ObjectMapper.readTree()` methods do not return `null` on end-of-input
+ (reported by Fabrizio C)
+#1407: `@JsonFormat.pattern` is ignored for `java.sql.Date` valued properties
+ (reported by sangpire@github)
+#1415: Creating CollectionType for non generic collection class broken
+#1428: Allow `@JsonValue` on a field, not just getter
+#1434: Explicitly pass null on invoke calls with no arguments
+ (contributed by Emiliano C)
+#1433: `ObjectMapper.convertValue()` with null does not consider null conversions
+ (`JsonDeserializer.getNullValue()`)
+ (contributed by jdmichal@github)
+#1440: Wrong `JsonStreamContext` in `DeserializationProblemHandler` when reading
+ `TokenBuffer` content
+ (reported by Patrick G)
+#1444: Change `ObjectMapper.setSerializationInclusion()` to apply to content inclusion too
+#1450: `SimpleModule.addKeyDeserializer()' should throw `IllegalArgumentException` if `null`
+ reference of `KeyDeserializer` passed
+ (suggested by PawelJagus@github)
+#1454: Support `@JsonFormat.lenient` for `java.util.Date`, `java.util.Calendar`
+#1474: Replace use of `Class.newInstance()` (deprecated in Java 9) with call via Constructor
+#1480: Add support for serializing `boolean`/`Boolean` as number (0 or 1)
+ (suggested by jwilmoth@github)
+#1520: Case insensitive enum deserialization with `MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS`
+ (contributed by Ana-Eliza B)
+#1522: Global `@JsonInclude(Include.NON_NULL)` for all properties with a specific type
+ (contributed by Carsten W)
+#1544: EnumMapDeserializer assumes a pure EnumMap and does not support EnumMap derived classes
+ (reported by Lyor G)
+#1550: Unexpected behavior with `@JsonInclude(JsonInclude.Include.NON_EMPTY)` and
+ `java.util.Date` serialization
+#1551: `JsonMappingException` with polymorphic type and `JsonIdentityInfo` when basic type is abstract
+ (reported by acm073@github)
+#1552: Map key converted to byte array is not serialized as base64 string
+ (reported by nmatt@github)
+#1554: Support deserialization of `Shape.OBJECT` ("as POJO") for `Map`s (and map-like types)
+#1556: Add `ObjectMapper.updateValue()` method to update instance with given overrides
+ (suggested by syncer@github)
+#1583: Add a `DeserializationFeature.FAIL_ON_TRAILING_TOKENS` to force reading of the
+ whole input as single value
+#1592: Add support for handling primitive/discrepancy problem with type refinements
+#1605: Allow serialization of `InetAddress` as simple numeric host address
+ (requested by Jared J)
+#1616: Extraneous type id mapping added for base type itself
+#1619: By-pass annotation introspection for array types
+#1637: `ObjectReader.at()` with `JsonPointer` stops after first collection
+ (reported by Chris P)
+#1653: Convenience overload(s) for ObjectMapper#registerSubtypes
+#1655: `@JsonAnyGetter` uses different `bean` parameter in `SimpleBeanPropertyFilter`
+ (reported by georgeflugq@github)
+#1678: Rewrite `StdDateFormat` ISO-8601 handling functionality
+#1684: Rewrite handling of type ids to let `JsonGenerator` handle (more of) details
+#1688: Deserialization fails for `java.nio.file.Path` implementations when default typing
+ enabled
+ (reported by Christian B)
+#1690: Prevent use of quoted number (index) for Enum deserialization via
+ `MapperFeature.ALLOW_COERCION_OF_SCALARS`
+ (requested by magdel@github)
2.8.11.4 (25-Jul-2019)
@@ -84,6 +463,7 @@
#1657: `StdDateFormat` deserializes dates with no tz/offset as UTC instead of
configured timezone
(reported by Bertrand R)
+#1680: Blacklist couple more types for deserialization
#1658: Infinite recursion when deserializing a class extending a Map,
with a recursive value type
(reported by Kevin G)
@@ -91,6 +471,7 @@
#1711: Delegating creator fails to work for binary data (`byte[]`) with
binary formats (CBOR, Smile)
#1735: Missing type checks when using polymorphic type ids
+ (reported by Lukas Euler)
#1737: Block more JDK types from polymorphic deserialization (CVE 2017-15095)
2.8.9 (12-Jun-2017)
@@ -201,7 +582,6 @@
2.8.3 (17-Sep-2016)
-#929: `@JsonCreator` not working on a factory with multiple arguments for a enum type
#1351: `@JsonInclude(NON_DEFAULT)` doesn't omit null fields
(reported by Gili T)
#1353: Improve error-handling for `java.net.URL` deserialization
diff --git a/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java
index 587c29f..bb6cbf9 100644
--- a/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java
@@ -1,6 +1,5 @@
package com.fasterxml.jackson.databind;
-
/**
* Defines interface for resolvers that can resolve abstract types into concrete
* ones; either by using static mappings, or possibly by materializing
@@ -23,8 +22,10 @@
* resolved: caller is expected to keep calling this method on registered
* resolvers, until a concrete type is located.
*
- * @param config Configuration in use; should always be of type
- * <code>DeserializationConfig</code>
+ * @param config Configuration in use
+ * @param type Type to find mapping for
+ *
+ * @return Type to map given input type (if mapping found) or {@code null} (if not).
*/
public JavaType findTypeMapping(DeserializationConfig config, JavaType type) {
return null;
@@ -35,6 +36,11 @@
* obsoleted in 2.7
*
* @deprecated since 2.8 (may be removed from 2.9 or later)
+ *
+ * @param config Configuration in use
+ * @param type Type to resolve
+ *
+ * @return Resolved concrete type
*/
@Deprecated
public JavaType resolveAbstractType(DeserializationConfig config,
@@ -49,8 +55,7 @@
* It will be called after checking all other possibilities,
* including defaulting.
*
- * @param config Configuration in use; should always be of type
- * <code>DeserializationConfig</code>
+ * @param config Configuration in use
* @param typeDesc Description of the POJO type to resolve
*
* @return Resolved concrete type (which should retain generic
diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java
index 2f2e224..57056ae 100644
--- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java
@@ -3,13 +3,10 @@
import java.lang.annotation.Annotation;
import java.util.*;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.Versioned;
+
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
@@ -20,8 +17,6 @@
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
-import com.fasterxml.jackson.databind.type.MapLikeType;
-import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.Converter;
import com.fasterxml.jackson.databind.util.NameTransformer;
@@ -67,7 +62,7 @@
* {@link com.fasterxml.jackson.annotation.JsonManagedReference}
*/
MANAGED_REFERENCE
-
+
/**
* Reference property that Jackson manages by suppressing it during serialization,
* and reconstructing during deserialization.
@@ -238,56 +233,12 @@
*/
public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac)
{
- // 28-Apr-2016, tatu: For backwards compatibility let's delegate to older
- // methods, for Jackson 2.8
- String[] ignorals = findPropertiesToIgnore(ac, true);
- Boolean b = (ac instanceof AnnotatedClass) ?
- findIgnoreUnknownProperties((AnnotatedClass) ac) : null;
- JsonIgnoreProperties.Value v;
- if (ignorals == null) {
- if (b == null) {
- return null;
- }
- v = JsonIgnoreProperties.Value.empty();
- } else {
- v = JsonIgnoreProperties.Value.forIgnoredProperties(ignorals);
- }
- if (b != null) {
- v = b.booleanValue() ? v.withIgnoreUnknown() : v.withoutIgnoreUnknown();
- }
- return v;
+ // 18-Oct-2016, tatu: Used to call deprecated methods for backwards
+ // compatibility in 2.8, but not any more in 2.9
+ return JsonIgnoreProperties.Value.empty();
}
/**
- * @param forSerialization True if requesting properties to ignore for serialization;
- * false if for deserialization
- *
- * @since 2.6
- *
- * @deprecated Since 2.8, use {@link #findPropertyIgnorals} instead
- */
- @Deprecated // since 2.8
- public String[] findPropertiesToIgnore(Annotated ac, boolean forSerialization) {
- return null;
- }
-
- /**
- * @deprecated Since 2.6, use variant that takes second argument.
- */
- @Deprecated // since 2.6
- public String[] findPropertiesToIgnore(Annotated ac) {
- return null;
- }
-
- /**
- * Method for checking whether an annotation indicates that all unknown properties
- *
- * @deprecated Since 2.8, use {@link #findPropertyIgnorals} instead
- */
- @Deprecated // since 2.8
- public Boolean findIgnoreUnknownProperties(AnnotatedClass ac) { return null; }
-
- /**
* Method for checking whether properties that have specified type
* (class, not generics aware) should be completely ignored for
* serialization and deserialization purposes.
@@ -335,6 +286,35 @@
*/
public String findClassDescription(AnnotatedClass ac) { return null; }
+ /**
+ * @param forSerialization True if requesting properties to ignore for serialization;
+ * false if for deserialization
+ *
+ * @since 2.6
+ *
+ * @deprecated Since 2.8, use {@link #findPropertyIgnorals} instead
+ */
+ @Deprecated // since 2.8
+ public String[] findPropertiesToIgnore(Annotated ac, boolean forSerialization) {
+ return null;
+ }
+
+ /**
+ * @deprecated Since 2.6, use variant that takes second argument.
+ */
+ @Deprecated // since 2.6
+ public String[] findPropertiesToIgnore(Annotated ac) {
+ return null;
+ }
+
+ /**
+ * Method for checking whether an annotation indicates that all unknown properties
+ *
+ * @deprecated Since 2.8, use {@link #findPropertyIgnorals} instead
+ */
+ @Deprecated // since 2.8
+ public Boolean findIgnoreUnknownProperties(AnnotatedClass ac) { return null; }
+
/*
/**********************************************************
/* Property auto-detection
@@ -365,7 +345,7 @@
* instantiating resolver builder, but also configuring it based on
* relevant annotations (not including ones checked with a call to
* {@link #findSubtypes}
- *
+ *
* @param config Configuration settings in effect (for serialization or deserialization)
* @param ac Annotated class to check for annotations
* @param baseType Base java type of value for which resolver is to be found
@@ -487,18 +467,26 @@
*
* @return Identifier of value to inject, if any; null if no injection
* indicator is found
+ *
+ * @since 2.9
*/
- public Object findInjectableValueId(AnnotatedMember m) { return null; }
+ public JacksonInject.Value findInjectableValue(AnnotatedMember m) {
+ // 05-Apr-2017, tatu: Just for 2.9, call deprecated method to help
+ // with some cases of overrides for legacy code
+ Object id = findInjectableValueId(m);
+ if (id != null) {
+ return JacksonInject.Value.forId(id);
+ }
+ return null;
+ }
/**
* Method that can be called to check whether this member has
* an annotation that suggests whether value for matching property
* is required or not.
- *
- * @since 2.0
*/
public Boolean hasRequiredMarker(AnnotatedMember m) { return null; }
-
+
/**
* Method for checking if annotated property (represented by a field or
* getter/setter method) has definitions for views it is to be included in.
@@ -507,6 +495,9 @@
* otherwise it will only be included for views included in returned
* array. View matches are checked using class inheritance rules (sub-classes
* inherit inclusions of super-classes)
+ *<p>
+ * Since 2.9 this method may also be called to find "default view(s)" for
+ * {@link AnnotatedClass}
*
* @param a Annotated property (represented by a method, field or ctor parameter)
* @return Array of views (represented by classes) that the property is included in;
@@ -522,7 +513,9 @@
*
* @since 2.1
*/
- public JsonFormat.Value findFormat(Annotated memberOrClass) { return null; }
+ public JsonFormat.Value findFormat(Annotated memberOrClass) {
+ return JsonFormat.Value.empty();
+ }
/**
* Method used to check if specified property has annotation that indicates
@@ -579,7 +572,7 @@
* it is "weak" and does not either proof that a property exists (for example,
* if visibility is not high enough), or override explicit names.
* In practice this method is used to introspect optional names for creator
- * parameters (which may or may not be available and can not be detected
+ * parameters (which may or may not be available and cannot be detected
* by standard databind); or to provide alternate name mangling for
* fields, getters and/or setters.
*
@@ -588,6 +581,16 @@
public String findImplicitPropertyName(AnnotatedMember member) { return null; }
/**
+ * Method called to find if given property has alias(es) defined.
+ *
+ * @return `null` if member has no information; otherwise a `List` (possibly
+ * empty) of aliases to use.
+ *
+ * @since 2.9
+ */
+ public List<PropertyName> findPropertyAliases(Annotated ann) { return null; }
+
+ /**
* Method for finding optional access definition for a property, annotated
* on one of its accessors. If a definition for read-only, write-only
* or read-write cases, visibility rules may be modified. Note, however,
@@ -602,7 +605,7 @@
* Method called in cases where a class has two methods eligible to be used
* for the same logical property, and default logic is not enough to figure
* out clear precedence. Introspector may try to choose one to use; or, if
- * unable, return `null` to indicate it can not resolve the problem.
+ * unable, return `null` to indicate it cannot resolve the problem.
*
* @since 2.7
*/
@@ -611,6 +614,14 @@
return null;
}
+ /**
+ * @deprecated Since 2.9 Use {@link #findInjectableValue} instead
+ */
+ @Deprecated // since 2.9
+ public Object findInjectableValueId(AnnotatedMember m) {
+ return null;
+ }
+
/*
/**********************************************************
/* Serialization: general annotations
@@ -721,6 +732,18 @@
}
/**
+ * Method for checking inclusion criteria for a type (Class) or property (yes, method
+ * name is bit unfortunate -- not just for properties!).
+ * In case of class, acts as the default for properties POJO contains; for properties
+ * acts as override for class defaults and possible global defaults.
+ *
+ * @since 2.6
+ */
+ public JsonInclude.Value findPropertyInclusion(Annotated a) {
+ return JsonInclude.Value.empty();
+ }
+
+ /**
* Method for checking whether given annotated entity (class, method,
* field) defines which Bean/Map properties are to be included in
* serialization.
@@ -746,9 +769,9 @@
* Method for checking whether content (entries) of a {@link java.util.Map} property
* are to be included during serialization or not.
* NOTE: this is NOT called for POJO properties, or array/Collection elements.
- *
+ *
* @since 2.5
- *
+ *
* @deprecated Since 2.7 Use {@link #findPropertyInclusion} instead
*/
@Deprecated // since 2.7
@@ -756,18 +779,6 @@
return defValue;
}
- /**
- * Method for checking inclusion criteria for a type (Class) or property (yes, method
- * name is bit unfortunate -- not just for properties!).
- * In case of class, acts as the default for properties POJO contains; for properties
- * acts as override for class defaults and possible global defaults.
- *
- * @since 2.6
- */
- public JsonInclude.Value findPropertyInclusion(Annotated a) {
- return JsonInclude.Value.empty();
- }
-
/*
/**********************************************************
/* Serialization: type refinements
@@ -775,14 +786,19 @@
*/
/**
- * Method for accessing annotated type definition that a
- * method/field can have, to be used as the type for serialization
- * instead of the runtime type.
- * Type returned (if any) needs to be widening conversion (super-type).
- * Declared return type of the method is also considered acceptable.
+ * Method called to find out possible type refinements to use
+ * for deserialization, including not just value itself but
+ * key and/or content type, if type has those.
*
- * @return Class to use instead of runtime type
- *
+ * @since 2.7
+ */
+ public JavaType refineSerializationType(final MapperConfig<?> config,
+ final Annotated a, final JavaType baseType) throws JsonMappingException
+ {
+ return baseType;
+ }
+
+ /**
* @deprecated Since 2.7 call {@link #refineSerializationType} instead
*/
@Deprecated // since 2.7
@@ -791,13 +807,6 @@
}
/**
- * Method for finding possible widening type definition that a property
- * value can have, to define less specific key type to use for serialization.
- * It should be only be used with {@link java.util.Map} types.
- *
- * @return Class specifying more general type to use instead of
- * declared type, if annotation found; null if not
- *
* @deprecated Since 2.7 call {@link #refineSerializationType} instead
*/
@Deprecated // since 2.7
@@ -806,138 +815,13 @@
}
/**
- * Method for finding possible widening type definition that a property
- * value can have, to define less specific key type to use for serialization.
- * It should be only used with structured types (arrays, collections, maps).
- *
- * @return Class specifying more general type to use instead of
- * declared type, if annotation found; null if not
- *
* @deprecated Since 2.7 call {@link #refineSerializationType} instead
*/
@Deprecated // since 2.7
public Class<?> findSerializationContentType(Annotated am, JavaType baseType) {
return null;
}
-
- /**
- * Method called to find out possible type refinements to use
- * for deserialization.
- *
- * @since 2.7
- */
- public JavaType refineSerializationType(final MapperConfig<?> config,
- final Annotated a, final JavaType baseType) throws JsonMappingException
- {
- JavaType type = baseType;
- final TypeFactory tf = config.getTypeFactory();
-
- // 10-Oct-2015, tatu: For 2.7, we'll need to delegate back to
- // now-deprecated secondary methods; this because while
- // direct sub-class not yet retrofitted may only override
- // those methods. With 2.8 or later we may consider removal
- // of these methods
-
-
- // Ok: start by refining the main type itself; common to all types
- Class<?> serClass = findSerializationType(a);
- if (serClass != null) {
- if (type.hasRawClass(serClass)) {
- // 30-Nov-2015, tatu: As per [databind#1023], need to allow forcing of
- // static typing this way
- type = type.withStaticTyping();
- } else {
- Class<?> currRaw = type.getRawClass();
- try {
- // 11-Oct-2015, tatu: For deser, we call `TypeFactory.constructSpecializedType()`,
- // may be needed here too in future?
- if (serClass.isAssignableFrom(currRaw)) { // common case
- type = tf.constructGeneralizedType(type, serClass);
- } else if (currRaw.isAssignableFrom(serClass)) { // specialization, ok as well
- type = tf.constructSpecializedType(type, serClass);
- } else {
- throw new JsonMappingException(null,
- String.format("Can not refine serialization type %s into %s; types not related",
- type, serClass.getName()));
- }
- } catch (IllegalArgumentException iae) {
- throw new JsonMappingException(null,
- String.format("Failed to widen type %s with annotation (value %s), from '%s': %s",
- type, serClass.getName(), a.getName(), iae.getMessage()),
- iae);
- }
- }
- }
- // Then further processing for container types
-
- // First, key type (for Maps, Map-like types):
- if (type.isMapLikeType()) {
- JavaType keyType = type.getKeyType();
- Class<?> keyClass = findSerializationKeyType(a, keyType);
- if (keyClass != null) {
- if (keyType.hasRawClass(keyClass)) {
- keyType = keyType.withStaticTyping();
- } else {
- Class<?> currRaw = keyType.getRawClass();
- try {
- // 19-May-2016, tatu: As per [databind#1231], [databind#1178] may need to actually
- // specialize (narrow) type sometimes, even if more commonly opposite
- // is needed.
- if (keyClass.isAssignableFrom(currRaw)) { // common case
- keyType = tf.constructGeneralizedType(keyType, keyClass);
- } else if (currRaw.isAssignableFrom(keyClass)) { // specialization, ok as well
- keyType = tf.constructSpecializedType(keyType, keyClass);
- } else {
- throw new JsonMappingException(null,
- String.format("Can not refine serialization key type %s into %s; types not related",
- keyType, keyClass.getName()));
- }
- } catch (IllegalArgumentException iae) {
- throw new JsonMappingException(null,
- String.format("Failed to widen key type of %s with concrete-type annotation (value %s), from '%s': %s",
- type, keyClass.getName(), a.getName(), iae.getMessage()),
- iae);
- }
- }
- type = ((MapLikeType) type).withKeyType(keyType);
- }
- }
-
- JavaType contentType = type.getContentType();
- if (contentType != null) { // collection[like], map[like], array, reference
- // And then value types for all containers:
- Class<?> contentClass = findSerializationContentType(a, contentType);
- if (contentClass != null) {
- if (contentType.hasRawClass(contentClass)) {
- contentType = contentType.withStaticTyping();
- } else {
- // 03-Apr-2016, tatu: As per [databind#1178], may need to actually
- // specialize (narrow) type sometimes, even if more commonly opposite
- // is needed.
- Class<?> currRaw = contentType.getRawClass();
- try {
- if (contentClass.isAssignableFrom(currRaw)) { // common case
- contentType = tf.constructGeneralizedType(contentType, contentClass);
- } else if (currRaw.isAssignableFrom(contentClass)) { // specialization, ok as well
- contentType = tf.constructSpecializedType(contentType, contentClass);
- } else {
- throw new JsonMappingException(null,
- String.format("Can not refine serialization content type %s into %s; types not related",
- contentType, contentClass.getName()));
- }
- } catch (IllegalArgumentException iae) { // shouldn't really happen
- throw new JsonMappingException(null,
- String.format("Internal error: failed to refine value type of %s with concrete-type annotation (value %s), from '%s': %s",
- type, contentClass.getName(), a.getName(), iae.getMessage()),
- iae);
- }
- }
- type = type.withContentType(contentType);
- }
- }
- return type;
- }
-
+
/*
/**********************************************************
/* Serialization: class annotations
@@ -991,14 +875,6 @@
* @since 2.1
*/
public PropertyName findNameForSerialization(Annotated a) {
- /*
- if (name != null) {
- if (name.length() == 0) { // empty String means 'default'
- return PropertyName.USE_DEFAULT;
- }
- return new PropertyName(name);
- }
- */
return null;
}
@@ -1008,11 +884,71 @@
* should be used as "the value" of the object instance; usually
* serialized as a primitive value such as String or number.
*
- * @return True if such annotation is found (and is not disabled);
- * false if no enabled annotation is found
+ * @return {@link Boolean#TRUE} if such annotation is found and is not disabled;
+ * {@link Boolean#FALSE} if disabled annotation (block) is found (to indicate
+ * accessor is definitely NOT to be used "as value"); or `null` if no
+ * information found.
+ *
+ * @since 2.9
*/
- public boolean hasAsValueAnnotation(AnnotatedMethod am) {
- return false;
+ public Boolean hasAsValue(Annotated a) {
+ // 20-Nov-2016, tatu: Delegate in 2.9; remove redirect from later versions
+ if (a instanceof AnnotatedMethod) {
+ if (hasAsValueAnnotation((AnnotatedMethod) a)) {
+ return true;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Method for checking whether given method has an annotation
+ * that suggests that the method is to serve as "any setter";
+ * method to be used for accessing set of miscellaneous "extra"
+ * properties, often bound with matching "any setter" method.
+ *
+ * @return True if such annotation is found (and is not disabled),
+ * false otherwise
+ *
+ * @since 2.9
+ */
+ public Boolean hasAnyGetter(Annotated a) {
+
+ // 21-Nov-2016, tatu: Delegate in 2.9; remove redirect from later versions
+ if (a instanceof AnnotatedMethod) {
+ if (hasAnyGetterAnnotation((AnnotatedMethod) a)) {
+ return true;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Method for efficiently figuring out which if given set of <code>Enum</code> values
+ * have explicitly defined name. Method will overwrite entries in incoming <code>names</code>
+ * array with explicit names found, if any, leaving other entries unmodified.
+ *<p>
+ * Default implementation will simply delegate to {@link #findEnumValue}, which is close
+ * enough, although unfortunately NOT 100% equivalent (as it will also consider <code>name()</code>
+ * to give explicit value).
+ *
+ * @since 2.7
+ */
+ public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[] names) {
+ // 18-Oct-2016, tatu: In 2.8 delegated to deprecated method; not so in 2.9 and beyond
+ return names;
+ }
+
+ /**
+ * 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.
+ *
+ * @since 2.8
+ */
+ public Enum<?> findDefaultEnumValue(Class<Enum<?>> enumCls) {
+ return null;
}
/**
@@ -1033,42 +969,21 @@
}
/**
- * Method for efficiently figuring out which if given set of <code>Enum</code> values
- * have explicitly defined name. Method will overwrite entries in incoming <code>names</code>
- * array with explicit names found, if any, leaving other entries unmodified.
- *<p>
- * Default implementation will simply delegate to {@link #findEnumValue}, which is close
- * enough, although unfortunately NOT 100% equivalent (as it will also consider <code>name()</code>
- * to give explicit value).
- *
- * @since 2.7
+ * @deprecated Since 2.9 Use {@link #hasAsValue(Annotated)} instead.
*/
- public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[] names) {
- for (int i = 0, len = enumValues.length; i < len; ++i) {
- /* 12-Mar-2016, tatu: This is quite tricky, considering that we should NOT
- * overwrite values with default `name`... so for now, let's only delegate
- * if no value has been set. Still not optimal but has to do
- */
- // TODO: In 2.8, stop delegation?
- if (names[i] == null) {
- names[i] = findEnumValue(enumValues[i]);
- }
- }
- return names;
+ @Deprecated // since 2.9
+ public boolean hasAsValueAnnotation(AnnotatedMethod am) {
+ return false;
}
/**
- * 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.
- *
- * @since 2.8
+ * @deprecated Since 2.9 Use {@link #hasAnyGetter} instead
*/
- public Enum<?> findDefaultEnumValue(Class<Enum<?>> enumCls) {
- return null;
+ @Deprecated
+ public boolean hasAnyGetterAnnotation(AnnotatedMethod am) {
+ return false;
}
-
+
/*
/**********************************************************
/* Deserialization: general annotations
@@ -1175,65 +1090,9 @@
public JavaType refineDeserializationType(final MapperConfig<?> config,
final Annotated a, final JavaType baseType) throws JsonMappingException
{
- JavaType type = baseType;
- final TypeFactory tf = config.getTypeFactory();
-
- // 10-Oct-2015, tatu: For 2.7, we'll need to delegate back to
- // now-deprecated secondary methods; this because while
- // direct sub-class not yet retrofitted may only override
- // those methods. With 2.8 or later we may consider removal
- // of these methods
-
-
- // Ok: start by refining the main type itself; common to all types
- Class<?> valueClass = findDeserializationType(a, type);
- if ((valueClass != null) && !type.hasRawClass(valueClass)) {
- try {
- type = tf.constructSpecializedType(type, valueClass);
- } catch (IllegalArgumentException iae) {
- throw new JsonMappingException(null,
- String.format("Failed to narrow type %s with annotation (value %s), from '%s': %s",
- type, valueClass.getName(), a.getName(), iae.getMessage()),
- iae);
- }
- }
- // Then further processing for container types
-
- // First, key type (for Maps, Map-like types):
- if (type.isMapLikeType()) {
- JavaType keyType = type.getKeyType();
- Class<?> keyClass = findDeserializationKeyType(a, keyType);
- if (keyClass != null) {
- try {
- keyType = tf.constructSpecializedType(keyType, keyClass);
- type = ((MapLikeType) type).withKeyType(keyType);
- } catch (IllegalArgumentException iae) {
- throw new JsonMappingException(null,
- String.format("Failed to narrow key type of %s with concrete-type annotation (value %s), from '%s': %s",
- type, keyClass.getName(), a.getName(), iae.getMessage()),
- iae);
- }
- }
- }
- JavaType contentType = type.getContentType();
- if (contentType != null) { // collection[like], map[like], array, reference
- // And then value types for all containers:
- Class<?> contentClass = findDeserializationContentType(a, contentType);
- if (contentClass != null) {
- try {
- contentType = tf.constructSpecializedType(contentType, contentClass);
- type = type.withContentType(contentType);
- } catch (IllegalArgumentException iae) {
- throw new JsonMappingException(null,
- String.format("Failed to narrow value type of %s with concrete-type annotation (value %s), from '%s': %s",
- type, contentClass.getName(), a.getName(), iae.getMessage()),
- iae);
- }
- }
- }
- return type;
+ return baseType;
}
-
+
/**
* Method for accessing annotated type definition that a
* property can have, to be used as the type for deserialization
@@ -1325,7 +1184,7 @@
public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
return null;
}
-
+
/*
/**********************************************************
/* Deserialization: property annotations
@@ -1347,14 +1206,6 @@
* @since 2.1
*/
public PropertyName findNameForDeserialization(Annotated a) {
- /*
- if (name != null) {
- if (name.length() == 0) { // empty String means 'default'
- return PropertyName.USE_DEFAULT;
- }
- return new PropertyName(name);
- }
- */
return null;
}
@@ -1366,24 +1217,60 @@
*
* @return True if such annotation is found (and is not disabled),
* false otherwise
+ *
+ * @since 2.9
*/
- public boolean hasAnySetterAnnotation(AnnotatedMethod am) {
- return false;
+ public Boolean hasAnySetter(Annotated a) {
+ return null;
}
/**
- * Method for checking whether given method has an annotation
- * that suggests that the method is to serve as "any setter";
- * method to be used for accessing set of miscellaneous "extra"
- * properties, often bound with matching "any setter" method.
+ * Method for finding possible settings for property, given annotations
+ * on an accessor.
*
- * @return True if such annotation is found (and is not disabled),
- * false otherwise
+ * @since 2.9
*/
- public boolean hasAnyGetterAnnotation(AnnotatedMethod am) {
- return false;
+ public JsonSetter.Value findSetterInfo(Annotated a) {
+ return JsonSetter.Value.empty();
}
-
+
+ /**
+ * Method for finding merge settings for property, if any.
+ *
+ * @since 2.9
+ */
+ public Boolean findMergeInfo(Annotated a) {
+ return null;
+ }
+
+ /**
+ * Method called to check whether potential Creator (constructor or static factory
+ * method) has explicit annotation to indicate it as actual Creator; and if so,
+ * which {@link com.fasterxml.jackson.annotation.JsonCreator.Mode} to use.
+ *<p>
+ * NOTE: caller needs to consider possibility of both `null` (no annotation found)
+ * and {@link com.fasterxml.jackson.annotation.JsonCreator.Mode#DISABLED} (annotation found,
+ * but disabled); latter is necessary as marker in case multiple introspectors are chained,
+ * 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
+ *
+ * @since 2.9
+ */
+ public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
+ // 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 (mode == null) {
+ mode = JsonCreator.Mode.DEFAULT;
+ }
+ return mode;
+ }
+ return null;
+ }
+
/**
* Method for checking whether given annotated item (method, constructor)
* has an annotation
@@ -1393,7 +1280,10 @@
*
* @return True if such annotation is found (and is not disabled),
* false otherwise
+ *
+ * @deprecated Since 2.9 use {@link #findCreatorAnnotation} instead.
*/
+ @Deprecated
public boolean hasCreatorAnnotation(Annotated a) {
return false;
}
@@ -1405,11 +1295,21 @@
* creator with implicit but no explicit name for the argument).
*
* @since 2.5
+ * @deprecated Since 2.9 use {@link #findCreatorAnnotation} instead.
*/
+ @Deprecated
public JsonCreator.Mode findCreatorBinding(Annotated a) {
return null;
}
-
+
+ /**
+ * @deprecated Since 2.9 use {@link #hasAnySetter} instead.
+ */
+ @Deprecated // since 2.9
+ public boolean hasAnySetterAnnotation(AnnotatedMethod am) {
+ return false;
+ }
+
/*
/**********************************************************
/* Overridable methods: may be used as low-level extension
diff --git a/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java b/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java
index cd21182..24ddfe9 100644
--- a/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java
+++ b/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java
@@ -23,7 +23,7 @@
{
/**
* Bean type information, including raw class and possible
- * * generics information
+ * generics information
*/
protected final JavaType _type;
@@ -114,12 +114,22 @@
*/
public abstract List<BeanPropertyDefinition> findProperties();
+ public abstract Set<String> getIgnoredPropertyNames();
+
/**
* Method for locating all back-reference properties (setters, fields) bean has
+ *
+ * @since 2.9
*/
- public abstract Map<String,AnnotatedMember> findBackReferenceProperties();
+ public abstract List<BeanPropertyDefinition> findBackReferences();
- public abstract Set<String> getIgnoredPropertyNames();
+ /**
+ * Method for locating all back-reference properties (setters, fields) bean has
+ *
+ * @deprecated Since 2.9 use {@link #findBackReferences()} instead
+ */
+ @Deprecated
+ public abstract Map<String,AnnotatedMember> findBackReferenceProperties();
/*
/**********************************************************
@@ -162,39 +172,63 @@
/* Basic API for finding property accessors
/**********************************************************
*/
-
- public abstract AnnotatedMember findAnyGetter();
/**
- * Method used to locate the method of introspected class that
- * implements {@link com.fasterxml.jackson.annotation.JsonAnySetter}. If no such method exists
- * null is returned. If more than one are found, an exception
- * is thrown.
- * Additional checks are also made to see that method signature
- * is acceptable: needs to take 2 arguments, first one String or
- * Object; second any can be any type.
- */
- public abstract AnnotatedMethod findAnySetter();
-
- /**
- * Method used to locate the field of the class that implements
- * {@link com.fasterxml.jackson.annotation.JsonAnySetter} If no such method
- * exists null is returned. If more than one are found, an exception is thrown.
- *
- * @since 2.8
- */
- public abstract AnnotatedMember findAnySetterField();
-
- /**
- * Method for locating the getter method that is annotated with
+ * Method for locating accessor (readable field, or "getter" method)
+ * that has
* {@link com.fasterxml.jackson.annotation.JsonValue} annotation,
* if any. If multiple ones are found,
* an error is reported by throwing {@link IllegalArgumentException}
+ *
+ * @since 2.9
*/
- public abstract AnnotatedMethod findJsonValueMethod();
+ public abstract AnnotatedMember findJsonValueAccessor();
+
+ public abstract AnnotatedMember findAnyGetter();
+
+ /**
+ * Method used to locate a mutator (settable field, or 2-argument set method)
+ * of introspected class that
+ * implements {@link com.fasterxml.jackson.annotation.JsonAnySetter}.
+ * If no such mutator exists null is returned. If more than one are found,
+ * an exception is thrown.
+ * Additional checks are also made to see that method signature
+ * is acceptable: needs to take 2 arguments, first one String or
+ * Object; second any can be any type.
+ *
+ * @since 2.9
+ */
+ public abstract AnnotatedMember findAnySetterAccessor();
public abstract AnnotatedMethod findMethod(String name, Class<?>[] paramTypes);
+
+ @Deprecated // since 2.9
+ public abstract AnnotatedMethod findJsonValueMethod();
+ /**
+ * @deprecated Since 2.9: use {@link #findAnySetterAccessor} instead
+ */
+ @Deprecated
+ public AnnotatedMethod findAnySetter() {
+ AnnotatedMember m = findAnySetterAccessor();
+ if (m instanceof AnnotatedMethod) {
+ return (AnnotatedMethod) m;
+ }
+ return null;
+ }
+
+ /**
+ * @deprecated Since 2.9: use {@link #findAnySetterAccessor} instead
+ */
+ @Deprecated
+ public AnnotatedMember findAnySetterField() {
+ AnnotatedMember m = findAnySetterAccessor();
+ if (m instanceof AnnotatedField) {
+ return m;
+ }
+ return null;
+ }
+
/*
/**********************************************************
/* Basic API, class configuration
@@ -279,4 +313,13 @@
* suitable default constructor was found; null otherwise.
*/
public abstract Object instantiateBean(boolean fixAccess);
+
+ /**
+ * Method for finding out if the POJO specifies default view(s) to
+ * use for properties, considering both per-type annotations and
+ * global default settings.
+ *
+ * @since 2.9
+ */
+ public abstract Class<?>[] findDefaultViews();
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java b/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java
index 525aa11..e8a562c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/BeanProperty.java
@@ -1,12 +1,17 @@
package com.fasterxml.jackson.databind;
import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonFormat.Value;
import com.fasterxml.jackson.annotation.JsonInclude;
+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.Annotations;
import com.fasterxml.jackson.databind.util.Named;
@@ -14,7 +19,7 @@
* Bean properties are logical entities that represent data
* that Java objects (POJOs (Plain Old Java Objects), sometimes also called "beans")
* contain; and that are accessed using accessors (methods like getters
- * and setters, fields, constructor parametrers).
+ * and setters, fields, constructor parameters).
* Instances allow access to annotations directly associated
* to property (via field or method), as well as contextual
* annotations (annotations for class that contains properties).
@@ -26,7 +31,7 @@
* {@link com.fasterxml.jackson.databind.deser.ContextualDeserializer}
* resolution occurs (<code>createContextual(...)</code> method is called).
* References may (need to) be retained by serializers and deserializers,
- * especially when further resolving dependant handlers like value
+ * especially when further resolving dependent handlers like value
* serializers/deserializers or structured types.
*/
public interface BeanProperty extends Named
@@ -161,6 +166,16 @@
*/
public JsonInclude.Value findPropertyInclusion(MapperConfig<?> config, Class<?> baseType);
+ /**
+ * Method for accessing set of possible alternate names that are accepted
+ * during deserialization.
+ *
+ * @return List (possibly empty) of alternate names; never null
+ *
+ * @since 2.9
+ */
+ public List<PropertyName> findAliases(MapperConfig<?> config);
+
/*
/**********************************************************
/* Schema/introspection support
@@ -196,8 +211,11 @@
* Simple stand-alone implementation, useful as a placeholder
* or base class for more complex implementations.
*/
- public static class Std implements BeanProperty
+ public static class Std implements BeanProperty,
+ java.io.Serializable // 2.9
{
+ private static final long serialVersionUID = 1L;
+
protected final PropertyName _name;
protected final JavaType _type;
protected final PropertyName _wrapperName;
@@ -211,29 +229,32 @@
*/
protected final AnnotatedMember _member;
- /**
- * Annotations defined in the context class (if any); may be null
- * if no annotations were found
- */
- protected final Annotations _contextAnnotations;
-
public Std(PropertyName name, JavaType type, PropertyName wrapperName,
- Annotations contextAnnotations, AnnotatedMember member,
- PropertyMetadata metadata)
+ AnnotatedMember member, PropertyMetadata metadata)
{
_name = name;
_type = type;
_wrapperName = wrapperName;
_metadata = metadata;
_member = member;
- _contextAnnotations = contextAnnotations;
+ }
+
+ /**
+ * @deprecated Since 2.9
+ */
+ @Deprecated
+ public Std(PropertyName name, JavaType type, PropertyName wrapperName,
+ Annotations contextAnnotations,
+ AnnotatedMember member, PropertyMetadata metadata)
+ {
+ this(name, type, wrapperName, member, metadata);
}
/**
* @since 2.6
*/
public Std(Std base, JavaType newType) {
- this(base._name, newType, base._wrapperName, base._contextAnnotations, base._member, base._metadata);
+ this(base._name, newType, base._wrapperName, base._member, base._metadata);
}
public Std withType(JavaType type) {
@@ -247,7 +268,7 @@
@Override
public <A extends Annotation> A getContextAnnotation(Class<A> acls) {
- return (_contextAnnotations == null) ? null : _contextAnnotations.get(acls);
+ return null;
}
@Override
@@ -279,7 +300,7 @@
@Override
public JsonInclude.Value findPropertyInclusion(MapperConfig<?> config, Class<?> baseType)
{
- JsonInclude.Value v0 = config.getDefaultPropertyInclusion(baseType);
+ JsonInclude.Value v0 = config.getDefaultInclusion(baseType, _type.getRawClass());
AnnotationIntrospector intr = config.getAnnotationIntrospector();
if ((intr == null) || (_member == null)) {
return v0;
@@ -291,6 +312,13 @@
return v0.withOverrides(v);
}
+ @Override
+ public List<PropertyName> findAliases(MapperConfig<?> config) {
+ // 26-Feb-2017, tatu: Do we really need to allow actual definition?
+ // For now, let's not.
+ return Collections.emptyList();
+ }
+
@Override public String getName() { return _name.getSimpleName(); }
@Override public PropertyName getFullName() { return _name; }
@Override public JavaType getType() { return _type; }
@@ -314,4 +342,91 @@
throw new UnsupportedOperationException("Instances of "+getClass().getName()+" should not get visited");
}
}
+
+ /**
+ * Alternative "Null" implementation that can be used in cases where a non-null
+ * {@link BeanProperty} is needed
+ *
+ * @since 2.9
+ */
+ public static class Bogus implements BeanProperty
+ {
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public PropertyName getFullName() {
+ return PropertyName.NO_NAME;
+ }
+
+ @Override
+ public JavaType getType() {
+ return TypeFactory.unknownType();
+ }
+
+ @Override
+ public PropertyName getWrapperName() {
+ return null;
+ }
+
+ @Override
+ public PropertyMetadata getMetadata() {
+ return PropertyMetadata.STD_REQUIRED_OR_OPTIONAL;
+ }
+
+ @Override
+ public boolean isRequired() {
+ return false;
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return false;
+ }
+
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> acls) {
+ return null;
+ }
+
+ @Override
+ public <A extends Annotation> A getContextAnnotation(Class<A> acls) {
+ return null;
+ }
+
+ @Override
+ public AnnotatedMember getMember() {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public Value findFormatOverrides(AnnotationIntrospector intr) {
+ return Value.empty();
+ }
+
+ @Override
+ public Value findPropertyFormat(MapperConfig<?> config, Class<?> baseType) {
+ return Value.empty();
+ }
+
+ @Override
+ public com.fasterxml.jackson.annotation.JsonInclude.Value findPropertyInclusion(
+ MapperConfig<?> config, Class<?> baseType)
+ {
+ return null;
+ }
+
+ @Override
+ public List<PropertyName> findAliases(MapperConfig<?> config) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void depositSchemaProperty(JsonObjectFormatVisitor objectVisitor,
+ SerializerProvider provider) throws JsonMappingException {
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java b/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java
index f42a0bb..1111038 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java
@@ -25,6 +25,15 @@
*/
public abstract class DatabindContext
{
+ /**
+ * Let's limit length of error messages, for cases where underlying data
+ * may be very large -- no point in spamming logs with megabytes of meaningless
+ * data.
+ *
+ * @since 2.9
+ */
+ private final static int MAX_ERROR_STR_LEN = 500;
+
/*
/**********************************************************
/* Generic config access
@@ -134,7 +143,10 @@
* type (usually {@link java.lang.Class})
*/
public JavaType constructType(Type type) {
- return getTypeFactory().constructType(type);
+ if (type == null) {
+ return null;
+ }
+ return getTypeFactory().constructType(type);
}
/**
@@ -149,6 +161,60 @@
return getConfig().constructSpecializedType(baseType, subclass);
}
+ /**
+ * Lookup method called when code needs to resolve class name from input;
+ * usually simple lookup
+ *
+ * @since 2.9
+ */
+ public JavaType resolveSubType(JavaType baseType, String subClass)
+ throws JsonMappingException
+ {
+ // 30-Jan-2010, tatu: Most ids are basic class names; so let's first
+ // check if any generics info is added; and only then ask factory
+ // to do translation when necessary
+ if (subClass.indexOf('<') > 0) {
+ // note: may want to try combining with specialization (esp for EnumMap)?
+ // 17-Aug-2017, tatu: As per [databind#1735] need to ensure assignment
+ // compatibility -- needed later anyway, and not doing so may open
+ // security issues.
+ JavaType t = getTypeFactory().constructFromCanonical(subClass);
+ if (t.isTypeOrSubTypeOf(baseType.getRawClass())) {
+ return t;
+ }
+ } else {
+ Class<?> cls;
+ try {
+ cls = getTypeFactory().findClass(subClass);
+ } catch (ClassNotFoundException e) { // let caller handle this problem
+ return null;
+ } catch (Exception e) {
+ throw invalidTypeIdException(baseType, subClass, String.format(
+ "problem: (%s) %s",
+ e.getClass().getName(),
+ ClassUtil.exceptionMessage(e)));
+ }
+ if (baseType.isTypeOrSuperTypeOf(cls)) {
+ return getTypeFactory().constructSpecializedType(baseType, cls);
+ }
+ }
+ throw invalidTypeIdException(baseType, subClass, "Not a subtype");
+ }
+
+ /**
+ * Helper method for constructing exception to indicate that given type id
+ * could not be resolved to a valid subtype of specified base type.
+ * Most commonly called during polymorphic deserialization.
+ *<p>
+ * Note that most of the time this method should NOT be called directly: instead,
+ * method <code>handleUnknownTypeId()</code> should be called which will call this method
+ * if necessary.
+ *
+ * @since 2.9
+ */
+ protected abstract JsonMappingException invalidTypeIdException(JavaType baseType, String typeId,
+ String extraDesc);
+
public abstract TypeFactory getTypeFactory();
/*
@@ -224,4 +290,87 @@
}
return (Converter<Object,Object>) conv;
}
+
+ /*
+ /**********************************************************
+ /* Error reporting
+ /**********************************************************
+ */
+
+ /**
+ * Helper method called to indicate a generic problem that stems from type
+ * definition(s), not input data, or input/output state; typically this
+ * means throwing a {@link com.fasterxml.jackson.databind.exc.InvalidDefinitionException}.
+ *
+ * @since 2.9
+ */
+ public abstract <T> T reportBadDefinition(JavaType type, String msg) throws JsonMappingException;
+
+ /**
+ * @since 2.9
+ */
+ public <T> T reportBadDefinition(Class<?> type, String msg) throws JsonMappingException {
+ return reportBadDefinition(constructType(type), msg);
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ protected final String _format(String msg, Object... msgArgs) {
+ if (msgArgs.length > 0) {
+ return String.format(msg, msgArgs);
+ }
+ return msg;
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected final String _truncate(String desc) {
+ if (desc == null) {
+ return "";
+ }
+ if (desc.length() <= MAX_ERROR_STR_LEN) {
+ return desc;
+ }
+ return desc.substring(0, MAX_ERROR_STR_LEN) + "]...[" + desc.substring(desc.length() - MAX_ERROR_STR_LEN);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected String _quotedString(String desc) {
+ if (desc == null) {
+ return "[N/A]";
+ }
+ // !!! should we quote it? (in case there are control chars, linefeeds)
+ return String.format("\"%s\"", _truncate(desc));
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected String _colonConcat(String msgBase, String extra) {
+ if (extra == null) {
+ return msgBase;
+ }
+ return msgBase + ": " + extra;
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected String _desc(String desc) {
+ if (desc == null) {
+ return "[N/A]";
+ }
+ // !!! should we quote it? (in case there are control chars, linefeeds)
+ return _truncate(desc);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java
index 68556ad..8867935 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java
@@ -1,10 +1,7 @@
package com.fasterxml.jackson.databind;
-import java.text.DateFormat;
import java.util.*;
-import com.fasterxml.jackson.annotation.*;
-
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.cfg.*;
@@ -12,7 +9,6 @@
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.TypeFactory;
import com.fasterxml.jackson.databind.util.LinkedNode;
import com.fasterxml.jackson.databind.util.RootNameLookup;
@@ -29,22 +25,22 @@
extends MapperConfigBase<DeserializationFeature, DeserializationConfig>
implements java.io.Serializable // since 2.1
{
- // since 2.5
- private static final long serialVersionUID = 1;
+ // since 2.9
+ private static final long serialVersionUID = 2;
/*
/**********************************************************
/* Configured helper objects
/**********************************************************
*/
-
+
/**
* Linked list that contains all registered problem handlers.
* Implementation as front-added linked list allows for sharing
* of the list (tail) without copying the list.
*/
protected final LinkedNode<DeserializationProblemHandler> _problemHandlers;
-
+
/**
* Factory used for constructing {@link com.fasterxml.jackson.databind.JsonNode} instances.
*/
@@ -60,13 +56,13 @@
* Set of {@link DeserializationFeature}s enabled.
*/
protected final int _deserFeatures;
-
+
/*
/**********************************************************
/* Parser features: generic, format-specific
/**********************************************************
*/
-
+
/**
* States of {@link com.fasterxml.jackson.core.JsonParser.Feature}s to enable/disable.
*/
@@ -93,7 +89,7 @@
/*
/**********************************************************
- /* Life-cycle, constructors
+ /* Life-cycle, primary constructors for new instances
/**********************************************************
*/
@@ -101,8 +97,8 @@
* Constructor used by ObjectMapper to create default configuration object instance.
*/
public DeserializationConfig(BaseSettings base,
- SubtypeResolver str, SimpleMixInResolver mixins,
- RootNameLookup rootNames, ConfigOverrides configOverrides)
+ SubtypeResolver str, SimpleMixInResolver mixins, RootNameLookup rootNames,
+ ConfigOverrides configOverrides)
{
super(base, str, mixins, rootNames, configOverrides);
_deserFeatures = collectFeatureDefaults(DeserializationFeature.class);
@@ -115,14 +111,31 @@
}
/**
- * @deprecated Since 2.8, remove from 2.9 or later
+ * Copy-constructor used for making a copy used by new {@link ObjectMapper}.
+ *
+ * @since 2.9
*/
- @Deprecated
- public DeserializationConfig(BaseSettings base, SubtypeResolver str,
- SimpleMixInResolver mixins, RootNameLookup rootNames) {
- this(base, str, mixins, rootNames, null);
+ protected DeserializationConfig(DeserializationConfig src,
+ SimpleMixInResolver mixins, RootNameLookup rootNames,
+ ConfigOverrides configOverrides)
+ {
+ super(src, mixins, rootNames, configOverrides);
+ _deserFeatures = src._deserFeatures;
+ _problemHandlers = src._problemHandlers;
+ _nodeFactory = src._nodeFactory;
+ _parserFeatures = src._parserFeatures;
+ _parserFeaturesToChange = src._parserFeaturesToChange;
+ _formatReadFeatures = src._formatReadFeatures;
+ _formatReadFeaturesToChange = src._formatReadFeaturesToChange;
}
-
+
+ /*
+ /**********************************************************
+ /* Life-cycle, secondary constructors to support
+ /* "mutant factories", with single property changes
+ /**********************************************************
+ */
+
private DeserializationConfig(DeserializationConfig src,
int mapperFeatures, int deserFeatures,
int parserFeatures, int parserFeatureMask,
@@ -239,109 +252,37 @@
_formatReadFeaturesToChange = src._formatReadFeaturesToChange;
}
- /**
- * Copy-constructor used for making a copy used by new {@link ObjectMapper}.
- *
- * @since 2.8
- */
- protected DeserializationConfig(DeserializationConfig src, SimpleMixInResolver mixins,
- RootNameLookup rootNames, ConfigOverrides configOverrides)
- {
- super(src, mixins, rootNames, configOverrides);
- _deserFeatures = src._deserFeatures;
- _problemHandlers = src._problemHandlers;
- _nodeFactory = src._nodeFactory;
- _parserFeatures = src._parserFeatures;
- _parserFeaturesToChange = src._parserFeaturesToChange;
- _formatReadFeatures = src._formatReadFeatures;
- _formatReadFeaturesToChange = src._formatReadFeaturesToChange;
- }
-
// for unit tests only:
protected BaseSettings getBaseSettings() { return _base; }
/*
/**********************************************************
- /* Life-cycle, factory methods from MapperConfig
+ /* Life-cycle, general factory methods from MapperConfig(Base)
/**********************************************************
*/
-
- @Override
- public DeserializationConfig with(MapperFeature... features)
- {
- int newMapperFlags = _mapperFeatures;
- for (MapperFeature f : features) {
- newMapperFlags |= f.getMask();
- }
- return (newMapperFlags == _mapperFeatures) ? this :
- new DeserializationConfig(this, newMapperFlags, _deserFeatures,
- _parserFeatures, _parserFeaturesToChange,
- _formatReadFeatures, _formatReadFeaturesToChange);
-
+
+ @Override // since 2.9
+ protected final DeserializationConfig _withBase(BaseSettings newBase) {
+ return (_base == newBase) ? this : new DeserializationConfig(this, newBase);
}
- @Override
- public DeserializationConfig without(MapperFeature... features)
- {
- int newMapperFlags = _mapperFeatures;
- for (MapperFeature f : features) {
- newMapperFlags &= ~f.getMask();
- }
- return (newMapperFlags == _mapperFeatures) ? this :
- new DeserializationConfig(this, newMapperFlags, _deserFeatures,
- _parserFeatures, _parserFeaturesToChange,
- _formatReadFeatures, _formatReadFeaturesToChange);
+ @Override // since 2.9
+ protected final DeserializationConfig _withMapperFeatures(int mapperFeatures) {
+ return new DeserializationConfig(this, mapperFeatures, _deserFeatures,
+ _parserFeatures, _parserFeaturesToChange,
+ _formatReadFeatures, _formatReadFeaturesToChange);
}
- @Override
- public DeserializationConfig with(MapperFeature feature, boolean state)
- {
- int newMapperFlags;
- if (state) {
- newMapperFlags = _mapperFeatures | feature.getMask();
- } else {
- newMapperFlags = _mapperFeatures & ~feature.getMask();
- }
- return (newMapperFlags == _mapperFeatures) ? this :
- new DeserializationConfig(this, newMapperFlags, _deserFeatures,
- _parserFeatures, _parserFeaturesToChange,
- _formatReadFeatures, _formatReadFeaturesToChange);
- }
-
- @Override
- public DeserializationConfig with(ClassIntrospector ci) {
- return _withBase(_base.withClassIntrospector(ci));
- }
-
- @Override
- public DeserializationConfig with(AnnotationIntrospector ai) {
- return _withBase(_base.withAnnotationIntrospector(ai));
- }
-
- @Override
- public DeserializationConfig with(VisibilityChecker<?> vc) {
- return _withBase(_base.withVisibilityChecker(vc));
- }
-
- @Override
- public DeserializationConfig withVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility) {
- return _withBase( _base.withVisibility(forMethod, visibility));
- }
-
- @Override
- public DeserializationConfig with(TypeResolverBuilder<?> trb) {
- return _withBase(_base.withTypeResolverBuilder(trb));
- }
+ /*
+ /**********************************************************
+ /* Life-cycle, specific factory methods from MapperConfig
+ /**********************************************************
+ */
@Override
public DeserializationConfig with(SubtypeResolver str) {
return (_subtypeResolver == str) ? this : new DeserializationConfig(this, str);
}
-
- @Override
- public DeserializationConfig with(PropertyNamingStrategy pns) {
- return _withBase(_base.withPropertyNamingStrategy(pns));
- }
@Override
public DeserializationConfig withRootName(PropertyName rootName) {
@@ -356,58 +297,14 @@
}
@Override
- public DeserializationConfig with(TypeFactory tf) {
- return _withBase( _base.withTypeFactory(tf));
- }
-
- @Override
- public DeserializationConfig with(DateFormat df) {
- return _withBase(_base.withDateFormat(df));
- }
-
- @Override
- public DeserializationConfig with(HandlerInstantiator hi) {
- return _withBase(_base.withHandlerInstantiator(hi));
- }
-
- @Override
- public DeserializationConfig withInsertedAnnotationIntrospector(AnnotationIntrospector ai) {
- return _withBase(_base.withInsertedAnnotationIntrospector(ai));
- }
-
- @Override
- public DeserializationConfig withAppendedAnnotationIntrospector(AnnotationIntrospector ai) {
- return _withBase(_base.withAppendedAnnotationIntrospector(ai));
- }
-
- @Override
public DeserializationConfig withView(Class<?> view) {
return (_view == view) ? this : new DeserializationConfig(this, view);
}
@Override
- public DeserializationConfig with(Locale l) {
- return _withBase(_base.with(l));
- }
-
- @Override
- public DeserializationConfig with(TimeZone tz) {
- return _withBase(_base.with(tz));
- }
-
- @Override
- public DeserializationConfig with(Base64Variant base64) {
- return _withBase(_base.with(base64));
- }
-
- @Override
public DeserializationConfig with(ContextAttributes attrs) {
return (attrs == _attributes) ? this : new DeserializationConfig(this, attrs);
}
-
- private final DeserializationConfig _withBase(BaseSettings newBase) {
- return (_base == newBase) ? this : new DeserializationConfig(this, newBase);
- }
/*
/**********************************************************
@@ -734,84 +631,6 @@
/*
/**********************************************************
- /* MapperConfig implementation/overrides: introspection
- /**********************************************************
- */
-
- /**
- * Method for getting {@link AnnotationIntrospector} configured
- * to introspect annotation values used for configuration.
- */
- @Override
- public AnnotationIntrospector getAnnotationIntrospector()
- {
- /* 29-Jul-2009, tatu: it's now possible to disable use of
- * annotations; can be done using "no-op" introspector
- */
- if (isEnabled(MapperFeature.USE_ANNOTATIONS)) {
- return super.getAnnotationIntrospector();
- }
- return NopAnnotationIntrospector.instance;
- }
-
- /**
- * Accessor for getting bean description that only contains class
- * annotations: useful if no getter/setter/creator information is needed.
- */
- @Override
- public BeanDescription introspectClassAnnotations(JavaType type) {
- return getClassIntrospector().forClassAnnotations(this, type, this);
- }
-
- /**
- * Accessor for getting bean description that only contains immediate class
- * annotations: ones from the class, and its direct mix-in, if any, but
- * not from super types.
- */
- @Override
- public BeanDescription introspectDirectClassAnnotations(JavaType type) {
- return getClassIntrospector().forDirectClassAnnotations(this, type, this);
- }
-
- /*
- /**********************************************************
- /* Configuration: default settings with per-type overrides
- /**********************************************************
- */
-
- @Override
- public JsonInclude.Value getDefaultPropertyInclusion() {
- return EMPTY_INCLUDE;
- }
-
- @Override
- public JsonInclude.Value getDefaultPropertyInclusion(Class<?> baseType) {
- ConfigOverride overrides = findConfigOverride(baseType);
- if (overrides != null) {
- JsonInclude.Value v = overrides.getInclude();
- if (v != null) {
- return v;
- }
- }
- return EMPTY_INCLUDE;
- }
-
- @Override
- public JsonInclude.Value getDefaultPropertyInclusion(Class<?> baseType,
- JsonInclude.Value defaultIncl)
- {
- ConfigOverride overrides = findConfigOverride(baseType);
- if (overrides != null) {
- JsonInclude.Value v = overrides.getInclude();
- if (v != null) {
- return v;
- }
- }
- return defaultIncl;
- }
-
- /*
- /**********************************************************
/* MapperConfig implementation/overrides: other
/**********************************************************
*/
@@ -865,6 +684,18 @@
return _deserFeatures;
}
+ /**
+ * Convenience method equivalant to:
+ *<code>
+ * isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
+ *</code>
+ *
+ * @since 2.9
+ */
+ public final boolean requiresFullValue() {
+ return DeserializationFeature.FAIL_ON_TRAILING_TOKENS.enabledIn(_deserFeatures);
+ }
+
/*
/**********************************************************
/* Other configuration
@@ -949,19 +780,6 @@
} else {
subtypes = getSubtypeResolver().collectAndResolveSubtypesByTypeId(this, ac);
}
- /* 04-May-2014, tatu: When called from DeserializerFactory, additional code like
- * this is invoked. But here we do not actually have access to mappings, so not
- * quite sure what to do, if anything. May need to revisit if the underlying
- * problem re-surfaces...
- */
- /*
- if ((b.getDefaultImpl() == null) && baseType.isAbstract()) {
- JavaType defaultType = mapAbstractType(config, baseType);
- if (defaultType != null && defaultType.getRawClass() != baseType.getRawClass()) {
- b = b.defaultImpl(defaultType.getRawClass());
- }
- }
- */
return b.buildTypeDeserializer(this, baseType, subtypes);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
index 8ee553b..e2ff054 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
@@ -9,12 +9,16 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdResolver;
+
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.cfg.ContextAttributes;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId;
import com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
@@ -31,14 +35,14 @@
* Used to allow passing in configuration settings and reusable temporary
* objects (scrap arrays, containers).
*<p>
- * Instance life-cycle is such that an partially configured "blueprint" object
+ * Instance life-cycle is such that a partially configured "blueprint" object
* is registered with {@link ObjectMapper} (and {@link ObjectReader},
- * and when an actual instance is needed for deserialization,
- * a fully configured instance will
- * be created using a method in excented API of sub-class
+ * and when actual instance is needed for deserialization,
+ * a fully configured instance will be created using a method in extended internal
+ * API of sub-class
* ({@link com.fasterxml.jackson.databind.deser.DefaultDeserializationContext#createInstance}).
* Each instance is guaranteed to only be used from single-threaded context;
- * instances may be reused iff no configuration has changed.
+ * instances may be reused if (and only if) no configuration has changed.
*<p>
* Defined as abstract class so that implementations must define methods
* for reconfiguring blueprints and creating instances.
@@ -49,13 +53,6 @@
{
private static final long serialVersionUID = 1L; // 2.6
- /**
- * Let's limit length of error messages, for cases where underlying data
- * may be very large -- no point in spamming logs with megs of meaningless
- * data.
- */
- private final static int MAX_ERROR_STR_LEN = 500;
-
/*
/**********************************************************
/* Configuration, immutable
@@ -157,11 +154,13 @@
DeserializerCache cache)
{
if (df == null) {
- throw new IllegalArgumentException("Can not pass null DeserializerFactory");
+ throw new IllegalArgumentException("Cannot pass null DeserializerFactory");
}
_factory = df;
- _cache = (cache == null) ? new DeserializerCache() : cache;
-
+ if (cache == null) {
+ cache = new DeserializerCache();
+ }
+ _cache = cache;
_featureFlags = 0;
_config = null;
_injectableValues = null;
@@ -374,9 +373,11 @@
public final Object findInjectableValue(Object valueId,
BeanProperty forProperty, Object beanInstance)
+ throws JsonMappingException
{
if (_injectableValues == null) {
- throw new IllegalStateException("No 'injectableValues' configured, can not inject value with id ["+valueId+"]");
+ reportBadDefinition(ClassUtil.classOf(valueId), String.format(
+"No 'injectableValues' configured, cannot inject value with id [%s]", valueId));
}
return _injectableValues.findInjectableValue(valueId, this, forProperty, beanInstance);
}
@@ -539,7 +540,7 @@
* </pre>
*/
public final JavaType constructType(Class<?> cls) {
- return _config.constructType(cls);
+ return (cls == null) ? null : _config.constructType(cls);
}
/**
@@ -686,19 +687,6 @@
return deser;
}
- @Deprecated // since 2.5; remove from 2.9
- public JsonDeserializer<?> handlePrimaryContextualization(JsonDeserializer<?> deser, BeanProperty prop) throws JsonMappingException {
- return handlePrimaryContextualization(deser, prop, TypeFactory.unknownType());
- }
-
- @Deprecated // since 2.5; remove from 2.9
- public JsonDeserializer<?> handleSecondaryContextualization(JsonDeserializer<?> deser, BeanProperty prop) throws JsonMappingException {
- if (deser instanceof ContextualDeserializer) {
- deser = ((ContextualDeserializer) deser).createContextual(this, prop);
- }
- return deser;
- }
-
/*
/**********************************************************
/* Parsing methods that may use reusable/-cyclable objects
@@ -722,7 +710,8 @@
return df.parse(dateStr);
} catch (ParseException e) {
throw new IllegalArgumentException(String.format(
- "Failed to parse Date value '%s': %s", dateStr, e.getMessage()));
+ "Failed to parse Date value '%s': %s", dateStr,
+ ClassUtil.exceptionMessage(e)));
}
}
@@ -765,7 +754,8 @@
public <T> T readValue(JsonParser p, JavaType type) throws IOException {
JsonDeserializer<Object> deser = findRootValueDeserializer(type);
if (deser == null) {
- reportMappingException("Could not find JsonDeserializer for type %s", type);
+ reportBadDefinition(type,
+ "Could not find JsonDeserializer for type "+type);
}
return (T) deser.deserialize(p, this);
}
@@ -789,10 +779,9 @@
public <T> T readPropertyValue(JsonParser p, BeanProperty prop, JavaType type) throws IOException {
JsonDeserializer<Object> deser = findContextualValueDeserializer(type, prop);
if (deser == null) {
- String propName = (prop == null) ? "NULL" : ("'"+prop.getName()+"'");
- reportMappingException(
+ return reportBadDefinition(type, String.format(
"Could not find JsonDeserializer for type %s (via property %s)",
- type, propName);
+ type, ClassUtil.nameOf(prop)));
}
return (T) deser.deserialize(p, this);
}
@@ -837,7 +826,7 @@
/**
* Method that deserializers should call if they encounter a String value
- * that can not be converted to expected key of a {@link java.util.Map}
+ * that cannot be converted to expected key of a {@link java.util.Map}
* valued property.
* Default implementation will try to call {@link DeserializationProblemHandler#handleWeirdNumberValue}
* on configured handlers, if any, to allow for recovery; if recovery does not
@@ -859,9 +848,7 @@
throws IOException
{
// but if not handled, just throw exception
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
+ msg = _format(msg, msgArgs);
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
while (h != null) {
// Can bail out if it's handled
@@ -882,7 +869,7 @@
/**
* Method that deserializers should call if they encounter a String value
- * that can not be converted to target property type, in cases where some
+ * that cannot be converted to target property type, in cases where some
* String values could be acceptable (either with different settings,
* or different value).
* Default implementation will try to call {@link DeserializationProblemHandler#handleWeirdStringValue}
@@ -905,9 +892,7 @@
throws IOException
{
// but if not handled, just throw exception
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
+ msg = _format(msg, msgArgs);
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
while (h != null) {
// Can bail out if it's handled
@@ -928,7 +913,7 @@
/**
* Method that deserializers should call if they encounter a numeric value
- * that can not be converted to target property type, in cases where some
+ * that cannot be converted to target property type, in cases where some
* numeric values could be acceptable (either with different settings,
* or different numeric value).
* Default implementation will try to call {@link DeserializationProblemHandler#handleWeirdNumberValue}
@@ -950,9 +935,7 @@
String msg, Object... msgArgs)
throws IOException
{
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
+ msg = _format(msg, msgArgs);
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
while (h != null) {
// Can bail out if it's handled
@@ -962,7 +945,7 @@
if (_isCompatible(targetClass, key)) {
return key;
}
- throw weirdNumberException(value, targetClass, String.format(
+ throw weirdNumberException(value, targetClass, _format(
"DeserializationProblemHandler.handleWeirdNumberValue() for type %s returned value of type %s",
targetClass, key.getClass()));
}
@@ -971,6 +954,28 @@
throw weirdNumberException(value, targetClass, msg);
}
+ public Object handleWeirdNativeValue(JavaType targetType, Object badValue,
+ JsonParser p)
+ throws IOException
+ {
+ LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
+ final Class<?> raw = targetType.getRawClass();
+ for (; h != null; h = h.next()) {
+ // Can bail out if it's handled
+ Object goodValue = h.value().handleWeirdNativeValue(this, targetType, badValue, p);
+ if (goodValue != DeserializationProblemHandler.NOT_HANDLED) {
+ // Sanity check for broken handlers, otherwise nasty to debug:
+ if ((goodValue == null) || raw.isInstance(goodValue)) {
+ return goodValue;
+ }
+ throw JsonMappingException.from(p, _format(
+"DeserializationProblemHandler.handleWeirdNativeValue() for type %s returned value of type %s",
+targetType, goodValue.getClass()));
+ }
+ }
+ throw weirdNativeValueException(badValue, raw);
+ }
+
/**
* Method that deserializers should call if they fail to instantiate value
* due to lack of viable instantiator (usually creator, that is, constructor
@@ -980,36 +985,51 @@
* just skipping it) to keep input state valid
*
* @param instClass Type that was to be instantiated
+ * @param valueInst (optional) Value instantiator to be used, if any; null if type does not
+ * use one for instantiation (custom deserialiers don't; standard POJO deserializer does)
* @param p Parser that points to the JSON value to decode
*
* @return Object that should be constructed, if any; has to be of type <code>instClass</code>
*
- * @since 2.8
+ * @since 2.9 (2.8 had alternate that did not take <code>ValueInstantiator</code>)
*/
- public Object handleMissingInstantiator(Class<?> instClass, JsonParser p,
- String msg, Object... msgArgs)
+ @SuppressWarnings("resource")
+ public Object handleMissingInstantiator(Class<?> instClass, ValueInstantiator valueInst,
+ JsonParser p, String msg, Object... msgArgs)
throws IOException
{
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
+ if (p == null) {
+ p = getParser();
}
+ msg = _format(msg, msgArgs);
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
while (h != null) {
// Can bail out if it's handled
Object instance = h.value().handleMissingInstantiator(this,
- instClass, p, msg);
+ instClass, valueInst, p, msg);
if (instance != DeserializationProblemHandler.NOT_HANDLED) {
// Sanity check for broken handlers, otherwise nasty to debug:
if (_isCompatible(instClass, instance)) {
return instance;
}
- throw instantiationException(instClass, String.format(
- "DeserializationProblemHandler.handleMissingInstantiator() for type %s returned value of type %s",
- instClass, instance.getClass()));
+ reportBadDefinition(constructType(instClass), String.format(
+"DeserializationProblemHandler.handleMissingInstantiator() for type %s returned value of type %s",
+ instClass, ClassUtil.classNameOf(instance)));
}
h = h.next();
}
- throw instantiationException(instClass, msg);
+
+ // 16-Oct-2016, tatu: This is either a definition problem (if no applicable creator
+ // exists), or input mismatch problem (otherwise) since none of existing creators
+ // match with token.
+ if ((valueInst != null) && !valueInst.canInstantiate()) {
+ msg = String.format("Cannot construct instance of %s (no Creators, like default construct, exist): %s",
+ ClassUtil.nameOf(instClass), msg);
+ return reportBadDefinition(constructType(instClass), msg);
+ }
+ msg = String.format("Cannot construct instance of %s (although at least one Creator exists): %s",
+ ClassUtil.nameOf(instClass), msg);
+ return reportInputMismatch(instClass, msg);
}
/**
@@ -1042,23 +1062,21 @@
if (_isCompatible(instClass, instance)) {
return instance;
}
- throw instantiationException(instClass, String.format(
- "DeserializationProblemHandler.handleInstantiationProblem() for type %s returned value of type %s",
- instClass, instance.getClass()));
+ reportBadDefinition(constructType(instClass), String.format(
+"DeserializationProblemHandler.handleInstantiationProblem() for type %s returned value of type %s",
+ instClass, ClassUtil.classNameOf(instance)));
}
h = h.next();
}
// 18-May-2016, tatu: Only wrap if not already a valid type to throw
- if (t instanceof IOException) {
- throw (IOException) t;
- }
+ ClassUtil.throwIfIOE(t);
throw instantiationException(instClass, t);
}
/**
* Method that deserializers should call if the first token of the value to
* deserialize is of unexpected type (that is, type of token that deserializer
- * can not handle). This could occur, for example, if a Number deserializer
+ * cannot handle). This could occur, for example, if a Number deserializer
* encounter {@link JsonToken#START_ARRAY} instead of
* {@link JsonToken#VALUE_NUMBER_INT} or {@link JsonToken#VALUE_NUMBER_FLOAT}.
*
@@ -1074,15 +1092,16 @@
{
return handleUnexpectedToken(instClass, p.getCurrentToken(), p, null);
}
-
+
/**
* Method that deserializers should call if the first token of the value to
* deserialize is of unexpected type (that is, type of token that deserializer
- * can not handle). This could occur, for example, if a Number deserializer
+ * cannot handle). This could occur, for example, if a Number deserializer
* encounter {@link JsonToken#START_ARRAY} instead of
* {@link JsonToken#VALUE_NUMBER_INT} or {@link JsonToken#VALUE_NUMBER_FLOAT}.
*
* @param instClass Type that was to be instantiated
+ * @param t Token encountered that does match expected
* @param p Parser that points to the JSON value to decode
*
* @return Object that should be constructed, if any; has to be of type <code>instClass</code>
@@ -1090,13 +1109,10 @@
* @since 2.8
*/
public Object handleUnexpectedToken(Class<?> instClass, JsonToken t,
- JsonParser p,
- String msg, Object... msgArgs)
+ JsonParser p, String msg, Object... msgArgs)
throws IOException
{
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
+ msg = _format(msg, msgArgs);
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
while (h != null) {
Object instance = h.value().handleUnexpectedToken(this,
@@ -1105,31 +1121,32 @@
if (_isCompatible(instClass, instance)) {
return instance;
}
- reportMappingException("DeserializationProblemHandler.handleUnexpectedToken() for type %s returned value of type %s",
- instClass, instance.getClass());
+ reportBadDefinition(constructType(instClass), String.format(
+ "DeserializationProblemHandler.handleUnexpectedToken() for type %s returned value of type %s",
+ ClassUtil.nameOf(instClass), ClassUtil.classNameOf(instance)));
}
h = h.next();
}
if (msg == null) {
if (t == null) {
msg = String.format("Unexpected end-of-input when binding data into %s",
- _calcName(instClass));
+ ClassUtil.nameOf(instClass));
} else {
- msg = String.format("Can not deserialize instance of %s out of %s token",
- _calcName(instClass), t);
+ msg = String.format("Cannot deserialize instance of %s out of %s token",
+ ClassUtil.nameOf(instClass), t);
}
}
- reportMappingException(msg);
+ reportInputMismatch(instClass, msg);
return null; // never gets here
}
/**
* Method that deserializers should call if they encounter a type id
- * (for polymorphic deserialization) that can not be resolved to an
+ * (for polymorphic deserialization) that cannot be resolved to an
* actual type; usually since there is no mapping defined.
* Default implementation will try to call {@link DeserializationProblemHandler#handleUnknownTypeId}
* on configured handlers, if any, to allow for recovery; if recovery does not
- * succeed, will throw exception constructed with {@link #unknownTypeIdException}.
+ * succeed, will throw exception constructed with {@link #invalidTypeIdException}.
*
* @param baseType Base type from which resolution starts
* @param id Type id that could not be converted
@@ -1138,7 +1155,7 @@
*
* @return {@link JavaType} that id resolves to
*
- * @throws IOException To indicate unrecoverable problem, if resolution can not
+ * @throws IOException To indicate unrecoverable problem, if resolution cannot
* be made to work
*
* @since 2.8
@@ -1158,7 +1175,7 @@
if (type.isTypeOrSubTypeOf(baseType.getRawClass())) {
return type;
}
- throw unknownTypeIdException(baseType, id,
+ throw invalidTypeIdException(baseType, id,
"problem handler tried to resolve into non-subtype: "+type);
}
h = h.next();
@@ -1167,7 +1184,38 @@
if (!isEnabled(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE)) {
return null;
}
- throw unknownTypeIdException(baseType, id, extraDesc);
+ throw invalidTypeIdException(baseType, id, extraDesc);
+ }
+
+ /**
+ * @since 2.9
+ */
+ public JavaType handleMissingTypeId(JavaType baseType,
+ TypeIdResolver idResolver, String extraDesc) throws IOException
+ {
+ LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
+ while (h != null) {
+ // Can bail out if it's handled
+ JavaType type = h.value().handleMissingTypeId(this, baseType, idResolver, extraDesc);
+ if (type != null) {
+ if (type.hasRawClass(Void.class)) {
+ return null;
+ }
+ // But ensure there's type compatibility
+ if (type.isTypeOrSubTypeOf(baseType.getRawClass())) {
+ return type;
+ }
+ throw invalidTypeIdException(baseType, null,
+ "problem handler tried to resolve into non-subtype: "+type);
+ }
+ h = h.next();
+ }
+ // 09-Mar-2017, tatu: We may want to consider yet another feature at some
+ // point to allow returning `null`... but that seems bit risky for now
+// if (!isEnabled(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE)) {
+// return null;
+// }
+ throw missingTypeIdException(baseType, extraDesc);
}
/**
@@ -1186,7 +1234,7 @@
/*
/**********************************************************
/* Methods for problem reporting, in cases where recovery
- /* is not considered possible
+ /* is not considered possible: input problem
/**********************************************************
*/
@@ -1198,15 +1246,131 @@
* recovery is attempted (via {@link DeserializationProblemHandler}, as
* problem is considered to be difficult to recover from, in general.
*
+ * @since 2.9
+ */
+ public void reportWrongTokenException(JsonDeserializer<?> deser,
+ JsonToken expToken, String msg, Object... msgArgs)
+ throws JsonMappingException
+ {
+ msg = _format(msg, msgArgs);
+ throw wrongTokenException(getParser(), deser.handledType(), expToken, msg);
+ }
+
+ /**
+ * Method for deserializers to call
+ * when the token encountered was of type different than what <b>should</b>
+ * be seen at that position, usually within a sequence of expected tokens.
+ * Note that this method will throw a {@link JsonMappingException} and no
+ * recovery is attempted (via {@link DeserializationProblemHandler}, as
+ * problem is considered to be difficult to recover from, in general.
+ *
+ * @since 2.9
+ */
+ public void reportWrongTokenException(JavaType targetType,
+ JsonToken expToken, String msg, Object... msgArgs)
+ throws JsonMappingException
+ {
+ msg = _format(msg, msgArgs);
+ throw wrongTokenException(getParser(), targetType, expToken, msg);
+ }
+
+ /**
+ * Method for deserializers to call
+ * when the token encountered was of type different than what <b>should</b>
+ * be seen at that position, usually within a sequence of expected tokens.
+ * Note that this method will throw a {@link JsonMappingException} and no
+ * recovery is attempted (via {@link DeserializationProblemHandler}, as
+ * problem is considered to be difficult to recover from, in general.
+ *
+ * @since 2.9
+ */
+ public void reportWrongTokenException(Class<?> targetType,
+ JsonToken expToken, String msg, Object... msgArgs)
+ throws JsonMappingException
+ {
+ msg = _format(msg, msgArgs);
+ throw wrongTokenException(getParser(), targetType, expToken, msg);
+ }
+
+ /**
* @since 2.8
*/
+ public <T> T reportUnresolvedObjectId(ObjectIdReader oidReader, Object bean)
+ throws JsonMappingException
+ {
+ String msg = String.format("No Object Id found for an instance of %s, to assign to property '%s'",
+ ClassUtil.classNameOf(bean), oidReader.propertyName);
+ return reportInputMismatch(oidReader.idProperty, msg);
+ }
+
+ /**
+ * Helper method used to indicate a problem with input in cases where more
+ * specific <code>reportXxx()</code> method was not available.
+ *
+ * @since 2.9
+ */
+ public <T> T reportInputMismatch(BeanProperty prop,
+ String msg, Object... msgArgs) throws JsonMappingException
+ {
+ msg = _format(msg, msgArgs);
+ JavaType type = (prop == null) ? null : prop.getType();
+ throw MismatchedInputException.from(getParser(), type, msg);
+ }
+
+ /**
+ * Helper method used to indicate a problem with input in cases where more
+ * specific <code>reportXxx()</code> method was not available.
+ *
+ * @since 2.9
+ */
+ public <T> T reportInputMismatch(JsonDeserializer<?> src,
+ String msg, Object... msgArgs) throws JsonMappingException
+ {
+ msg = _format(msg, msgArgs);
+ throw MismatchedInputException.from(getParser(), src.handledType(), msg);
+ }
+
+ /**
+ * Helper method used to indicate a problem with input in cases where more
+ * specific <code>reportXxx()</code> method was not available.
+ *
+ * @since 2.9
+ */
+ public <T> T reportInputMismatch(Class<?> targetType,
+ String msg, Object... msgArgs) throws JsonMappingException
+ {
+ msg = _format(msg, msgArgs);
+ throw MismatchedInputException.from(getParser(), targetType, msg);
+ }
+
+ /**
+ * Helper method used to indicate a problem with input in cases where more
+ * specific <code>reportXxx()</code> method was not available.
+ *
+ * @since 2.9
+ */
+ public <T> T reportInputMismatch(JavaType targetType,
+ String msg, Object... msgArgs) throws JsonMappingException
+ {
+ msg = _format(msg, msgArgs);
+ throw MismatchedInputException.from(getParser(), targetType, msg);
+ }
+
+ public <T> T reportTrailingTokens(Class<?> targetType,
+ JsonParser p, JsonToken trailingToken) throws JsonMappingException
+ {
+ throw MismatchedInputException.from(p, targetType, String.format(
+"Trailing token (of type %s) found after value (bound as %s): not allowed as per `DeserializationFeature.FAIL_ON_TRAILING_TOKENS`",
+trailingToken, ClassUtil.nameOf(targetType)
+ ));
+ }
+
+ @Deprecated // since 2.9
public void reportWrongTokenException(JsonParser p,
JsonToken expToken, String msg, Object... msgArgs)
throws JsonMappingException
{
- if ((msg != null) && (msgArgs.length > 0)) {
- msg = String.format(msg, msgArgs);
- }
+ msg = _format(msg, msgArgs);
throw wrongTokenException(p, expToken, msg);
}
@@ -1226,52 +1390,31 @@
JsonDeserializer<?> deser)
throws JsonMappingException
{
- if (!isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
- return;
+ if (isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
+ // Do we know properties that are expected instead?
+ Collection<Object> propIds = (deser == null) ? null : deser.getKnownPropertyNames();
+ throw UnrecognizedPropertyException.from(_parser,
+ instanceOrClass, fieldName, propIds);
}
- // Do we know properties that are expected instead?
- Collection<Object> propIds = (deser == null) ? null : deser.getKnownPropertyNames();
- throw UnrecognizedPropertyException.from(_parser,
- instanceOrClass, fieldName, propIds);
}
/**
* @since 2.8
+ *
+ * @deprecated Since 2.9: not clear this ever occurs
*/
- public void reportMappingException(String msg, Object... msgArgs)
- throws JsonMappingException
- {
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
- throw JsonMappingException.from(getParser(), msg);
+ @Deprecated // since 2.9
+ public void reportMissingContent(String msg, Object... msgArgs) throws JsonMappingException {
+ throw MismatchedInputException.from(getParser(), (JavaType) null, "No content to map due to end-of-input");
}
- /**
- * @since 2.8
+ /*
+ /**********************************************************
+ /* Methods for problem reporting, in cases where recovery
+ /* is not considered possible: POJO definition problems
+ /**********************************************************
*/
- public void reportMissingContent(String msg, Object... msgArgs)
- throws JsonMappingException
- {
- if (msg == null) {
- msg = "No content to map due to end-of-input";
- } else if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
- throw JsonMappingException.from(getParser(), msg);
- }
-
- /**
- * @since 2.8
- */
- public void reportUnresolvedObjectId(ObjectIdReader oidReader, Object bean)
- throws JsonMappingException
- {
- String msg = String.format("No Object Id found for an instance of %s, to assign to property '%s'",
- bean.getClass().getName(), oidReader.propertyName);
- throw JsonMappingException.from(getParser(), msg);
- }
-
+
/**
* Helper method called to indicate problem in POJO (serialization) definitions or settings
* regarding specific Java type, unrelated to actual JSON content to map.
@@ -1280,13 +1423,11 @@
* @since 2.9
*/
public <T> T reportBadTypeDefinition(BeanDescription bean,
- String message, Object... args) throws JsonMappingException {
- if (args != null && args.length > 0) {
- message = String.format(message, args);
- }
- String beanDesc = (bean == null) ? "N/A" : _desc(bean.getType().getGenericSignature());
- throw mappingException("Invalid type definition for type %s: %s",
- beanDesc, message);
+ String msg, Object... msgArgs) throws JsonMappingException {
+ msg = _format(msg, msgArgs);
+ String beanDesc = ClassUtil.nameOf(bean.getBeanClass());
+ msg = String.format("Invalid type definition for type %s: %s", beanDesc, msg);
+ throw InvalidDefinitionException.from(_parser, msg, bean, null);
}
/**
@@ -1297,70 +1438,39 @@
* @since 2.9
*/
public <T> T reportBadPropertyDefinition(BeanDescription bean, BeanPropertyDefinition prop,
- String message, Object... args) throws JsonMappingException {
- if (args != null && args.length > 0) {
- message = String.format(message, args);
+ String msg, Object... msgArgs) throws JsonMappingException {
+ msg = _format(msg, msgArgs);
+ String propName = ClassUtil.nameOf(prop);
+ String beanDesc = ClassUtil.nameOf(bean.getBeanClass());
+ msg = String.format("Invalid definition for property %s (of type %s): %s",
+ propName, beanDesc, msg);
+ throw InvalidDefinitionException.from(_parser, msg, bean, prop);
+ }
+
+ @Override
+ public <T> T reportBadDefinition(JavaType type, String msg) throws JsonMappingException {
+ throw InvalidDefinitionException.from(_parser, msg, type);
+ }
+
+ /**
+ * Method that deserializer may call if it is called to do an update ("merge")
+ * but deserializer operates on a non-mergeable type. Although this should
+ * usually be caught earlier, sometimes it may only be caught during operation
+ * and if so this is the method to call.
+ * Note that if {@link MapperFeature#IGNORE_MERGE_FOR_UNMERGEABLE} is enabled,
+ * this method will simply return null; otherwise {@link InvalidDefinitionException}
+ * will be thrown.
+ *
+ * @since 2.9
+ */
+ public <T> T reportBadMerge(JsonDeserializer<?> deser) throws JsonMappingException
+ {
+ if (isEnabled(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)) {
+ return null;
}
- String propName = (prop == null) ? "N/A" : _quotedString(prop.getName());
- String beanDesc = (bean == null) ? "N/A" : _desc(bean.getType().getGenericSignature());
- throw mappingException("Invalid definition for property %s (of type %s): %s",
- propName, beanDesc, message);
- }
-
- /*
- /**********************************************************
- /* Methods for constructing exceptions, "untyped"
- /**********************************************************
- */
-
- /**
- * Helper method for constructing generic mapping exception with specified
- * message and current location information.
- * Note that application code should almost always call
- * one of <code>handleXxx</code> methods, or {@link #reportMappingException(String, Object...)}
- * instead.
- *
- * @since 2.6
- */
- public JsonMappingException mappingException(String message) {
- return JsonMappingException.from(getParser(), message);
- }
-
- /**
- * Helper method for constructing generic mapping exception with specified
- * message and current location information
- * Note that application code should almost always call
- * one of <code>handleXxx</code> methods, or {@link #reportMappingException(String, Object...)}
- * instead.
- *
- * @since 2.6
- */
- public JsonMappingException mappingException(String msgTemplate, Object... args) {
- if (args != null && args.length > 0) {
- msgTemplate = String.format(msgTemplate, args);
- }
- return JsonMappingException.from(getParser(), msgTemplate);
- }
-
- /**
- * Helper method for constructing generic mapping exception for specified type
- *
- * @deprecated Since 2.8 use {@link #handleUnexpectedToken(Class, JsonParser)} instead
- */
- @Deprecated
- public JsonMappingException mappingException(Class<?> targetClass) {
- return mappingException(targetClass, _parser.getCurrentToken());
- }
-
- /**
- * @deprecated Since 2.8 use {@link #handleUnexpectedToken(Class, JsonParser)} instead
- */
- @Deprecated
- public JsonMappingException mappingException(Class<?> targetClass, JsonToken token) {
- String tokenDesc = (token == null) ? "<end of input>" : String.format("%s token", token);
- return JsonMappingException.from(_parser,
- String.format("Can not deserialize instance of %s out of %s",
- _calcName(targetClass), tokenDesc));
+ JavaType type = constructType(deser.handledType());
+ String msg = String.format("Invalid configuration: values of type %s cannot be merged", type);
+ throw InvalidDefinitionException.from(getParser(), msg, type);
}
/*
@@ -1377,16 +1487,32 @@
* Note that most of the time this method should NOT be directly called;
* instead, {@link #reportWrongTokenException} should be called and will
* call this method as necessary.
+ *
+ * @since 2.9
*/
- public JsonMappingException wrongTokenException(JsonParser p, JsonToken expToken,
- String msg0)
+ public JsonMappingException wrongTokenException(JsonParser p, JavaType targetType,
+ JsonToken expToken, String extra)
{
String msg = String.format("Unexpected token (%s), expected %s",
p.getCurrentToken(), expToken);
- if (msg0 != null) {
- msg = msg + ": "+msg0;
- }
- return JsonMappingException.from(p, msg);
+ msg = _colonConcat(msg, extra);
+ return MismatchedInputException.from(p, targetType, msg);
+ }
+
+ public JsonMappingException wrongTokenException(JsonParser p, Class<?> targetType,
+ JsonToken expToken, String extra)
+ {
+ String msg = String.format("Unexpected token (%s), expected %s",
+ p.getCurrentToken(), expToken);
+ msg = _colonConcat(msg, extra);
+ return MismatchedInputException.from(p, targetType, msg);
+ }
+
+ @Deprecated // since 2.9
+ public JsonMappingException wrongTokenException(JsonParser p, JsonToken expToken,
+ String msg)
+ {
+ return wrongTokenException(p, (JavaType) null, expToken, msg);
}
/**
@@ -1400,8 +1526,8 @@
public JsonMappingException weirdKeyException(Class<?> keyClass, String keyValue,
String msg) {
return InvalidFormatException.from(_parser,
- String.format("Can not deserialize Map key of type %s from String %s: %s",
- keyClass.getName(), _quotedString(keyValue), msg),
+ String.format("Cannot deserialize Map key of type %s from String %s: %s",
+ ClassUtil.nameOf(keyClass), _quotedString(keyValue), msg),
keyValue, keyClass);
}
@@ -1421,8 +1547,8 @@
public JsonMappingException weirdStringException(String value, Class<?> instClass,
String msg) {
return InvalidFormatException.from(_parser,
- String.format("Can not deserialize value of type %s from String %s: %s",
- instClass.getName(), _quotedString(value), msg),
+ String.format("Cannot deserialize value of type %s from String %s: %s",
+ ClassUtil.nameOf(instClass), _quotedString(value), msg),
value, instClass);
}
@@ -1436,8 +1562,26 @@
public JsonMappingException weirdNumberException(Number value, Class<?> instClass,
String msg) {
return InvalidFormatException.from(_parser,
- String.format("Can not deserialize value of type %s from number %s: %s",
- instClass.getName(), String.valueOf(value), msg),
+ String.format("Cannot deserialize value of type %s from number %s: %s",
+ ClassUtil.nameOf(instClass), String.valueOf(value), msg),
+ value, instClass);
+ }
+
+ /**
+ * Helper method for constructing exception to indicate that input JSON
+ * token of type "native value" (see {@link JsonToken#VALUE_EMBEDDED_OBJECT})
+ * is of incompatible type (and there is no delegating creator or such to use)
+ * and can not be used to construct value of specified type (usually POJO).
+ * Note that most of the time this method should NOT be called; instead,
+ * {@link #handleWeirdNativeValue} should be called which will call this method
+ *
+ * @since 2.9
+ */
+ public JsonMappingException weirdNativeValueException(Object value, Class<?> instClass)
+ {
+ return InvalidFormatException.from(_parser, String.format(
+"Cannot deserialize value of type %s from native value (`JsonToken.VALUE_EMBEDDED_OBJECT`) of type %s: incompatible types",
+ ClassUtil.nameOf(instClass), ClassUtil.classNameOf(value)),
value, instClass);
}
@@ -1446,14 +1590,24 @@
* to indicate problem with physically constructing instance of
* specified class (missing constructor, exception from constructor)
*<p>
- * Note that most of the time this method should NOT be called; instead,
+ * Note that most of the time this method should NOT be called directly; instead,
* {@link #handleInstantiationProblem} should be called which will call this method
* if necessary.
*/
- public JsonMappingException instantiationException(Class<?> instClass, Throwable t) {
- return JsonMappingException.from(_parser,
- String.format("Can not construct instance of %s, problem: %s",
- instClass.getName(), t.getMessage()), t);
+ public JsonMappingException instantiationException(Class<?> instClass, Throwable cause) {
+ // Most likely problem with Creator definition, right?
+ final JavaType type = constructType(instClass);
+ String excMsg;
+ if (cause == null) {
+ excMsg = "N/A";
+ } else if ((excMsg = ClassUtil.exceptionMessage(cause)) == null) {
+ excMsg = ClassUtil.nameOf(cause.getClass());
+ }
+ String msg = String.format("Cannot construct instance of %s, problem: %s",
+ ClassUtil.nameOf(instClass), excMsg);
+ InvalidDefinitionException e = InvalidDefinitionException.from(_parser, msg, type);
+ e.initCause(cause);
+ return e;
}
/**
@@ -1465,29 +1619,30 @@
* {@link #handleMissingInstantiator} should be called which will call this method
* if necessary.
*/
- public JsonMappingException instantiationException(Class<?> instClass, String msg) {
- return JsonMappingException.from(_parser,
- String.format("Can not construct instance of %s: %s",
- instClass.getName(), msg));
+ public JsonMappingException instantiationException(Class<?> instClass, String msg0) {
+ // Most likely problem with Creator definition, right?
+ JavaType type = constructType(instClass);
+ String msg = String.format("Cannot construct instance of %s: %s",
+ ClassUtil.nameOf(instClass), msg0);
+ return InvalidDefinitionException.from(_parser, msg, type);
+ }
+
+ @Override
+ public JsonMappingException invalidTypeIdException(JavaType baseType, String typeId,
+ String extraDesc) {
+ String msg = String.format("Could not resolve type id '%s' as a subtype of %s",
+ typeId, baseType);
+ return InvalidTypeIdException.from(_parser, _colonConcat(msg, extraDesc), baseType, typeId);
}
/**
- * Helper method for constructing exception to indicate that given type id
- * could not be resolved to a valid subtype of specified base type, during
- * polymorphic deserialization.
- *<p>
- * Note that most of the time this method should NOT be called; instead,
- * {@link #handleUnknownTypeId} should be called which will call this method
- * if necessary.
+ * @since 2.9
*/
- public JsonMappingException unknownTypeIdException(JavaType baseType, String typeId,
+ public JsonMappingException missingTypeIdException(JavaType baseType,
String extraDesc) {
- String msg = String.format("Could not resolve type id '%s' into a subtype of %s",
- typeId, baseType);
- if (extraDesc != null) {
- msg = msg + ": "+extraDesc;
- }
- return InvalidTypeIdException.from(_parser, msg, baseType, typeId);
+ String msg = String.format("Missing type id when trying to resolve subtype of %s",
+ baseType);
+ return InvalidTypeIdException.from(_parser, _colonConcat(msg, extraDesc), baseType, null);
}
/*
@@ -1503,13 +1658,12 @@
*/
@Deprecated
public JsonMappingException unknownTypeException(JavaType type, String id,
- String extraDesc) {
+ String extraDesc)
+ {
String msg = String.format("Could not resolve type id '%s' into a subtype of %s",
id, type);
- if (extraDesc != null) {
- msg = msg + ": "+extraDesc;
- }
- return JsonMappingException.from(_parser, msg);
+ msg = _colonConcat(msg, extraDesc);
+ return MismatchedInputException.from(_parser, type, msg);
}
/**
@@ -1520,8 +1674,84 @@
*/
@Deprecated
public JsonMappingException endOfInputException(Class<?> instClass) {
- return JsonMappingException.from(_parser, "Unexpected end-of-input when trying to deserialize a "
- +instClass.getName());
+ return MismatchedInputException.from(_parser, instClass,
+ "Unexpected end-of-input when trying to deserialize a "+instClass.getName());
+ }
+
+ /*
+ /**********************************************************
+ /* Deprecated methods for constructing, throwing non-specific
+ /* JsonMappingExceptions: as of 2.9, should use more specific
+ /* ones.
+ /**********************************************************
+ */
+
+ /**
+ * Fallback method that may be called if no other <code>reportXxx</code>
+ * is applicable -- but only in that case.
+ *
+ * @since 2.8
+ *
+ * @deprecated Since 2.9: use a more specific method, or {@link #reportBadDefinition(JavaType, String)},
+ * or {@link #reportInputMismatch} instead
+ */
+ @Deprecated // since 2.9
+ public void reportMappingException(String msg, Object... msgArgs)
+ throws JsonMappingException
+ {
+ throw JsonMappingException.from(getParser(), _format(msg, msgArgs));
+ }
+
+ /**
+ * Helper method for constructing generic mapping exception with specified
+ * message and current location information.
+ * Note that application code should almost always call
+ * one of <code>handleXxx</code> methods, or {@link #reportMappingException(String, Object...)}
+ * instead.
+ *
+ * @since 2.6
+ *
+ * @deprecated Since 2.9 use more specific error reporting methods instead
+ */
+ @Deprecated
+ public JsonMappingException mappingException(String message) {
+ return JsonMappingException.from(getParser(), message);
+ }
+
+ /**
+ * Helper method for constructing generic mapping exception with specified
+ * message and current location information
+ * Note that application code should almost always call
+ * one of <code>handleXxx</code> methods, or {@link #reportMappingException(String, Object...)}
+ * instead.
+ *
+ * @since 2.6
+ *
+ * @deprecated Since 2.9 use more specific error reporting methods instead
+ */
+ @Deprecated
+ public JsonMappingException mappingException(String msg, Object... msgArgs) {
+ return JsonMappingException.from(getParser(), _format(msg, msgArgs));
+ }
+
+ /**
+ * Helper method for constructing generic mapping exception for specified type
+ *
+ * @deprecated Since 2.8 use {@link #handleUnexpectedToken(Class, JsonParser)} instead
+ */
+ @Deprecated
+ public JsonMappingException mappingException(Class<?> targetClass) {
+ return mappingException(targetClass, _parser.getCurrentToken());
+ }
+
+ /**
+ * @deprecated Since 2.8 use {@link #handleUnexpectedToken(Class, JsonParser)} instead
+ */
+ @Deprecated
+ public JsonMappingException mappingException(Class<?> targetClass, JsonToken token) {
+ return JsonMappingException.from(_parser,
+ String.format("Cannot deserialize instance of %s out of %s token",
+ ClassUtil.nameOf(targetClass), token));
}
/*
@@ -1544,48 +1774,4 @@
_dateFormat = df = (DateFormat) df.clone();
return df;
}
-
- protected String determineClassName(Object instance) {
- return ClassUtil.getClassDescription(instance);
- }
-
- protected String _calcName(Class<?> cls) {
- if (cls.isArray()) {
- return _calcName(cls.getComponentType())+"[]";
- }
- return cls.getName();
- }
-
- protected String _valueDesc() {
- try {
- return _desc(_parser.getText());
- } catch (Exception e) {
- return "[N/A]";
- }
- }
-
- protected String _desc(String desc) {
- if (desc == null) {
- return "[N/A]";
- }
- // !!! should we quote it? (in case there are control chars, linefeeds)
- if (desc.length() > MAX_ERROR_STR_LEN) {
- desc = desc.substring(0, MAX_ERROR_STR_LEN) + "]...[" + desc.substring(desc.length() - MAX_ERROR_STR_LEN);
- }
- return desc;
- }
-
- // @since 2.7
- protected String _quotedString(String desc) {
- if (desc == null) {
- return "[N/A]";
- }
- // !!! should we quote it? (in case there are control chars, linefeeds)
- if (desc.length() > MAX_ERROR_STR_LEN) {
- return String.format("\"%s]...[%s\"",
- desc.substring(0, MAX_ERROR_STR_LEN),
- desc.substring(desc.length() - MAX_ERROR_STR_LEN));
- }
- return "\"" + desc + "\"";
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
index a29272d..90f2b35 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
@@ -21,7 +21,7 @@
{
/*
/******************************************************
- /* Type conversion features
+ /* Value (mostly scalar) conversion features
/******************************************************
*/
@@ -92,23 +92,10 @@
* {@link java.util.List}s.
*/
USE_JAVA_ARRAY_FOR_JSON_ARRAY(false),
-
- /**
- * Feature that determines standard deserialization mechanism used for
- * Enum values: if enabled, Enums are assumed to have been serialized using
- * return value of <code>Enum.toString()</code>;
- * if disabled, return value of {@code Enum.name()} is assumed to have been used.
- *<p>
- * Note: this feature should usually have same value
- * as {@link SerializationFeature#WRITE_ENUMS_USING_TO_STRING}.
- *<p>
- * Feature is disabled by default.
- */
- READ_ENUMS_USING_TO_STRING(false),
-
+
/*
/******************************************************
- * Error handling features
+ /* Error handling features
/******************************************************
*/
@@ -156,7 +143,7 @@
/**
* Feature that determines what happens when type of a polymorphic
* value (indicated for example by {@link com.fasterxml.jackson.annotation.JsonTypeInfo})
- * can not be found (missing) or resolved (invalid class name, unmappable id);
+ * cannot be found (missing) or resolved (invalid class name, unmappable id);
* if enabled, an exception ir thrown; if false, null value is used instead.
*<p>
* Feature is enabled by default so that exception is thrown for missing or invalid
@@ -259,6 +246,26 @@
FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY(true),
/**
+ * Feature that determines behaviour for data-binding after binding the root value.
+ * If feature is enabled, one more call to
+ * {@link com.fasterxml.jackson.core.JsonParser#nextToken} is made to ensure that
+ * no more tokens are found (and if any is found,
+ * {@link com.fasterxml.jackson.databind.exc.MismatchedInputException} is thrown); if
+ * disabled, no further checks are made.
+ *<p>
+ * Feature could alternatively be called <code>READ_FULL_STREAM</code>, since it
+ * effectively verifies that input stream contains only as much data as is needed
+ * for binding the full value, and nothing more (except for possible ignorable
+ * white space or comments, if supported by data format).
+ *<p>
+ * Feature is disabled by default (so that no check is made for possible trailing
+ * token(s)) for backwards compatibility reasons.
+ *
+ * @since 2.9
+ */
+ FAIL_ON_TRAILING_TOKENS(false),
+
+ /**
* Feature that determines whether Jackson code should catch
* and wrap {@link Exception}s (but never {@link Error}s!)
* to add additional information about
@@ -325,21 +332,27 @@
/**
* Feature that can be enabled to allow JSON empty String
- * value ("") to be bound to POJOs as null.
- * If disabled, standard POJOs can only be bound from JSON null or
+ * value ("") to be bound as `null` for POJOs and other structured
+ * values ({@link java.util.Map}s, {@link java.util.Collection}s).
+ * If disabled, standard POJOs can only be bound from JSON `null` or
* JSON Object (standard meaning that no custom deserializers or
* constructors are defined; both of which can add support for other
* kinds of JSON values); if enabled, empty JSON String can be taken
* to be equivalent of JSON null.
*<p>
+ * NOTE: this does NOT apply to scalar values such as booleans and numbers;
+ * whether they can be coerced depends on
+ * {@link MapperFeature#ALLOW_COERCION_OF_SCALARS}.
+ *<p>
* Feature is disabled by default.
*/
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT(false),
/**
* Feature that can be enabled to allow empty JSON Array
- * value (that is, <code>[ ]</code>) to be bound to POJOs as null.
- * If disabled, standard POJOs can only be bound from JSON null or
+ * value (that is, <code>[ ]</code>) to be bound to POJOs (and
+ * with 2.9, other values too) as `null`.
+ * If disabled, standard POJOs can only be bound from JSON `null` or
* JSON Object (standard meaning that no custom deserializers or
* constructors are defined; both of which can add support for other
* kinds of JSON values); if enabled, empty JSON Array will be taken
@@ -364,7 +377,20 @@
* @since 2.6
*/
ACCEPT_FLOAT_AS_INT(true),
-
+
+ /**
+ * Feature that determines standard deserialization mechanism used for
+ * Enum values: if enabled, Enums are assumed to have been serialized using
+ * return value of <code>Enum.toString()</code>;
+ * if disabled, return value of <code>Enum.name()</code> is assumed to have been used.
+ *<p>
+ * Note: this feature should usually have same value
+ * as {@link SerializationFeature#WRITE_ENUMS_USING_TO_STRING}.
+ *<p>
+ * Feature is disabled by default.
+ */
+ READ_ENUMS_USING_TO_STRING(false),
+
/**
* Feature that allows unknown Enum values to be parsed as null values.
* If disabled, unknown Enum values will throw exceptions.
@@ -420,9 +446,14 @@
* Note that exact behavior depends on date/time types in question; and specifically
* JDK type of {@link java.util.Date} does NOT have in-built timezone information
* so this setting has no effect.
+ * Further, while {@link java.util.Calendar} does have this information basic
+ * JDK {@link java.text.SimpleDateFormat} is unable to retain parsed zone information,
+ * and as a result, {@link java.util.Calendar} will always get context timezone
+ * adjustment regardless of this setting.
*<p>
- * As of Jackson 2.8, this feature is supported only by extension modules for Joda
- * and Java 8 date/tyime datatypes.
+ *<p>
+ * Taking above into account, this feature is supported only by extension modules for
+ * Joda and Java 8 date/tyime datatypes.
*
* @since 2.2
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/InjectableValues.java b/src/main/java/com/fasterxml/jackson/databind/InjectableValues.java
index a4da378..9f773c3 100644
--- a/src/main/java/com/fasterxml/jackson/databind/InjectableValues.java
+++ b/src/main/java/com/fasterxml/jackson/databind/InjectableValues.java
@@ -2,6 +2,8 @@
import java.util.*;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
/**
* Abstract class that defines API for objects that provide value to
* "inject" during deserialization. An instance of this object
@@ -23,7 +25,7 @@
* if available; null if bean has not yet been constructed.
*/
public abstract Object findInjectableValue(Object valueId, DeserializationContext ctxt,
- BeanProperty forProperty, Object beanInstance);
+ BeanProperty forProperty, Object beanInstance) throws JsonMappingException;
/*
/**********************************************************
@@ -63,11 +65,13 @@
@Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt,
- BeanProperty forProperty, Object beanInstance)
+ BeanProperty forProperty, Object beanInstance) throws JsonMappingException
{
if (!(valueId instanceof String)) {
- String type = (valueId == null) ? "[null]" : valueId.getClass().getName();
- throw new IllegalArgumentException("Unrecognized inject value id type ("+type+"), expecting String");
+ ctxt.reportBadDefinition(ClassUtil.classOf(valueId),
+ String.format(
+ "Unrecognized inject value id type (%s), expecting String",
+ ClassUtil.classNameOf(valueId)));
}
String key = (String) valueId;
Object ob = _values.get(key);
diff --git a/src/main/java/com/fasterxml/jackson/databind/JavaType.java b/src/main/java/com/fasterxml/jackson/databind/JavaType.java
index 292cc39..3fdbb2b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JavaType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JavaType.java
@@ -211,15 +211,7 @@
if (subclass == _class) { // can still optimize for simple case
return this;
}
- JavaType result = _narrow(subclass);
- // TODO: these checks should NOT actually be needed; above should suffice:
- if (_valueHandler != result.<Object>getValueHandler()) {
- result = result.withValueHandler(_valueHandler);
- }
- if (_typeHandler != result.<Object>getTypeHandler()) {
- result = result.withTypeHandler(_typeHandler);
- }
- return result;
+ return _narrow(subclass);
}
@Deprecated // since 2.7
@@ -257,7 +249,14 @@
* @since 2.6
*/
public final boolean isTypeOrSubTypeOf(Class<?> clz) {
- return (_class == clz) || (clz.isAssignableFrom(_class));
+ return (_class == clz) || clz.isAssignableFrom(_class);
+ }
+
+ /**
+ * @since 2.9
+ */
+ public final boolean isTypeOrSuperTypeOf(Class<?> clz) {
+ return (_class == clz) || _class.isAssignableFrom(clz);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java
index 1e559a7..469055f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java
@@ -4,10 +4,11 @@
import java.util.Collection;
import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
-import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
+
+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.util.AccessPattern;
import com.fasterxml.jackson.databind.util.NameTransformer;
/**
@@ -29,7 +30,7 @@
*<p>
* In addition, to support per-property annotations (to configure aspects
* of deserialization on per-property basis), deserializers may want
- * to implement
+ * to implement
* {@link com.fasterxml.jackson.databind.deser.ContextualDeserializer},
* which allows specialization of deserializers: call to
* {@link com.fasterxml.jackson.databind.deser.ContextualDeserializer#createContextual}
@@ -43,6 +44,7 @@
* contextualization.
*/
public abstract class JsonDeserializer<T>
+ implements NullValueProvider // since 2.9
{
/*
/**********************************************************
@@ -80,8 +82,8 @@
* after the @class. Thus, if you want your method to work correctly
* both with and without polymorphism, you must begin your method with:
* <pre>
- * if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
- * jp.nextToken();
+ * if (p.getCurrentToken() == JsonToken.START_OBJECT) {
+ * p.nextToken();
* }
* </pre>
* This results in the stream pointing to the field name, so that
@@ -121,9 +123,12 @@
* update-existing-value operation (esp. immutable types)
*/
public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue)
- throws IOException, JsonProcessingException
+ throws IOException
{
- throw new UnsupportedOperationException("Can not update object of type "
+ if (ctxt.isEnabled(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)) {
+ return deserialize(p, ctxt);
+ }
+ throw new UnsupportedOperationException("Cannot update object of type "
+intoValue.getClass().getName()+" (by deserializer of type "+getClass().getName()+")");
}
@@ -251,10 +256,10 @@
/*
/**********************************************************
- /* Other accessors
+ /* Default NullValueProvider implementation
/**********************************************************
*/
-
+
/**
* Method that can be called to determine value to be used for
* representing null values (values deserialized when JSON token
@@ -270,12 +275,44 @@
*
* @since 2.6 Added to replace earlier no-arguments variant
*/
+ @Override
public T getNullValue(DeserializationContext ctxt) throws JsonMappingException {
// Change the direction in 2.7
return getNullValue();
}
/**
+ * Default implementation indicates that "null value" to use for input null
+ * is simply Java `null` for all deserializers, unless overridden by sub-classes.
+ * This information may be used as optimization.
+ */
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ // Default implementation assumes that the null value does not vary, which
+ // is usually the case for most implementations. But it is not necessarily
+ // `null`; so sub-classes may want to refine further.
+ return AccessPattern.CONSTANT;
+ }
+
+ /**
+ * This method may be called in conjunction with calls to
+ * {@link #getEmptyValue(DeserializationContext)}, to check whether it needs
+ * to be called just once (static values), or each time empty value is
+ * needed.
+ *
+ * @since 2.9
+ */
+ public AccessPattern getEmptyAccessPattern() {
+ return AccessPattern.DYNAMIC;
+ }
+
+ /*
+ /**********************************************************
+ /* Other accessors
+ /**********************************************************
+ */
+
+ /**
* Method called to determine value to be used for "empty" values
* (most commonly when deserializing from empty JSON Strings).
* Usually this is same as {@link #getNullValue} (which in turn
@@ -286,23 +323,24 @@
* Since version 2.6 (in which the context argument was added), call is
* expected to be made each and every time an empty value is needed.
*<p>
- * Default implementation simple calls {@link #getNullValue} and
+ * Since version 2.9 does not require return of `T` any more.
+ *<p>
+ * Default implementation simply calls {@link #getNullValue} and
* returns value.
*
* @since 2.6 Added to replace earlier no-arguments variant
*/
- public T getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
- // Change the direction in 2.7
- return getEmptyValue();
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return getNullValue(ctxt);
}
-
+
/**
* Accessor that can be used to check whether this deserializer
* is expecting to possibly get an Object Identifier value instead of full value
* serialization, and if so, should be able to resolve it to actual
* Object instance to return as deserialized value.
*<p>
- * Default implementation returns null, as support can not be implemented
+ * Default implementation returns null, as support cannot be implemented
* generically. Some standard deserializers (most notably
* {@link com.fasterxml.jackson.databind.deser.BeanDeserializer})
* do implement this feature, and may return reader instance, depending on exact
@@ -324,10 +362,33 @@
*/
public SettableBeanProperty findBackReference(String refName)
{
- throw new IllegalArgumentException("Can not handle managed/back reference '"+refName
+ throw new IllegalArgumentException("Cannot handle managed/back reference '"+refName
+"': type: value deserializer of type "+getClass().getName()+" does not support them");
}
+ /**
+ * Introspection method that may be called to see whether deserializer supports
+ * update of an existing value (aka "merging") or not. Return value should either
+ * be {@link Boolean#FALSE} if update is not supported at all (immutable values);
+ * {@link Boolean#TRUE} if update should usually work (regular POJOs, for example),
+ * or <code>null</code> if this is either not known, or may sometimes work.
+ *<p>
+ * Information gathered is typically used to either prevent merging update for
+ * property (either by skipping, if based on global defaults; or by exception during
+ * deserialization construction if explicit attempt made) if {@link Boolean#FALSE}
+ * returned, or inclusion if {@link Boolean#TRUE} is specified. If "unknown" case
+ * (<code>null</code> returned) behavior is to exclude property if global defaults
+ * used; or to allow if explicit per-type or property merging is defined.
+ *<p>
+ * Default implementation returns <code>null</code> to allow explicit per-type
+ * or per-property attempts.
+ *
+ * @since 2.9
+ */
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return null;
+ }
+
/*
/**********************************************************
/* Deprecated methods
@@ -344,8 +405,8 @@
* @deprecated Since 2.6 Use overloaded variant that takes context argument
*/
@Deprecated
- public T getEmptyValue() { return getNullValue(); }
-
+ public Object getEmptyValue() { return getNullValue(); }
+
/*
/**********************************************************
/* Helper classes
diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java b/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java
index 00896ea..4c25818 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java
@@ -7,6 +7,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Checked exception used to signal fatal problems with mapping of
@@ -85,7 +86,7 @@
public Reference(Object from, String fieldName) {
_from = from;
if (fieldName == null) {
- throw new NullPointerException("Can not pass null fieldName");
+ throw new NullPointerException("Cannot pass null fieldName");
}
_fieldName = fieldName;
}
@@ -326,13 +327,17 @@
* Factory method used when "upgrading" an {@link IOException} into
* {@link JsonMappingException}: usually only needed to comply with
* a signature.
+ *<p>
+ * NOTE: since 2.9 should usually NOT be used on input-side (deserialization)
+ * exceptions; instead use method(s) of <code>InputMismatchException</code>
*
* @since 2.1
*/
public static JsonMappingException fromUnexpectedIOE(IOException src) {
return new JsonMappingException(null,
String.format("Unexpected IOException (of type %s): %s",
- src.getClass().getName(), src.getMessage()));
+ src.getClass().getName(),
+ ClassUtil.exceptionMessage(src)));
}
/**
@@ -372,7 +377,8 @@
if (src instanceof JsonMappingException) {
jme = (JsonMappingException) src;
} else {
- String msg = src.getMessage();
+ // [databind#2128]: try to avoid duplication
+ String msg = ClassUtil.exceptionMessage(src);
// Let's use a more meaningful placeholder if all we have is null
if (msg == null || msg.length() == 0) {
msg = "(was "+src.getClass().getName()+")";
@@ -483,9 +489,7 @@
protected String _buildMessage()
{
- /* First: if we have no path info, let's just use parent's
- * definition as is
- */
+ // First: if we have no path info, let's just use parent's definition as is
String msg = super.getMessage();
if (_path == null) {
return msg;
diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java
index 142ed73..78e6b65 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java
@@ -99,18 +99,18 @@
}
@Override
- public final boolean isMissingNode() {
- return getNodeType() == JsonNodeType.MISSING;
+ public boolean isMissingNode() {
+ return false;
}
@Override
- public final boolean isArray() {
- return getNodeType() == JsonNodeType.ARRAY;
+ public boolean isArray() {
+ return false;
}
@Override
- public final boolean isObject() {
- return getNodeType() == JsonNodeType.OBJECT;
+ public boolean isObject() {
+ return false;
}
/**
@@ -311,7 +311,7 @@
* Note, however, that even if this method returns false, it
* is possible that conversion would be possible from other numeric
* types -- to check if this is possible, use
- * {@link #canConvertToInt()} instead.
+ * {@link #canConvertToLong()} instead.
*
* @return True if the value contained by this node is stored as Java <code>long</code>
*/
@@ -559,7 +559,7 @@
* and 1 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation can not be converted to an int (including structured types
+ * If representation cannot be converted to an int (including structured types
* like Objects and Arrays),
* default value of <b>0</b> will be returned; no exceptions are thrown.
*/
@@ -573,7 +573,7 @@
* and 1 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation can not be converted to an int (including structured types
+ * If representation cannot be converted to an int (including structured types
* like Objects and Arrays),
* specified <b>defaultValue</b> will be returned; no exceptions are thrown.
*/
@@ -587,7 +587,7 @@
* and 1 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation can not be converted to an long (including structured types
+ * If representation cannot be converted to an long (including structured types
* like Objects and Arrays),
* default value of <b>0</b> will be returned; no exceptions are thrown.
*/
@@ -601,7 +601,7 @@
* and 1 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation can not be converted to an long (including structured types
+ * If representation cannot be converted to an long (including structured types
* like Objects and Arrays),
* specified <b>defaultValue</b> will be returned; no exceptions are thrown.
*/
@@ -615,7 +615,7 @@
* and 1.0 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation can not be converted to an int (including structured types
+ * If representation cannot be converted to an int (including structured types
* like Objects and Arrays),
* default value of <b>0.0</b> will be returned; no exceptions are thrown.
*/
@@ -629,7 +629,7 @@
* and 1.0 (true), and Strings are parsed using default Java language integer
* parsing rules.
*<p>
- * If representation can not be converted to an int (including structured types
+ * If representation cannot be converted to an int (including structured types
* like Objects and Arrays),
* specified <b>defaultValue</b> will be returned; no exceptions are thrown.
*/
@@ -643,7 +643,7 @@
* 0 maps to false
* and Strings 'true' and 'false' map to corresponding values.
*<p>
- * If representation can not be converted to a boolean value (including structured types
+ * If representation cannot be converted to a boolean value (including structured types
* like Objects and Arrays),
* default value of <b>false</b> will be returned; no exceptions are thrown.
*/
@@ -657,7 +657,7 @@
* 0 maps to false
* and Strings 'true' and 'false' map to corresponding values.
*<p>
- * If representation can not be converted to a boolean value (including structured types
+ * If representation cannot be converted to a boolean value (including structured types
* like Objects and Arrays),
* specified <b>defaultValue</b> will be returned; no exceptions are thrown.
*/
@@ -727,7 +727,7 @@
*<p>
* This method is functionally equivalent to:
*<pre>
- * node.get(fieldName) != null << !node.get(fieldName).isNull()
+ * node.get(fieldName) != null && !node.get(fieldName).isNull()
*</pre>
*
* @since 2.1
@@ -743,7 +743,7 @@
*<p>
* This method is equivalent to:
*<pre>
- * node.get(index) != null << !node.get(index).isNull()
+ * node.get(index) != null && !node.get(index).isNull()
*</pre>
*
* @since 2.1
@@ -896,7 +896,7 @@
*/
public JsonNode with(String propertyName) {
throw new UnsupportedOperationException("JsonNode not of type ObjectNode (but "
- +getClass().getName()+"), can not call with() on it");
+ +getClass().getName()+"), cannot call with() on it");
}
/**
@@ -909,7 +909,7 @@
*/
public JsonNode withArray(String propertyName) {
throw new UnsupportedOperationException("JsonNode not of type ObjectNode (but "
- +getClass().getName()+"), can not call withArray() on it");
+ +getClass().getName()+"), cannot call withArray() on it");
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonSerializable.java b/src/main/java/com/fasterxml/jackson/databind/JsonSerializable.java
index bd6f77b..a08898a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JsonSerializable.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JsonSerializable.java
@@ -31,12 +31,10 @@
* Serialization method called when additional type information is
* expected to be included in serialization, for deserialization to use.
*<p>
- * Usually implementation consists of a call to one of methods
- * in {@link TypeSerializer} (such as {@link TypeSerializer#writeTypePrefixForObject(Object, JsonGenerator)})
+ * Usually implementation consists of a call to {@link TypeSerializer#writeTypePrefix}
* followed by serialization of contents,
- * followed by another call to {@link TypeSerializer}
- * (such as {@link TypeSerializer#writeTypeSuffixForObject(Object, JsonGenerator)}).
- * Exact methods to call in {@link TypeSerializer} depend on shape of JSON Object used
+ * followed by a call to {@link TypeSerializer#writeTypeSuffix}).
+ * Details of the type id argument to pass depend on shape of JSON Object used
* (Array, Object or scalar like String/Number/Boolean).
*<p>
* Note that some types (most notably, "natural" types: String, Integer,
diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java b/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java
index 3506cd2..11c01b2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java
@@ -24,7 +24,7 @@
* with null values -- caller <b>must</b> handle null values, usually
* by calling {@link SerializerProvider#findNullValueSerializer} to obtain
* serializer to use.
- * This also means that custom serializers can not be directly used to change
+ * This also means that custom serializers cannot be directly used to change
* the output to produce when serializing null values.
*<p>
* If serializer is an aggregate one -- meaning it delegates handling of some
@@ -120,7 +120,7 @@
* serializing Objects value contains, if any.
*/
public abstract void serialize(T value, JsonGenerator gen, SerializerProvider serializers)
- throws IOException, JsonProcessingException;
+ throws IOException;
/**
* Method that can be called to ask implementation to serialize
@@ -157,8 +157,9 @@
if (clz == null) {
clz = value.getClass();
}
- serializers.reportMappingProblem("Type id handling not implemented for type %s (by serializer of type %s)",
- clz.getName(), getClass().getName());
+ serializers.reportBadDefinition(clz, String.format(
+ "Type id handling not implemented for type %s (by serializer of type %s)",
+ clz.getName(), getClass().getName()));
}
/*
@@ -188,7 +189,7 @@
* Default implementation will consider only null values to be empty.
*
* @deprecated Since 2.5 Use {@link #isEmpty(SerializerProvider, Object)} instead;
- * will be removed from 2.9
+ * will be removed from 3.0
*/
@Deprecated
public boolean isEmpty(T value) {
@@ -272,7 +273,7 @@
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType type)
throws JsonMappingException
{
- if (visitor != null) visitor.expectAnyFormat(type);
+ visitor.expectAnyFormat(type);
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/KeyDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/KeyDeserializer.java
index 90dd56d..217527d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/KeyDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/KeyDeserializer.java
@@ -2,8 +2,6 @@
import java.io.IOException;
-import com.fasterxml.jackson.core.*;
-
/**
* Abstract class that defines API used for deserializing JSON content
* field names into Java Map keys. These deserializers are only used
@@ -15,7 +13,7 @@
* Method called to deserialize a {@link java.util.Map} key from JSON property name.
*/
public abstract Object deserializeKey(String key, DeserializationContext ctxt)
- throws IOException, JsonProcessingException;
+ throws IOException;
/**
* This marker class is only to be used with annotations, to
diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
index e163b5f..9b7bb96 100644
--- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
@@ -18,7 +18,7 @@
{
/*
/******************************************************
- /* Introspection features
+ /* General introspection features
/******************************************************
*/
@@ -33,6 +33,43 @@
USE_ANNOTATIONS(true),
/**
+ * Feature that determines whether otherwise regular "getter"
+ * methods (but only ones that handle Collections and Maps,
+ * not getters of other type)
+ * can be used for purpose of getting a reference to a Collection
+ * and Map to modify the property, without requiring a setter
+ * method.
+ * This is similar to how JAXB framework sets Collections and
+ * Maps: no setter is involved, just setter.
+ *<p>
+ * Note that such getters-as-setters methods have lower
+ * precedence than setters, so they are only used if no
+ * setter is found for the Map/Collection property.
+ *<p>
+ * Feature is enabled by default.
+ */
+ USE_GETTERS_AS_SETTERS(true),
+
+ /**
+ * Feature that determines how <code>transient</code> modifier for fields
+ * is handled: if disabled, it is only taken to mean exclusion of the field
+ * as accessor; if true, it is taken to imply removal of the whole property.
+ *<p>
+ * Feature is disabled by default, meaning that existence of `transient`
+ * for a field does not necessarily lead to ignoral of getters or setters
+ * but just ignoring the use of field for access.
+ *
+ * @since 2.6
+ */
+ PROPAGATE_TRANSIENT_MARKER(false),
+
+ /*
+ /******************************************************
+ /* Introspection-based property auto-detection
+ /******************************************************
+ */
+
+ /**
* Feature that determines whether "creator" methods are
* automatically detected by consider public constructors,
* and static single argument methods with name "valueOf".
@@ -61,7 +98,7 @@
*<p>
* Feature is enabled by default.
*/
- AUTO_DETECT_FIELDS(true),
+ AUTO_DETECT_FIELDS(true),
/**
* Feature that determines whether regular "getter" methods are
@@ -98,22 +135,22 @@
*/
AUTO_DETECT_IS_GETTERS(true),
- /**
- * Feature that determines whether "setter" methods are
- * automatically detected based on standard Bean naming convention
- * or not. If yes, then all public one-argument methods that
- * start with prefix "set"
- * are considered setters. If disabled, only methods explicitly
- * annotated are considered setters.
- *<p>
- * Note that this feature has lower precedence than per-class
- * annotations, and is only used if there isn't more granular
- * configuration available.
- *<P>
- * Feature is enabled by default.
- */
- AUTO_DETECT_SETTERS(true),
-
+ /**
+ * Feature that determines whether "setter" methods are
+ * automatically detected based on standard Bean naming convention
+ * or not. If yes, then all public one-argument methods that
+ * start with prefix "set"
+ * are considered setters. If disabled, only methods explicitly
+ * annotated are considered setters.
+ *<p>
+ * Note that this feature has lower precedence than per-class
+ * annotations, and is only used if there isn't more granular
+ * configuration available.
+ *<P>
+ * Feature is enabled by default.
+ */
+ AUTO_DETECT_SETTERS(true),
+
/**
* Feature that determines whether getters (getter methods)
* can be auto-detected if there is no matching mutator (setter,
@@ -126,22 +163,61 @@
REQUIRE_SETTERS_FOR_GETTERS(false),
/**
- * Feature that determines whether otherwise regular "getter"
- * methods (but only ones that handle Collections and Maps,
- * not getters of other type)
- * can be used for purpose of getting a reference to a Collection
- * and Map to modify the property, without requiring a setter
- * method.
- * This is similar to how JAXB framework sets Collections and
- * Maps: no setter is involved, just setter.
+ * Feature that determines whether member fields declared as 'final' may
+ * be auto-detected to be used mutators (used to change value of the logical
+ * property) or not. If enabled, 'final' access modifier has no effect, and
+ * such fields may be detected according to usual visibility and inference
+ * rules; if disabled, such fields are NOT used as mutators except if
+ * explicitly annotated for such use.
*<p>
- * Note that such getters-as-setters methods have lower
- * precedence than setters, so they are only used if no
- * setter is found for the Map/Collection property.
+ * Feature is enabled by default, for backwards compatibility reasons.
+ *
+ * @since 2.2
+ */
+ ALLOW_FINAL_FIELDS_AS_MUTATORS(true),
+
+ /**
+ * Feature that determines whether member mutators (fields and
+ * setters) may be "pulled in" even if they are not visible,
+ * as long as there is a visible accessor (getter or field) with same name.
+ * For example: field "value" may be inferred as mutator,
+ * if there is visible or explicitly marked getter "getValue()".
+ * If enabled, inferring is enabled; otherwise (disabled) only visible and
+ * explicitly annotated accessors are ever used.
+ *<p>
+ * Note that 'getters' are never inferred and need to be either visible (including
+ * bean-style naming) or explicitly annotated.
*<p>
* Feature is enabled by default.
+ *
+ * @since 2.2
*/
- USE_GETTERS_AS_SETTERS(true),
+ INFER_PROPERTY_MUTATORS(true),
+
+ /**
+ * Feature that determines handling of {@code java.beans.ConstructorProperties}
+ * annotation: when enabled, it is considered as alias of
+ * {@link com.fasterxml.jackson.annotation.JsonCreator}, to mean that constructor
+ * should be considered a property-based Creator; when disabled, only constructor
+ * parameter name information is used, but constructor is NOT considered an explicit
+ * Creator (although may be discovered as one using other annotations or heuristics).
+ *<p>
+ * Feature is mostly used to help inter-operability with frameworks like Lombok
+ * that may automatically generate {@code ConstructorProperties} annotation
+ * but without necessarily meaning that constructor should be used as Creator
+ * for deserialization.
+ *<p>
+ * Feature is enabled by default.
+ *
+ * @since 2.9
+ */
+ INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES(true),
+
+ /*
+ /******************************************************
+ /* Access modifier handling
+ /******************************************************
+ */
/**
* Feature that determines whether method and field access
@@ -187,51 +263,6 @@
*/
OVERRIDE_PUBLIC_ACCESS_MODIFIERS(true),
- /**
- * Feature that determines whether member mutators (fields and
- * setters) may be "pulled in" even if they are not visible,
- * as long as there is a visible accessor (getter or field) with same name.
- * For example: field "value" may be inferred as mutator,
- * if there is visible or explicitly marked getter "getValue()".
- * If enabled, inferring is enabled; otherwise (disabled) only visible and
- * explicitly annotated accessors are ever used.
- *<p>
- * Note that 'getters' are never inferred and need to be either visible (including
- * bean-style naming) or explicitly annotated.
- *<p>
- * Feature is enabled by default.
- *
- * @since 2.2
- */
- INFER_PROPERTY_MUTATORS(true),
-
- /**
- * Feature that determines whether member fields declared as 'final' may
- * be auto-detected to be used mutators (used to change value of the logical
- * property) or not. If enabled, 'final' access modifier has no effect, and
- * such fields may be detected according to usual visibility and inference
- * rules; if disabled, such fields are NOT used as mutators except if
- * explicitly annotated for such use.
- *<p>
- * Feature is enabled by default, for backwards compatibility reasons.
- *
- * @since 2.2
- */
- ALLOW_FINAL_FIELDS_AS_MUTATORS(true),
-
- /**
- * Feature that determines how <code>transient</code> modifier for fields
- * is handled: if disabled, it is only taken to mean exclusion of the field
- * as accessor; if true, removal of the whole property.
- *<p>
- * Feature is disabled by default, meaning that existence of `transient`
- * for a field does not necessarily lead to ignoral of getters or setters
- * but just ignoring the use of field for access.
- *
- * @since 2.6
- */
- PROPAGATE_TRANSIENT_MARKER(false),
-
/*
/******************************************************
/* Type-handling features
@@ -255,6 +286,20 @@
*/
USE_STATIC_TYPING(false),
+ /**
+ * Feature that specifies whether the declared base type of a polymorphic value
+ * is to be used as the "default" implementation, if no explicit default class
+ * is specified via {@code @JsonTypeInfo.defaultImpl} annotation.
+ *<p>
+ * Note that feature only has effect on deserialization of regular polymorphic properties:
+ * it does NOT affect non-polymorphic cases, and is unlikely to work with Default Typing.
+ *<p>
+ * Feature is disabled by default for backwards compatibility.
+ *
+ * @since 2.9.6
+ */
+ USE_BASE_TYPE_AS_DEFAULT_IMPL(false),
+
/*
/******************************************************
/* View-related features
@@ -307,6 +352,7 @@
/* Name-related features
/******************************************************
*/
+
/**
* Feature that will allow for more forgiving deserialization of incoming JSON.
* If enabled, the bean properties will be matched using their lower-case equivalents,
@@ -322,7 +368,20 @@
* @since 2.5
*/
ACCEPT_CASE_INSENSITIVE_PROPERTIES(false),
-
+
+
+ /**
+ * Feature that determines if Enum deserialization should be case sensitive or not.
+ * If enabled, Enum deserialization will ignore case, that is, case of incoming String
+ * value and enum id (dependant on other settings, either `name()`, `toString()`, or
+ * explicit override) do not need to match.
+ * <p>
+ * Feature is disabled by default.
+ *
+ * @since 2.9
+ */
+ ACCEPT_CASE_INSENSITIVE_ENUMS(false),
+
/**
* Feature that can be enabled to make property names be
* overridden by wrapper name (usually detected with annotations
@@ -366,6 +425,34 @@
/*
/******************************************************
+ /* Coercion features
+ /******************************************************
+ */
+
+ /**
+ * Feature that determines whether coercions from secondary representations are allowed
+ * for simple non-textual scalar types: numbers and booleans. This includes `primitive`
+ * types and their wrappers, but excludes `java.lang.String` and date/time types.
+ *<p>
+ * When feature is disabled, only strictly compatible input may be bound: numbers for
+ * numbers, boolean values for booleans. When feature is enabled, conversions from
+ * JSON String are allowed, as long as textual value matches (for example, String
+ * "true" is allowed as equivalent of JSON boolean token `true`; or String "1.0"
+ * for `double`).
+ *<p>
+ * Note that it is possible that other configurability options can override this
+ * in closer scope (like on per-type or per-property basis); this is just the global
+ * default.
+ *<p>
+ * Feature is enabled by default (for backwards compatibility since this was the
+ * default behavior)
+ *
+ * @since 2.9
+ */
+ ALLOW_COERCION_OF_SCALARS(true),
+
+ /*
+ /******************************************************
/* Other features
/******************************************************
*/
@@ -387,8 +474,22 @@
*
* @since 2.5
*/
- IGNORE_DUPLICATE_MODULE_REGISTRATIONS(true)
-
+ IGNORE_DUPLICATE_MODULE_REGISTRATIONS(true),
+
+ /**
+ * Setting that determines what happens if an attempt is made to explicitly
+ * "merge" value of a property, where value does not support merging; either
+ * merging is skipped and new value is created (<code>true</code>) or
+ * an exception is thrown (false).
+ *<p>
+ * Feature is disabled by default since non-mergeable property types are ignored
+ * even if defaults call for merging, and usually explicit per-type or per-property
+ * settings for such types should result in an exception.
+ *
+ * @since 2.9
+ */
+ IGNORE_MERGE_FOR_UNMERGEABLE(true)
+
;
private final boolean _defaultState;
diff --git a/src/main/java/com/fasterxml/jackson/databind/Module.java b/src/main/java/com/fasterxml/jackson/databind/Module.java
index 590dd3c..5884e6b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/Module.java
+++ b/src/main/java/com/fasterxml/jackson/databind/Module.java
@@ -1,5 +1,7 @@
package com.fasterxml.jackson.databind;
+import java.util.Collection;
+
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.cfg.MutableConfigOverride;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
@@ -297,6 +299,14 @@
* they have), using specified type names.
*/
public void registerSubtypes(NamedType... subtypes);
+
+ /**
+ * Method for registering specified classes as subtypes (of supertype(s)
+ * they have)
+ *
+ * @since 2.9
+ */
+ public void registerSubtypes(Collection<Class<?>> subtypes);
/**
* Method used for defining mix-in annotations to use for augmenting
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
index f1b5272..5232de4 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
@@ -17,13 +17,9 @@
import com.fasterxml.jackson.core.type.ResolvedType;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.util.*;
-import com.fasterxml.jackson.databind.cfg.BaseSettings;
-import com.fasterxml.jackson.databind.cfg.ContextAttributes;
-import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
-import com.fasterxml.jackson.databind.cfg.MapperConfig;
-import com.fasterxml.jackson.databind.cfg.MutableConfigOverride;
-import com.fasterxml.jackson.databind.cfg.ConfigOverrides;
+import com.fasterxml.jackson.databind.cfg.*;
import com.fasterxml.jackson.databind.deser.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsontype.*;
@@ -80,7 +76,7 @@
* on Streaming API).
*<p>
* Mapper instances are fully thread-safe provided that ALL configuration of the
- * instance occurs before ANY read or write calls. If configuration of a mapper
+ * instance occurs before ANY read or write calls. If configuration of a mapper instance
* is modified after first usage, changes may or may not take effect, and configuration
* calls themselves may fail.
* If you need to use different configuration, you have two main possibilities:
@@ -94,7 +90,7 @@
* </li>
* <li>If the specific kind of configurability is not available via {@link ObjectReader} and
* {@link ObjectWriter}, you may need to use multiple {@link ObjectMapper} instead (for example:
- * you can not change mix-in annotations on-the-fly; or, set of custom (de)serializers).
+ * you cannot change mix-in annotations on-the-fly; or, set of custom (de)serializers).
* To help with this usage, you may want to use method {@link #copy()} which creates a clone
* of the mapper with specific configuration, and allows configuration of the copied instance
* before it gets used. Note that {@link #copy} operation is as expensive as constructing
@@ -112,13 +108,19 @@
* produce differing deserializers), and that the performance impact
* greatest at root level (since it'll essentially cache the full
* graph of deserializers involved).
+ *<p>
+ * Notes on security: use "default typing" feature (see {@link #enableDefaultTyping()})
+ * is a potential security risk, if used with untrusted content (content generated by
+ * untrusted external parties). If so, you may want to construct a custom
+ * {@link TypeResolverBuilder} implementation to limit possible types to instantiate,
+ * (using {@link #setDefaultTyping}).
*/
public class ObjectMapper
extends ObjectCodec
implements Versioned,
java.io.Serializable // as of 2.1
{
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 2L; // as of 2.9
/*
/**********************************************************
@@ -134,11 +136,15 @@
*<p>
* Since 2.4 there are special exceptions for JSON Tree model
* types (sub-types of {@link TreeNode}: default typing is never
- * applied to them
- * (see <a href="https://github.com/FasterXML/jackson-databind/issues/88">databind#88</a> for details)
- *<p>
+ * applied to them.
* Since 2.8(.4) additional checks are made to avoid attempts at default
* typing primitive-valued properties.
+ *<p>
+ * NOTE: use of Default Typing can be a potential security risk if incoming
+ * content comes from untrusted sources, and it is recommended that this
+ * is either not done, or, if enabled, use {@link #setDefaultTyping}
+ * passing a custom {@link TypeResolverBuilder} implementation that white-lists
+ * legal types to use.
*/
public enum DefaultTyping {
/**
@@ -284,16 +290,14 @@
// 16-May-2009, tatu: Ditto ^^^
protected final static AnnotationIntrospector DEFAULT_ANNOTATION_INTROSPECTOR = new JacksonAnnotationIntrospector();
- protected final static VisibilityChecker<?> STD_VISIBILITY_CHECKER = VisibilityChecker.Std.defaultInstance();
-
/**
* Base settings contain defaults used for all {@link ObjectMapper}
* instances.
*/
protected final static BaseSettings DEFAULT_BASE = new BaseSettings(
- null, // can not share global ClassIntrospector any more (2.5+)
+ null, // cannot share global ClassIntrospector any more (2.5+)
DEFAULT_ANNOTATION_INTROSPECTOR,
- STD_VISIBILITY_CHECKER, null, TypeFactory.defaultInstance(),
+ null, TypeFactory.defaultInstance(),
null, StdDateFormat.instance, null,
Locale.getDefault(),
null, // to indicate "use Jackson default TimeZone" (UTC since Jackson 2.7)
@@ -334,9 +338,9 @@
* Currently active per-type configuration overrides, accessed by
* declared type of property.
*
- * @since 2.8
+ * @since 2.9
*/
- protected ConfigOverrides _propertyOverrides;
+ protected final ConfigOverrides _configOverrides;
/*
/**********************************************************
@@ -497,12 +501,14 @@
_subtypeResolver = src._subtypeResolver;
_typeFactory = src._typeFactory;
_injectableValues = src._injectableValues;
- _propertyOverrides = src._propertyOverrides.copy();
+ _configOverrides = src._configOverrides.copy();
_mixIns = src._mixIns.copy();
RootNameLookup rootNames = new RootNameLookup();
- _serializationConfig = new SerializationConfig(src._serializationConfig, _mixIns, rootNames, _propertyOverrides);
- _deserializationConfig = new DeserializationConfig(src._deserializationConfig, _mixIns, rootNames, _propertyOverrides);
+ _serializationConfig = new SerializationConfig(src._serializationConfig,
+ _mixIns, rootNames, _configOverrides);
+ _deserializationConfig = new DeserializationConfig(src._deserializationConfig,
+ _mixIns, rootNames, _configOverrides);
_serializerProvider = src._serializerProvider.copy();
_deserializationContext = src._deserializationContext.copy();
@@ -555,12 +561,11 @@
SimpleMixInResolver mixins = new SimpleMixInResolver(null);
_mixIns = mixins;
BaseSettings base = DEFAULT_BASE.withClassIntrospector(defaultClassIntrospector());
- ConfigOverrides propOverrides = new ConfigOverrides();
- _propertyOverrides = propOverrides;
+ _configOverrides = new ConfigOverrides();
_serializationConfig = new SerializationConfig(base,
- _subtypeResolver, mixins, rootNames, propOverrides);
+ _subtypeResolver, mixins, rootNames, _configOverrides);
_deserializationConfig = new DeserializationConfig(base,
- _subtypeResolver, mixins, rootNames, propOverrides);
+ _subtypeResolver, mixins, rootNames, _configOverrides);
// Some overrides we may need
final boolean needOrder = _jsonFactory.requiresPropertyOrdering();
@@ -618,6 +623,7 @@
protected void _checkInvalidCopy(Class<?> exp)
{
if (getClass() != exp) {
+ // 10-Nov-2016, tatu: could almost use `ClassUtil.verifyMustOverride()` but not quite
throw new IllegalStateException("Failed copy(): "+getClass().getName()
+" (version: "+version()+") does not override copy(); it has to");
}
@@ -741,8 +747,6 @@
throw new IllegalArgumentException("Module without defined version");
}
- final ObjectMapper mapper = this;
-
// And then call registration
module.setupModule(new Module.SetupContext()
{
@@ -757,7 +761,7 @@
@Override
public <C extends ObjectCodec> C getOwner() {
// why do we need the cast here?!?
- return (C) mapper;
+ return (C) ObjectMapper.this;
}
@Override
@@ -767,140 +771,145 @@
@Override
public boolean isEnabled(MapperFeature f) {
- return mapper.isEnabled(f);
+ return ObjectMapper.this.isEnabled(f);
}
@Override
public boolean isEnabled(DeserializationFeature f) {
- return mapper.isEnabled(f);
+ return ObjectMapper.this.isEnabled(f);
}
@Override
public boolean isEnabled(SerializationFeature f) {
- return mapper.isEnabled(f);
+ return ObjectMapper.this.isEnabled(f);
}
@Override
public boolean isEnabled(JsonFactory.Feature f) {
- return mapper.isEnabled(f);
+ return ObjectMapper.this.isEnabled(f);
}
@Override
public boolean isEnabled(JsonParser.Feature f) {
- return mapper.isEnabled(f);
+ return ObjectMapper.this.isEnabled(f);
}
@Override
public boolean isEnabled(JsonGenerator.Feature f) {
- return mapper.isEnabled(f);
+ return ObjectMapper.this.isEnabled(f);
}
// // // Mutant accessors
@Override
public MutableConfigOverride configOverride(Class<?> type) {
- return mapper.configOverride(type);
+ return ObjectMapper.this.configOverride(type);
}
// // // Methods for registering handlers: deserializers
@Override
public void addDeserializers(Deserializers d) {
- DeserializerFactory df = mapper._deserializationContext._factory.withAdditionalDeserializers(d);
- mapper._deserializationContext = mapper._deserializationContext.with(df);
+ DeserializerFactory df = _deserializationContext._factory.withAdditionalDeserializers(d);
+ _deserializationContext = _deserializationContext.with(df);
}
@Override
public void addKeyDeserializers(KeyDeserializers d) {
- DeserializerFactory df = mapper._deserializationContext._factory.withAdditionalKeyDeserializers(d);
- mapper._deserializationContext = mapper._deserializationContext.with(df);
+ DeserializerFactory df = _deserializationContext._factory.withAdditionalKeyDeserializers(d);
+ _deserializationContext = _deserializationContext.with(df);
}
@Override
public void addBeanDeserializerModifier(BeanDeserializerModifier modifier) {
- DeserializerFactory df = mapper._deserializationContext._factory.withDeserializerModifier(modifier);
- mapper._deserializationContext = mapper._deserializationContext.with(df);
+ DeserializerFactory df = _deserializationContext._factory.withDeserializerModifier(modifier);
+ _deserializationContext = _deserializationContext.with(df);
}
// // // Methods for registering handlers: serializers
@Override
public void addSerializers(Serializers s) {
- mapper._serializerFactory = mapper._serializerFactory.withAdditionalSerializers(s);
+ _serializerFactory = _serializerFactory.withAdditionalSerializers(s);
}
@Override
public void addKeySerializers(Serializers s) {
- mapper._serializerFactory = mapper._serializerFactory.withAdditionalKeySerializers(s);
+ _serializerFactory = _serializerFactory.withAdditionalKeySerializers(s);
}
@Override
public void addBeanSerializerModifier(BeanSerializerModifier modifier) {
- mapper._serializerFactory = mapper._serializerFactory.withSerializerModifier(modifier);
+ _serializerFactory = _serializerFactory.withSerializerModifier(modifier);
}
// // // Methods for registering handlers: other
@Override
public void addAbstractTypeResolver(AbstractTypeResolver resolver) {
- DeserializerFactory df = mapper._deserializationContext._factory.withAbstractTypeResolver(resolver);
- mapper._deserializationContext = mapper._deserializationContext.with(df);
+ DeserializerFactory df = _deserializationContext._factory.withAbstractTypeResolver(resolver);
+ _deserializationContext = _deserializationContext.with(df);
}
@Override
public void addTypeModifier(TypeModifier modifier) {
- TypeFactory f = mapper._typeFactory;
+ TypeFactory f = _typeFactory;
f = f.withModifier(modifier);
- mapper.setTypeFactory(f);
+ setTypeFactory(f);
}
@Override
public void addValueInstantiators(ValueInstantiators instantiators) {
- DeserializerFactory df = mapper._deserializationContext._factory.withValueInstantiators(instantiators);
- mapper._deserializationContext = mapper._deserializationContext.with(df);
+ DeserializerFactory df = _deserializationContext._factory.withValueInstantiators(instantiators);
+ _deserializationContext = _deserializationContext.with(df);
}
@Override
public void setClassIntrospector(ClassIntrospector ci) {
- mapper._deserializationConfig = mapper._deserializationConfig.with(ci);
- mapper._serializationConfig = mapper._serializationConfig.with(ci);
+ _deserializationConfig = _deserializationConfig.with(ci);
+ _serializationConfig = _serializationConfig.with(ci);
}
@Override
public void insertAnnotationIntrospector(AnnotationIntrospector ai) {
- mapper._deserializationConfig = mapper._deserializationConfig.withInsertedAnnotationIntrospector(ai);
- mapper._serializationConfig = mapper._serializationConfig.withInsertedAnnotationIntrospector(ai);
+ _deserializationConfig = _deserializationConfig.withInsertedAnnotationIntrospector(ai);
+ _serializationConfig = _serializationConfig.withInsertedAnnotationIntrospector(ai);
}
@Override
public void appendAnnotationIntrospector(AnnotationIntrospector ai) {
- mapper._deserializationConfig = mapper._deserializationConfig.withAppendedAnnotationIntrospector(ai);
- mapper._serializationConfig = mapper._serializationConfig.withAppendedAnnotationIntrospector(ai);
+ _deserializationConfig = _deserializationConfig.withAppendedAnnotationIntrospector(ai);
+ _serializationConfig = _serializationConfig.withAppendedAnnotationIntrospector(ai);
}
@Override
public void registerSubtypes(Class<?>... subtypes) {
- mapper.registerSubtypes(subtypes);
+ ObjectMapper.this.registerSubtypes(subtypes);
}
@Override
public void registerSubtypes(NamedType... subtypes) {
- mapper.registerSubtypes(subtypes);
+ ObjectMapper.this.registerSubtypes(subtypes);
}
-
+
+ @Override
+ public void registerSubtypes(Collection<Class<?>> subtypes) {
+ ObjectMapper.this.registerSubtypes(subtypes);
+ }
+
@Override
public void setMixInAnnotations(Class<?> target, Class<?> mixinSource) {
- mapper.addMixIn(target, mixinSource);
+ addMixIn(target, mixinSource);
}
@Override
public void addDeserializationProblemHandler(DeserializationProblemHandler handler) {
- mapper.addHandler(handler);
+ addHandler(handler);
}
@Override
public void setNamingStrategy(PropertyNamingStrategy naming) {
- mapper.setPropertyNamingStrategy(naming);
+ setPropertyNamingStrategy(naming);
}
});
return this;
@@ -936,14 +945,27 @@
*
* @since 2.2
*/
- public ObjectMapper registerModules(Iterable<Module> modules)
+ public ObjectMapper registerModules(Iterable<? extends Module> modules)
{
for (Module module : modules) {
registerModule(module);
}
return this;
}
-
+
+ /**
+ * The set of {@link Module} typeIds that are registered in this
+ * ObjectMapper. By default the typeId for a module is it's full
+ * class name (see {@link Module#getTypeId()}).
+ *
+ * @since 2.9.6
+ */
+ public Set<Object> getRegisteredModuleIds()
+ {
+ return (_registeredModuleTypes == null) ?
+ Collections.emptySet() : Collections.unmodifiableSet(_registeredModuleTypes);
+ }
+
/**
* Method for locating available methods, using JDK {@link ServiceLoader}
* facility, along with module-provided SPI.
@@ -1088,7 +1110,7 @@
/**
* Accessor for the "blueprint" (or, factory) instance, from which instances
* are created by calling {@link DefaultSerializerProvider#createInstance}.
- * Note that returned instance can not be directly used as it is not properly
+ * Note that returned instance cannot be directly used as it is not properly
* configured: to get a properly configured instance to call, use
* {@link #getSerializerProviderInstance()} instead.
*/
@@ -1199,7 +1221,7 @@
public final void addMixInAnnotations(Class<?> target, Class<?> mixinSource) {
addMixIn(target, mixinSource);
}
-
+
/*
/**********************************************************
/* Configuration, introspection
@@ -1216,28 +1238,20 @@
}
/**
- * @deprecated Since 2.6 use {@link #setVisibility(VisibilityChecker)} instead.
- */
- @Deprecated
- public void setVisibilityChecker(VisibilityChecker<?> vc) {
- setVisibility(vc);
- }
-
- /**
- * Method for setting currently configured {@link VisibilityChecker},
+ * Method for setting currently configured default {@link VisibilityChecker},
* object used for determining whether given property element
* (method, field, constructor) can be auto-detected or not.
- * This default checker is used if no per-class overrides
- * are defined.
+ * This default checker is used as the base visibility:
+ * per-class overrides (both via annotations and per-type config overrides)
+ * can further change these settings.
*
* @since 2.6
*/
public ObjectMapper setVisibility(VisibilityChecker<?> vc) {
- _deserializationConfig = _deserializationConfig.with(vc);
- _serializationConfig = _serializationConfig.with(vc);
+ _configOverrides.setDefaultVisibility(vc);
return this;
}
-
+
/**
* Convenience method that allows changing configuration for
* underlying {@link VisibilityChecker}s, to change details of what kinds of
@@ -1264,11 +1278,12 @@
*/
public ObjectMapper setVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility)
{
- _deserializationConfig = _deserializationConfig.withVisibility(forMethod, visibility);
- _serializationConfig = _serializationConfig.withVisibility(forMethod, visibility);
+ VisibilityChecker<?> vc = _configOverrides.getDefaultVisibility();
+ vc = vc.withVisibility(forMethod, visibility);
+ _configOverrides.setDefaultVisibility(vc);
return this;
}
-
+
/**
* Method for accessing subtype resolver in use.
*/
@@ -1342,27 +1357,6 @@
}
/**
- * Convenience method, equivalent to calling:
- *<pre>
- * setPropertyInclusion(JsonInclude.Value.construct(incl, Include.ALWAYS));
- *</pre>
- */
- public ObjectMapper setSerializationInclusion(JsonInclude.Include incl) {
- setPropertyInclusion(JsonInclude.Value.construct(incl, JsonInclude.Include.USE_DEFAULTS));
- return this;
- }
-
- /**
- * Method for setting default POJO property inclusion strategy for serialization.
- *
- * @since 2.7
- */
- public ObjectMapper setPropertyInclusion(JsonInclude.Value incl) {
- _serializationConfig = _serializationConfig.withPropertyInclusion(incl);
- return this;
- }
-
- /**
* Method for specifying {@link PrettyPrinter} to use when "default pretty-printing"
* is enabled (by enabling {@link SerializationFeature#INDENT_OUTPUT})
*
@@ -1377,6 +1371,105 @@
return this;
}
+ /**
+ * @deprecated Since 2.6 use {@link #setVisibility(VisibilityChecker)} instead.
+ */
+ @Deprecated
+ public void setVisibilityChecker(VisibilityChecker<?> vc) {
+ setVisibility(vc);
+ }
+
+ /*
+ /**********************************************************
+ /* Configuration: global-default/per-type override settings
+ /**********************************************************
+ */
+
+ /**
+ * Convenience method, equivalent to calling:
+ *<pre>
+ * setPropertyInclusion(JsonInclude.Value.construct(incl, incl));
+ *</pre>
+ *<p>
+ * NOTE: behavior differs slightly from 2.8, where second argument was
+ * implied to be <code>JsonInclude.Include.ALWAYS</code>.
+ */
+ public ObjectMapper setSerializationInclusion(JsonInclude.Include incl) {
+ setPropertyInclusion(JsonInclude.Value.construct(incl, incl));
+ return this;
+ }
+
+ /**
+ * @since 2.7
+ * @deprecated Since 2.9 use {@link #setDefaultPropertyInclusion}
+ */
+ @Deprecated
+ public ObjectMapper setPropertyInclusion(JsonInclude.Value incl) {
+ return setDefaultPropertyInclusion(incl);
+ }
+
+ /**
+ * Method for setting default POJO property inclusion strategy for serialization,
+ * applied for all properties for which there are no per-type or per-property
+ * overrides (via annotations or config overrides).
+ *
+ * @since 2.9 (basically rename of <code>setPropertyInclusion</code>)
+ */
+ public ObjectMapper setDefaultPropertyInclusion(JsonInclude.Value incl) {
+ _configOverrides.setDefaultInclusion(incl);
+ return this;
+ }
+
+ /**
+ * Short-cut for:
+ *<pre>
+ * setDefaultPropertyInclusion(JsonInclude.Value.construct(incl, incl));
+ *</pre>
+ *
+ * @since 2.9 (basically rename of <code>setPropertyInclusion</code>)
+ */
+ public ObjectMapper setDefaultPropertyInclusion(JsonInclude.Include incl) {
+ _configOverrides.setDefaultInclusion(JsonInclude.Value.construct(incl, incl));
+ return this;
+ }
+
+ /**
+ * Method for setting default Setter configuration, regarding things like
+ * merging, null-handling; used for properties for which there are
+ * no per-type or per-property overrides (via annotations or config overrides).
+ *
+ * @since 2.9
+ */
+ public ObjectMapper setDefaultSetterInfo(JsonSetter.Value v) {
+ _configOverrides.setDefaultSetterInfo(v);
+ return this;
+ }
+
+ /**
+ * Method for setting auto-detection visibility definition
+ * defaults, which are in effect unless overridden by
+ * annotations (like <code>JsonAutoDetect</code>) or per-type
+ * visibility overrides.
+ *
+ * @since 2.9
+ */
+ public ObjectMapper setDefaultVisibility(JsonAutoDetect.Value vis) {
+ _configOverrides.setDefaultVisibility(VisibilityChecker.Std.construct(vis));
+ return this;
+ }
+
+ /**
+ * Method for setting default Setter configuration, regarding things like
+ * merging, null-handling; used for properties for which there are
+ * no per-type or per-property overrides (via annotations or config overrides).
+ *
+ * @since 2.9
+ */
+ public ObjectMapper setDefaultMergeable(Boolean b) {
+ _configOverrides.setDefaultMergeable(b);
+ return this;
+ }
+
/*
/**********************************************************
/* Type information configuration
@@ -1386,8 +1479,14 @@
/**
* Convenience method that is equivalent to calling
*<pre>
- * enableObjectTyping(DefaultTyping.OBJECT_AND_NON_CONCRETE);
+ * enableDefaultTyping(DefaultTyping.OBJECT_AND_NON_CONCRETE);
*</pre>
+ *<p>
+ * NOTE: use of Default Typing can be a potential security risk if incoming
+ * content comes from untrusted sources, and it is recommended that this
+ * is either not done, or, if enabled, use {@link #setDefaultTyping}
+ * passing a custom {@link TypeResolverBuilder} implementation that white-lists
+ * legal types to use.
*/
public ObjectMapper enableDefaultTyping() {
return enableDefaultTyping(DefaultTyping.OBJECT_AND_NON_CONCRETE);
@@ -1396,8 +1495,14 @@
/**
* Convenience method that is equivalent to calling
*<pre>
- * enableObjectTyping(dti, JsonTypeInfo.As.WRAPPER_ARRAY);
+ * enableDefaultTyping(dti, JsonTypeInfo.As.WRAPPER_ARRAY);
*</pre>
+ *<p>
+ * NOTE: use of Default Typing can be a potential security risk if incoming
+ * content comes from untrusted sources, and it is recommended that this
+ * is either not done, or, if enabled, use {@link #setDefaultTyping}
+ * passing a custom {@link TypeResolverBuilder} implementation that white-lists
+ * legal types to use.
*/
public ObjectMapper enableDefaultTyping(DefaultTyping dti) {
return enableDefaultTyping(dti, JsonTypeInfo.As.WRAPPER_ARRAY);
@@ -1411,6 +1516,12 @@
* NOTE: use of <code>JsonTypeInfo.As#EXTERNAL_PROPERTY</code> <b>NOT SUPPORTED</b>;
* and attempts of do so will throw an {@link IllegalArgumentException} to make
* this limitation explicit.
+ *<p>
+ * NOTE: use of Default Typing can be a potential security risk if incoming
+ * content comes from untrusted sources, and it is recommended that this
+ * is either not done, or, if enabled, use {@link #setDefaultTyping}
+ * passing a custom {@link TypeResolverBuilder} implementation that white-lists
+ * legal types to use.
*
* @param applicability Defines kinds of types for which additional type information
* is added; see {@link DefaultTyping} for more information.
@@ -1421,7 +1532,7 @@
* use "As.EXTERNAL_PROPERTY", since that will not work (with 2.5+)
*/
if (includeAs == JsonTypeInfo.As.EXTERNAL_PROPERTY) {
- throw new IllegalArgumentException("Can not use includeAs of "+includeAs);
+ throw new IllegalArgumentException("Cannot use includeAs of "+includeAs);
}
TypeResolverBuilder<?> typer = new DefaultTypeResolverBuilder(applicability);
@@ -1438,6 +1549,12 @@
* using "As.PROPERTY" inclusion mechanism and specified property name
* to use for inclusion (default being "@class" since default type information
* always uses class name as type identifier)
+ *<p>
+ * NOTE: use of Default Typing can be a potential security risk if incoming
+ * content comes from untrusted sources, and it is recommended that this
+ * is either not done, or, if enabled, use {@link #setDefaultTyping}
+ * passing a custom {@link TypeResolverBuilder} implementation that white-lists
+ * legal types to use.
*/
public ObjectMapper enableDefaultTypingAsProperty(DefaultTyping applicability, String propertyName)
{
@@ -1448,7 +1565,7 @@
typer = typer.typeProperty(propertyName);
return setDefaultTyping(typer);
}
-
+
/**
* Method for disabling automatic inclusion of type information; if so, only
* explicitly annotated types (ones with
@@ -1463,6 +1580,11 @@
* Method for enabling automatic inclusion of type information, using
* specified handler object for determining which types this affects,
* as well as details of how information is embedded.
+ *<p>
+ * NOTE: use of Default Typing can be a potential security risk if incoming
+ * content comes from untrusted sources, so care should be taken to use
+ * a {@link TypeResolverBuilder} that can limit allowed classes to
+ * deserialize.
*
* @param typer Type information inclusion handler
*/
@@ -1495,6 +1617,13 @@
getSubtypeResolver().registerSubtypes(types);
}
+ /**
+ * @since 2.9
+ */
+ public void registerSubtypes(Collection<Class<?>> subtypes) {
+ getSubtypeResolver().registerSubtypes(subtypes);
+ }
+
/*
/**********************************************************
/* Configuration, basic type handling
@@ -1519,7 +1648,7 @@
* @since 2.8
*/
public MutableConfigOverride configOverride(Class<?> type) {
- return _propertyOverrides.findOrCreateOverride(type);
+ return _configOverrides.findOrCreateOverride(type);
}
/*
@@ -1549,7 +1678,7 @@
_serializationConfig = _serializationConfig.with(f);
return this;
}
-
+
/**
* Convenience method for constructing {@link JavaType} out of given
* type (typically <code>java.lang.Class</code>), but without explicit
@@ -1558,7 +1687,7 @@
public JavaType constructType(Type t) {
return _typeFactory.constructType(t);
}
-
+
/*
/**********************************************************
/* Configuration, deserialization
@@ -1578,7 +1707,7 @@
public JsonNodeFactory getNodeFactory() {
return _deserializationConfig.getNodeFactory();
}
-
+
/**
* Method for specifying {@link JsonNodeFactory} to use for
* constructing root level tree nodes (via method
@@ -1701,6 +1830,15 @@
* Method that can be used to get hold of {@link JsonFactory} that this
* mapper uses if it needs to construct {@link JsonParser}s
* and/or {@link JsonGenerator}s.
+ *<p>
+ * WARNING: note that all {@link ObjectReader} and {@link ObjectWriter}
+ * instances created by this mapper usually share the same configured
+ * {@link JsonFactory}, so changes to its configuration will "leak".
+ * To avoid such observed changes you should always use "with()" and
+ * "without()" method of {@link ObjectReader} and {@link ObjectWriter}
+ * for changing {@link com.fasterxml.jackson.core.JsonParser.Feature}
+ * and {@link com.fasterxml.jackson.core.JsonGenerator.Feature}
+ * settings to use on per-call basis.
*
* @return {@link JsonFactory} that this mapper uses when it needs to
* construct Json parser and generators
@@ -1789,7 +1927,7 @@
_serializationConfig = _serializationConfig.with(tz);
return this;
}
-
+
/*
/**********************************************************
/* Configuration, simple features: MapperFeature
@@ -1976,6 +2114,10 @@
*<p>
* Note that this is equivalent to directly calling same method
* on {@link #getFactory}.
+ *<p>
+ * WARNING: since this method directly modifies state of underlying {@link JsonFactory},
+ * it will change observed configuration by {@link ObjectReader}s as well -- to avoid
+ * this, use {@link ObjectReader#with(JsonParser.Feature)} instead.
*/
public ObjectMapper configure(JsonParser.Feature f, boolean state) {
_jsonFactory.configure(f, state);
@@ -1987,6 +2129,10 @@
* for parser instances this object mapper creates.
*<p>
* Note that this is equivalent to directly calling same method on {@link #getFactory}.
+ *<p>
+ * WARNING: since this method directly modifies state of underlying {@link JsonFactory},
+ * it will change observed configuration by {@link ObjectReader}s as well -- to avoid
+ * this, use {@link ObjectReader#with(JsonParser.Feature)} instead.
*
* @since 2.5
*/
@@ -2002,6 +2148,10 @@
* for parser instances this object mapper creates.
*<p>
* Note that this is equivalent to directly calling same method on {@link #getFactory}.
+ *<p>
+ * WARNING: since this method directly modifies state of underlying {@link JsonFactory},
+ * it will change observed configuration by {@link ObjectReader}s as well -- to avoid
+ * this, use {@link ObjectReader#without(JsonParser.Feature)} instead.
*
* @since 2.5
*/
@@ -2028,6 +2178,10 @@
*<p>
* Note that this is equivalent to directly calling same method
* on {@link #getFactory}.
+ *<p>
+ * WARNING: since this method directly modifies state of underlying {@link JsonFactory},
+ * it will change observed configuration by {@link ObjectWriter}s as well -- to avoid
+ * this, use {@link ObjectWriter#with(JsonGenerator.Feature)} instead.
*/
public ObjectMapper configure(JsonGenerator.Feature f, boolean state) {
_jsonFactory.configure(f, state);
@@ -2039,6 +2193,10 @@
* for parser instances this object mapper creates.
*<p>
* Note that this is equivalent to directly calling same method on {@link #getFactory}.
+ *<p>
+ * WARNING: since this method directly modifies state of underlying {@link JsonFactory},
+ * it will change observed configuration by {@link ObjectWriter}s as well -- to avoid
+ * this, use {@link ObjectWriter#with(JsonGenerator.Feature)} instead.
*
* @since 2.5
*/
@@ -2054,6 +2212,10 @@
* for parser instances this object mapper creates.
*<p>
* Note that this is equivalent to directly calling same method on {@link #getFactory}.
+ *<p>
+ * WARNING: since this method directly modifies state of underlying {@link JsonFactory},
+ * it will change observed configuration by {@link ObjectWriter}s as well -- to avoid
+ * this, use {@link ObjectWriter#without(JsonGenerator.Feature)} instead.
*
* @since 2.5
*/
@@ -2096,7 +2258,7 @@
* Note: this method should NOT be used if the result type is a
* container ({@link java.util.Collection} or {@link java.util.Map}.
* The reason is that due to type erasure, key and value types
- * can not be introspected when using this method.
+ * cannot be introspected when using this method.
*
* @throws IOException if a low-level I/O problem (unexpected end-of-input,
* network error) occurs (passed through as-is without additional wrapping -- note
@@ -2331,11 +2493,9 @@
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
- public JsonNode readTree(InputStream in)
- throws IOException, JsonProcessingException
+ public JsonNode readTree(InputStream in) throws IOException
{
- JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(in), JSON_NODE_TYPE);
- return (n == null) ? NullNode.instance : n;
+ return _readTreeAndClose(_jsonFactory.createParser(in));
}
/**
@@ -2361,11 +2521,8 @@
* as a non-null {@link JsonNode} (one that returns <code>true</code>
* for {@link JsonNode#isNull()}
*/
- public JsonNode readTree(Reader r)
- throws IOException, JsonProcessingException
- {
- JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(r), JSON_NODE_TYPE);
- return (n == null) ? NullNode.instance : n;
+ public JsonNode readTree(Reader r) throws IOException {
+ return _readTreeAndClose(_jsonFactory.createParser(r));
}
/**
@@ -2391,11 +2548,8 @@
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
- public JsonNode readTree(String content)
- throws IOException, JsonProcessingException
- {
- JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(content), JSON_NODE_TYPE);
- return (n == null) ? NullNode.instance : n;
+ public JsonNode readTree(String content) throws IOException {
+ return _readTreeAndClose(_jsonFactory.createParser(content));
}
/**
@@ -2414,11 +2568,8 @@
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
- public JsonNode readTree(byte[] content)
- throws IOException, JsonProcessingException
- {
- JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(content), JSON_NODE_TYPE);
- return (n == null) ? NullNode.instance : n;
+ public JsonNode readTree(byte[] content) throws IOException {
+ return _readTreeAndClose(_jsonFactory.createParser(content));
}
/**
@@ -2444,8 +2595,7 @@
public JsonNode readTree(File file)
throws IOException, JsonProcessingException
{
- JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(file), JSON_NODE_TYPE);
- return (n == null) ? NullNode.instance : n;
+ return _readTreeAndClose(_jsonFactory.createParser(file));
}
/**
@@ -2468,11 +2618,8 @@
* @throws JsonParseException if underlying input contains invalid content
* of type {@link JsonParser} supports (JSON for default case)
*/
- public JsonNode readTree(URL source)
- throws IOException, JsonProcessingException
- {
- JsonNode n = (JsonNode) _readMapAndClose(_jsonFactory.createParser(source), JSON_NODE_TYPE);
- return (n == null) ? NullNode.instance : n;
+ public JsonNode readTree(URL source) throws IOException {
+ return _readTreeAndClose(_jsonFactory.createParser(source));
}
/*
@@ -2547,7 +2694,7 @@
/**
*<p>
* Note: return type is co-variant, as basic ObjectCodec
- * abstraction can not refer to concrete node types (as it's
+ * abstraction cannot refer to concrete node types (as it's
* part of core package, whereas impls are part of mapper
* package)
*/
@@ -2559,7 +2706,7 @@
/**
*<p>
* Note: return type is co-variant, as basic ObjectCodec
- * abstraction can not refer to concrete node types (as it's
+ * abstraction cannot refer to concrete node types (as it's
* part of core package, whereas impls are part of mapper
* package)
*/
@@ -3071,14 +3218,14 @@
SegmentedStringWriter sw = new SegmentedStringWriter(_jsonFactory._getBufferRecycler());
try {
_configAndWriteValue(_jsonFactory.createGenerator(sw), value);
- } catch (JsonProcessingException e) { // to support [JACKSON-758]
+ } catch (JsonProcessingException e) {
throw e;
} catch (IOException e) { // shouldn't really happen, but is declared as possibility so:
throw JsonMappingException.fromUnexpectedIOE(e);
}
return sw.getAndClear();
}
-
+
/**
* Method that can be used to serialize any Java value as
* a byte array. Functionally equivalent to calling
@@ -3519,8 +3666,6 @@
public <T> T convertValue(Object fromValue, Class<T> toValueType)
throws IllegalArgumentException
{
- // sanity check for null first:
- if (fromValue == null) return null;
return (T) _convert(fromValue, _typeFactory.constructType(toValueType));
}
@@ -3531,7 +3676,7 @@
public <T> T convertValue(Object fromValue, TypeReference<?> toValueTypeRef)
throws IllegalArgumentException
{
- return (T) convertValue(fromValue, _typeFactory.constructType(toValueTypeRef));
+ return (T) _convert(fromValue, _typeFactory.constructType(toValueTypeRef));
}
/**
@@ -3541,8 +3686,6 @@
public <T> T convertValue(Object fromValue, JavaType toValueType)
throws IllegalArgumentException
{
- // sanity check for null first:
- if (fromValue == null) return null;
return (T) _convert(fromValue, toValueType);
}
@@ -3557,17 +3700,20 @@
@SuppressWarnings("resource")
protected Object _convert(Object fromValue, JavaType toValueType)
throws IllegalArgumentException
- {
- // also, as per [databind#11], consider case for simple cast
- /* But with caveats: one is that while everything is Object.class, we don't
- * want to "optimize" that out; and the other is that we also do not want
- * to lose conversions of generic types.
- */
- Class<?> targetType = toValueType.getRawClass();
- if (targetType != Object.class
- && !toValueType.hasGenericTypes()
- && targetType.isAssignableFrom(fromValue.getClass())) {
- return fromValue;
+ {
+ // [databind#1433] Do not shortcut null values.
+ // This defaults primitives and fires deserializer getNullValue hooks.
+ if (fromValue != null) {
+ // also, as per [databind#11], consider case for simple cast
+ // But with caveats: one is that while everything is Object.class, we don't
+ // want to "optimize" that out; and the other is that we also do not want
+ // to lose conversions of generic types.
+ Class<?> targetType = toValueType.getRawClass();
+ if (targetType != Object.class
+ && !toValueType.hasGenericTypes()
+ && targetType.isAssignableFrom(fromValue.getClass())) {
+ return fromValue;
+ }
}
// Then use TokenBuffer, which is a JsonGenerator:
@@ -3587,7 +3733,7 @@
Object result;
// ok to pass in existing feature flags; unwrapping handled by mapper
final DeserializationConfig deserConfig = getDeserializationConfig();
- JsonToken t = _initForReading(p);
+ JsonToken t = _initForReading(p, toValueType);
if (t == JsonToken.VALUE_NULL) {
DeserializationContext ctxt = createDeserializationContext(p, deserConfig);
result = _findRootDeserializer(ctxt, toValueType).getNullValue(ctxt);
@@ -3606,6 +3752,69 @@
}
}
+ /**
+ * Convenience method similar to {@link #convertValue(Object, JavaType)} but one
+ * in which
+ *<p>
+ * Implementation is approximately as follows:
+ *<ol>
+ * <li>Serialize `updateWithValue` into {@link TokenBuffer}</li>
+ * <li>Construct {@link ObjectReader} with `valueToUpdate` (using {@link #readerForUpdating(Object)})
+ * </li>
+ * <li>Construct {@link JsonParser} (using {@link TokenBuffer#asParser()})
+ * </li>
+ * <li>Update using {@link ObjectReader#readValue(JsonParser)}.
+ * </li>
+ * <li>Return `valueToUpdate`
+ * </li>
+ *</ol>
+ *<p>
+ * Note that update is "shallow" in that only first level of properties (or, immediate contents
+ * of container to update) are modified, unless properties themselves indicate that
+ * merging should be applied for contents. Such merging can be specified using
+ * annotations (see <code>JsonMerge</code>) as well as using "config overrides" (see
+ * {@link #configOverride(Class)} and {@link #setDefaultMergeable(Boolean)}).
+ *
+ * @param valueToUpdate Object to update
+ * @param overrides Object to conceptually serialize and merge into value to
+ * update; can be thought of as a provider for overrides to apply.
+ *
+ * @return Either the first argument (`valueToUpdate`), if it is mutable; or a result of
+ * creating new instance that is result of "merging" values (for example, "updating" a
+ * Java array will create a new array)
+ *
+ * @throws JsonMappingException if there are structural incompatibilities that prevent update.
+ *
+ * @since 2.9
+ */
+ @SuppressWarnings("resource")
+ public <T> T updateValue(T valueToUpdate, Object overrides)
+ throws JsonMappingException
+ {
+ T result = valueToUpdate;
+ if ((valueToUpdate != null) && (overrides != null)) {
+ TokenBuffer buf = new TokenBuffer(this, false);
+ if (isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
+ buf = buf.forceUseOfBigDecimal(true);
+ }
+ try {
+ SerializationConfig config = getSerializationConfig().
+ without(SerializationFeature.WRAP_ROOT_VALUE);
+ _serializerProvider(config).serializeValue(buf, overrides);
+ JsonParser p = buf.asParser();
+ result = readerForUpdating(valueToUpdate).readValue(p);
+ p.close();
+ } catch (IOException e) { // should not occur, no real i/o...
+ if (e instanceof JsonMappingException) {
+ throw (JsonMappingException) e;
+ }
+ // 17-Mar-2017, tatu: Really ought not happen...
+ throw JsonMappingException.fromUnexpectedIOE(e);
+ }
+ }
+ return result;
+ }
+
/*
/**********************************************************
/* Extended Public API: JSON Schema generation
@@ -3644,7 +3853,7 @@
{
acceptJsonFormatVisitor(_typeFactory.constructType(type), visitor);
}
-
+
/**
* Method for visiting type hierarchy for given type, using specified visitor.
* Visitation uses <code>Serializer</code> hierarchy and related properties
@@ -3665,7 +3874,7 @@
}
_serializerProvider(getSerializationConfig()).acceptJsonFormatVisitor(type, visitor);
}
-
+
/*
/**********************************************************
/* Internal methods for serialization, overridable
@@ -3696,7 +3905,7 @@
try {
_serializerProvider(cfg).serializeValue(g, value);
} catch (Exception e) {
- ClassUtil.closeOnFailAndThrowAsIAE(g, e);
+ ClassUtil.closeOnFailAndThrowAsIOE(g, e);
return;
}
g.close();
@@ -3716,7 +3925,7 @@
toClose = null;
tmpToClose.close();
} catch (Exception e) {
- ClassUtil.closeOnFailAndThrowAsIAE(g, toClose, e);
+ ClassUtil.closeOnFailAndThrowAsIOE(g, toClose, e);
return;
}
g.close();
@@ -3736,7 +3945,7 @@
g.flush();
}
} catch (Exception e) {
- ClassUtil.closeOnFailAndThrowAsIAE(null, toClose, e);
+ ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
return;
}
toClose.close();
@@ -3749,19 +3958,10 @@
*/
/**
- * Internal helper method called to create an instance of {@link DeserializationContext}
- * for deserializing a single root value.
- * Can be overridden if a custom context is needed.
- */
- protected DefaultDeserializationContext createDeserializationContext(JsonParser p,
- DeserializationConfig cfg) {
- return _deserializationContext.createInstance(cfg, p, _injectableValues);
- }
-
- /**
* Actual implementation of value reading+binding operation.
*/
- protected Object _readValue(DeserializationConfig cfg, JsonParser p, JavaType valueType)
+ protected Object _readValue(DeserializationConfig cfg, JsonParser p,
+ JavaType valueType)
throws IOException
{
/* First: may need to read the next token, to initialize
@@ -3769,15 +3969,14 @@
* previous token has been cleared)
*/
Object result;
- JsonToken t = _initForReading(p);
+ JsonToken t = _initForReading(p, valueType);
+ final DeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
- DeserializationContext ctxt = createDeserializationContext(p, cfg);
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
- DeserializationContext ctxt = createDeserializationContext(p, cfg);
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
// ok, let's get the value
if (cfg.useRootWrapping()) {
@@ -3788,25 +3987,26 @@
}
// Need to consume the token too
p.clearCurrentToken();
+ if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
+ _verifyNoTrailingTokens(p, ctxt, valueType);
+ }
return result;
}
-
+
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
try (JsonParser p = p0) {
Object result;
- JsonToken t = _initForReading(p);
+ JsonToken t = _initForReading(p, valueType);
+ final DeserializationConfig cfg = getDeserializationConfig();
+ final DeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
- DeserializationContext ctxt = createDeserializationContext(p,
- getDeserializationConfig());
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else {
- DeserializationConfig cfg = getDeserializationConfig();
- DeserializationContext ctxt = createDeserializationContext(p, cfg);
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
if (cfg.useRootWrapping()) {
result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
@@ -3815,46 +4015,54 @@
}
ctxt.checkUnresolvedObjectId();
}
- // Need to consume the token too
- p.clearCurrentToken();
+ if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
+ _verifyNoTrailingTokens(p, ctxt, valueType);
+ }
return result;
}
}
/**
- * Method called to ensure that given parser is ready for reading
- * content for data binding.
+ * Similar to {@link #_readMapAndClose} but specialized for <code>JsonNode</code>
+ * reading.
*
- * @return First token to be used for data binding after this call:
- * can never be null as exception will be thrown if parser can not
- * provide more tokens.
- *
- * @throws IOException if the underlying input source has problems during
- * parsing
- * @throws JsonParseException if parser has problems parsing content
- * @throws JsonMappingException if the parser does not have any more
- * content to map (note: Json "null" value is considered content;
- * enf-of-stream not)
+ * @since 2.9
*/
- protected JsonToken _initForReading(JsonParser p) throws IOException
+ protected JsonNode _readTreeAndClose(JsonParser p0) throws IOException
{
- _deserializationConfig.initialize(p); // since 2.5
+ try (JsonParser p = p0) {
+ final JavaType valueType = JSON_NODE_TYPE;
- /* 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();
- if (t == null) {
- // and then we must get something...
- t = p.nextToken();
+ DeserializationConfig cfg = getDeserializationConfig();
+ // 27-Oct-2016, tatu: Need to inline `_initForReading()` due to
+ // special requirements by tree reading (no fail on eof)
+
+ cfg.initialize(p); // since 2.5
+ JsonToken t = p.getCurrentToken();
if (t == null) {
- // Throw mapping exception, since it's failure to map,
- // not an actual parsing problem
- throw JsonMappingException.from(p, "No content to map due to end-of-input");
+ t = p.nextToken();
+ if (t == null) { // [databind#1406]: expose end-of-input as `null`
+ return null;
+ }
}
+ if (t == JsonToken.VALUE_NULL) {
+ return cfg.getNodeFactory().nullNode();
+ }
+ DeserializationContext ctxt = createDeserializationContext(p, cfg);
+ JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
+ Object result;
+ if (cfg.useRootWrapping()) {
+ result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
+ } else {
+ result = deser.deserialize(p, ctxt);
+ if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
+ _verifyNoTrailingTokens(p, ctxt, valueType);
+ }
+ }
+ // No ObjectIds so can ignore
+// ctxt.checkUnresolvedObjectId();
+ return (JsonNode) result;
}
- return t;
}
protected Object _unwrapAndDeserialize(JsonParser p, DeserializationContext ctxt,
@@ -3866,19 +4074,19 @@
// 12-Jun-2015, tatu: Should try to support namespaces etc but...
String expSimpleName = expRootName.getSimpleName();
if (p.getCurrentToken() != JsonToken.START_OBJECT) {
- ctxt.reportWrongTokenException(p, 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(p, JsonToken.FIELD_NAME,
- "Current token not FIELD_NAME (to contain expected root name '"
- +expSimpleName+"'), but "+p.getCurrentToken());
+ 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.reportMappingException("Root name '%s' does not match expected ('%s') for type %s",
+ ctxt.reportInputMismatch(rootType,
+ "Root name '%s' does not match expected ('%s') for type %s",
actualName, expSimpleName, rootType);
}
// ok, then move to value itself....
@@ -3886,13 +4094,81 @@
Object result = deser.deserialize(p, ctxt);
// and last, verify that we now get matching END_OBJECT
if (p.nextToken() != JsonToken.END_OBJECT) {
- ctxt.reportWrongTokenException(p, 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}
+ * for deserializing a single root value.
+ * Can be overridden if a custom context is needed.
+ */
+ protected DefaultDeserializationContext createDeserializationContext(JsonParser p,
+ DeserializationConfig cfg) {
+ return _deserializationContext.createInstance(cfg, p, _injectableValues);
+ }
+
+ /**
+ * Method called to ensure that given parser is ready for reading
+ * content for data binding.
+ *
+ * @return First token to be used for data binding after this call:
+ * can never be null as exception will be thrown if parser cannot
+ * provide more tokens.
+ *
+ * @throws IOException if the underlying input source has problems during
+ * parsing
+ * @throws JsonParseException if parser has problems parsing content
+ * @throws JsonMappingException if the parser does not have any more
+ * content to map (note: Json "null" value is considered content;
+ * enf-of-stream not)
+ */
+ protected JsonToken _initForReading(JsonParser p, JavaType targetType) throws IOException
+ {
+ _deserializationConfig.initialize(p); // since 2.5
+
+ // 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();
+ if (t == null) {
+ // and then we must get something...
+ t = p.nextToken();
+ if (t == null) {
+ // Throw mapping exception, since it's failure to map,
+ // not an actual parsing problem
+ throw MismatchedInputException.from(p, targetType,
+ "No content to map due to end-of-input");
+ }
+ }
+ return t;
+ }
+
+ @Deprecated // since 2.9, use method that takes JavaType too
+ protected JsonToken _initForReading(JsonParser p) throws IOException {
+ return _initForReading(p, null);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected final void _verifyNoTrailingTokens(JsonParser p, DeserializationContext ctxt,
+ JavaType bindType)
+ throws IOException
+ {
+ JsonToken t = p.nextToken();
+ if (t != null) {
+ Class<?> bt = ClassUtil.rawClass(bindType);
+ ctxt.reportTrailingTokens(bt, p, t);
+ }
+ }
+
/*
/**********************************************************
/* Internal methods, other
@@ -3914,8 +4190,8 @@
// Nope: need to ask provider to resolve it
deser = ctxt.findRootValueDeserializer(valueType);
if (deser == null) { // can this happen?
- throw JsonMappingException.from(ctxt,
- "Can not find a deserializer for type "+valueType);
+ return ctxt.reportBadDefinition(valueType,
+ "Cannot find a deserializer for type "+valueType);
}
_rootDeserializers.put(valueType, deser);
return deser;
@@ -3928,7 +4204,7 @@
{
if (schema != null) {
if (!_jsonFactory.canUseSchema(schema)) {
- throw new IllegalArgumentException("Can not use FormatSchema of type "+schema.getClass().getName()
+ throw new IllegalArgumentException("Cannot use FormatSchema of type "+schema.getClass().getName()
+" for format "+_jsonFactory.getFormatName());
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
index d1ec95f..d313c42 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
@@ -11,15 +11,16 @@
import com.fasterxml.jackson.core.filter.TokenFilter;
import com.fasterxml.jackson.core.type.ResolvedType;
import com.fasterxml.jackson.core.type.TypeReference;
+
import com.fasterxml.jackson.databind.cfg.ContextAttributes;
import com.fasterxml.jackson.databind.deser.DataFormatReaders;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Builder object that can be used for per-serialization configuration of
@@ -32,12 +33,18 @@
* Instances are initially constructed by {@link ObjectMapper} and can be
* reused, shared, cached; both because of thread-safety and because
* instances are relatively light-weight.
+ *<p>
+ * NOTE: this class is NOT meant as sub-classable (with Jackson 2.8 and
+ * above) by users. It is left as non-final mostly to allow frameworks
+ * that require bytecode generation for proxying and similar use cases,
+ * but there is no expecation that functionality should be extended
+ * by sub-classing.
*/
public class ObjectReader
extends ObjectCodec
implements Versioned, java.io.Serializable // since 2.1
{
- private static final long serialVersionUID = 1L; // since 2.5
+ private static final long serialVersionUID = 2L; // since 2.9
private final static JavaType JSON_NODE_TYPE = SimpleType.constructUnsafe(JsonNode.class);
@@ -107,7 +114,7 @@
* this value object will be updated instead.
* Note that value can be of almost any type, except not
* {@link com.fasterxml.jackson.databind.type.ArrayType}; array
- * types can not be modified because array size is immutable.
+ * types cannot be modified because array size is immutable.
*/
protected final Object _valueToUpdate;
@@ -129,7 +136,7 @@
* NOTE: If defined non-null, <code>readValue()</code> methods that take
* {@link Reader} or {@link String} input <b>will fail with exception</b>,
* because format-detection only works on byte-sources. Also, if format
- * can not be detect reliably (as per detector settings),
+ * cannot be detect reliably (as per detector settings),
* a {@link JsonParseException} will be thrown).
*
* @since 2.1
@@ -175,9 +182,6 @@
_parserFactory = mapper._jsonFactory;
_valueType = valueType;
_valueToUpdate = valueToUpdate;
- if (valueToUpdate != null && valueType.isArrayType()) {
- throw new IllegalArgumentException("Can not update an array value");
- }
_schema = schema;
_injectableValues = injectableValues;
_unwrapRoot = config.useRootWrapping();
@@ -204,9 +208,6 @@
_valueType = valueType;
_rootDeserializer = rootDeser;
_valueToUpdate = valueToUpdate;
- if (valueToUpdate != null && valueType.isArrayType()) {
- throw new IllegalArgumentException("Can not update an array value");
- }
_schema = schema;
_injectableValues = injectableValues;
_unwrapRoot = config.useRootWrapping();
@@ -281,9 +282,9 @@
/*
/**********************************************************
- /* Methods sub-classes MUST override, used for constructing
- /* reader instances, (re)configuring parser instances
- /* Added in 2.5
+ /* Helper methods used internally for invoking constructors
+ /* Need to be overridden if sub-classing (not recommended)
+ /* is used.
/**********************************************************
*/
@@ -333,8 +334,7 @@
/*
/**********************************************************
- /* Methods sub-classes may choose to override, if customized
- /* initialization is needed.
+ /* Methods for initializing parser instance to use
/**********************************************************
*/
@@ -355,7 +355,8 @@
t = p.nextToken();
if (t == null) {
// Throw mapping exception, since it's failure to map, not an actual parsing problem
- ctxt.reportMissingContent(null); // default msg is fine
+ ctxt.reportInputMismatch(_valueType,
+ "No content to map due to end-of-input");
}
}
return t;
@@ -808,7 +809,7 @@
* to construct appropriate {@link JsonParser} for actual parsing.
*<p>
* Note: since format detection only works with byte sources, it is possible to
- * get a failure from some 'readValue()' methods. Also, if input can not be reliably
+ * get a failure from some 'readValue()' methods. Also, if input cannot be reliably
* (enough) detected as one of specified types, an exception will be thrown.
*<p>
* Note: not all {@link JsonFactory} types can be passed: specifically, ones that
@@ -832,7 +833,7 @@
* {@link DataFormatReaders}.
*<p>
* NOTE: since format detection only works with byte sources, it is possible to
- * get a failure from some 'readValue()' methods. Also, if input can not be reliably
+ * get a failure from some 'readValue()' methods. Also, if input cannot be reliably
* (enough) detected as one of specified types, an exception will be thrown.
*
* @param readers DataFormatReaders to use for detecting underlying format.
@@ -1010,7 +1011,7 @@
*/
@Override
@SuppressWarnings("unchecked")
- public <T> T readValue(JsonParser p, ResolvedType valueType) throws IOException, JsonProcessingException {
+ public <T> T readValue(JsonParser p, ResolvedType valueType) throws IOException {
return (T) forType((JavaType)valueType).readValue(p);
}
@@ -1138,7 +1139,10 @@
@Override
public JsonParser treeAsTokens(TreeNode n) {
- return new TreeTraversingParser((JsonNode) n, this);
+ // 05-Dec-2017, tatu: Important! Must clear "valueToUpdate" since we do not
+ // want update to be applied here, as a side effect
+ ObjectReader codec = withValueToUpdate(null);
+ return new TreeTraversingParser((JsonNode) n, codec);
}
/**
@@ -1159,7 +1163,7 @@
}
@Override
- public void writeTree(JsonGenerator jgen, TreeNode rootNode) {
+ public void writeTree(JsonGenerator g, TreeNode rootNode) {
throw new UnsupportedOperationException();
}
@@ -1176,13 +1180,11 @@
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
- public <T> T readValue(InputStream src)
- throws IOException, JsonProcessingException
+ public <T> T readValue(InputStream src) throws IOException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(_dataFormatReaders.findFormat(src), false);
}
-
return (T) _bindAndClose(_considerFilter(_parserFactory.createParser(src), false));
}
@@ -1193,16 +1195,14 @@
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
- public <T> T readValue(Reader src)
- throws IOException, JsonProcessingException
+ public <T> T readValue(Reader src) throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
}
-
return (T) _bindAndClose(_considerFilter(_parserFactory.createParser(src), false));
}
-
+
/**
* Method that binds content read from given JSON string,
* using configuration of this reader.
@@ -1210,8 +1210,7 @@
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
- public <T> T readValue(String src)
- throws IOException, JsonProcessingException
+ public <T> T readValue(String src) throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
@@ -1227,13 +1226,11 @@
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
- public <T> T readValue(byte[] src)
- throws IOException, JsonProcessingException
+ public <T> T readValue(byte[] src) throws IOException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(src, 0, src.length);
}
-
return (T) _bindAndClose(_considerFilter(_parserFactory.createParser(src), false));
}
@@ -1245,19 +1242,18 @@
*/
@SuppressWarnings("unchecked")
public <T> T readValue(byte[] src, int offset, int length)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(src, offset, length);
}
-
return (T) _bindAndClose(_considerFilter(_parserFactory.createParser(src, offset, length),
false));
}
@SuppressWarnings("unchecked")
public <T> T readValue(File src)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(_dataFormatReaders.findFormat(_inputStream(src)), true);
@@ -1274,12 +1270,11 @@
*/
@SuppressWarnings("unchecked")
public <T> T readValue(URL src)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(_dataFormatReaders.findFormat(_inputStream(src)), true);
}
-
return (T) _bindAndClose(_considerFilter(_parserFactory.createParser(src), false));
}
@@ -1290,14 +1285,13 @@
* objectReader.readValue(src.traverse())
*</pre>
*/
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({ "unchecked", "resource" })
public <T> T readValue(JsonNode src)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
}
-
return (T) _bindAndClose(_considerFilter(treeAsTokens(src), false));
}
@@ -1322,8 +1316,7 @@
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
- public JsonNode readTree(InputStream in)
- throws IOException, JsonProcessingException
+ public JsonNode readTree(InputStream in) throws IOException
{
if (_dataFormatReaders != null) {
return _detectBindAndCloseAsTree(in);
@@ -1340,8 +1333,7 @@
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
- public JsonNode readTree(Reader r)
- throws IOException, JsonProcessingException
+ public JsonNode readTree(Reader r) throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(r);
@@ -1358,8 +1350,7 @@
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
- public JsonNode readTree(String json)
- throws IOException, JsonProcessingException
+ public JsonNode readTree(String json) throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(json);
@@ -1393,7 +1384,7 @@
* to the token following it.
*/
public <T> MappingIterator<T> readValues(JsonParser p)
- throws IOException, JsonProcessingException
+ throws IOException
{
DeserializationContext ctxt = createDeserializationContext(p);
// false -> do not close as caller gave parser instance
@@ -1421,7 +1412,7 @@
* <code>START_ARRAY</code> which is part of the first element).
*/
public <T> MappingIterator<T> readValues(InputStream src)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(_dataFormatReaders.findFormat(src), false);
@@ -1435,7 +1426,7 @@
*/
@SuppressWarnings("resource")
public <T> MappingIterator<T> readValues(Reader src)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
@@ -1454,7 +1445,7 @@
*/
@SuppressWarnings("resource")
public <T> MappingIterator<T> readValues(String json)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(json);
@@ -1470,7 +1461,7 @@
* Overloaded version of {@link #readValue(InputStream)}.
*/
public <T> MappingIterator<T> readValues(byte[] src, int offset, int length)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(_dataFormatReaders.findFormat(src, offset, length), false);
@@ -1483,7 +1474,7 @@
* Overloaded version of {@link #readValue(InputStream)}.
*/
public final <T> MappingIterator<T> readValues(byte[] src)
- throws IOException, JsonProcessingException {
+ throws IOException {
return readValues(src, 0, src.length);
}
@@ -1491,7 +1482,7 @@
* Overloaded version of {@link #readValue(InputStream)}.
*/
public <T> MappingIterator<T> readValues(File src)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(
@@ -1506,7 +1497,7 @@
* @param src URL to read to access JSON content to parse.
*/
public <T> MappingIterator<T> readValues(URL src)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(
@@ -1540,12 +1531,12 @@
} catch (JsonProcessingException e) {
throw e;
} catch (IOException e) { // should not occur, no real i/o...
- throw new IllegalArgumentException(e.getMessage(), e);
+ throw JsonMappingException.fromUnexpectedIOE(e);
}
}
@Override
- public void writeValue(JsonGenerator gen, Object value) throws IOException, JsonProcessingException {
+ public void writeValue(JsonGenerator gen, Object value) throws IOException {
throw new UnsupportedOperationException("Not implemented for ObjectReader");
}
@@ -1554,7 +1545,7 @@
/* Helper methods, data-binding
/**********************************************************
*/
-
+
/**
* Actual implementation of value reading+binding operation.
*/
@@ -1582,26 +1573,20 @@
if (valueToUpdate == null) {
result = deser.deserialize(p, ctxt);
} else {
- deser.deserialize(p, ctxt, valueToUpdate);
- result = valueToUpdate;
+ // 20-Mar-2017, tatu: Important! May be different from `valueToUpdate`
+ // for immutable Objects like Java arrays; logical result
+ result = deser.deserialize(p, ctxt, valueToUpdate);
}
}
}
// Need to consume the token too
p.clearCurrentToken();
+ if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
+ _verifyNoTrailingTokens(p, ctxt, _valueType);
+ }
return result;
}
-
- /**
- * Consider filter when creating JsonParser.
- */
- protected JsonParser _considerFilter(final JsonParser p, boolean multiValue) {
- // 26-Mar-2016, tatu: Need to allow multiple-matches at least if we have
- // have a multiple-value read (that is, "readValues()").
- return ((_filter == null) || FilteringParserDelegate.class.isInstance(p))
- ? p : new FilteringParserDelegate(p, _filter, false, multiValue);
- }
-
+
protected Object _bindAndClose(JsonParser p0) throws IOException
{
try (JsonParser p = p0) {
@@ -1630,36 +1615,53 @@
}
}
}
+ if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
+ _verifyNoTrailingTokens(p, ctxt, _valueType);
+ }
return result;
}
}
- protected JsonNode _bindAndCloseAsTree(JsonParser p0) throws IOException {
+ protected final JsonNode _bindAndCloseAsTree(JsonParser p0) throws IOException {
try (JsonParser p = p0) {
return _bindAsTree(p);
}
}
-
- protected JsonNode _bindAsTree(JsonParser p) throws IOException
+
+ protected final JsonNode _bindAsTree(JsonParser p) throws IOException
{
- JsonNode result;
- DeserializationContext ctxt = createDeserializationContext(p);
- JsonToken t = _initForReading(ctxt, p);
- if (t == JsonToken.VALUE_NULL || t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
- result = NullNode.instance;
- } else {
- JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
- if (_unwrapRoot) {
- result = (JsonNode) _unwrapAndDeserialize(p, ctxt, JSON_NODE_TYPE, deser);
- } else {
- result = (JsonNode) deser.deserialize(p, ctxt);
+ // 27-Oct-2016, tatu: Need to inline `_initForReading()` due to
+ // special requirements by tree reading (no fail on eof)
+
+ _config.initialize(p);
+ if (_schema != null) {
+ p.setSchema(_schema);
+ }
+
+ JsonToken t = p.getCurrentToken();
+ if (t == null) {
+ t = p.nextToken();
+ if (t == null) { // [databind#1406]: expose end-of-input as `null`
+ return null;
}
}
- // Need to consume the token too
- p.clearCurrentToken();
- return result;
+ DeserializationContext ctxt = createDeserializationContext(p);
+ if (t == JsonToken.VALUE_NULL) {
+ return ctxt.getNodeFactory().nullNode();
+ }
+ JsonDeserializer<Object> deser = _findTreeDeserializer(ctxt);
+ Object result;
+ if (_unwrapRoot) {
+ result = _unwrapAndDeserialize(p, ctxt, JSON_NODE_TYPE, deser);
+ } else {
+ result = deser.deserialize(p, ctxt);
+ if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
+ _verifyNoTrailingTokens(p, ctxt, JSON_NODE_TYPE);
+ }
+ }
+ return (JsonNode) result;
}
-
+
/**
* @since 2.1
*/
@@ -1679,18 +1681,19 @@
String expSimpleName = expRootName.getSimpleName();
if (p.getCurrentToken() != JsonToken.START_OBJECT) {
- ctxt.reportWrongTokenException(p, 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(p, 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.reportMappingException("Root name '%s' does not match expected ('%s') for type %s",
+ ctxt.reportInputMismatch(rootType,
+ "Root name '%s' does not match expected ('%s') for type %s",
actualName, expSimpleName, rootType);
}
// ok, then move to value itself....
@@ -1704,13 +1707,45 @@
}
// and last, verify that we now get matching END_OBJECT
if (p.nextToken() != JsonToken.END_OBJECT) {
- ctxt.reportWrongTokenException(p, 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.
+ */
+ protected JsonParser _considerFilter(final JsonParser p, boolean multiValue) {
+ // 26-Mar-2016, tatu: Need to allow multiple-matches at least if we have
+ // have a multiple-value read (that is, "readValues()").
+ return ((_filter == null) || FilteringParserDelegate.class.isInstance(p))
+ ? p : new FilteringParserDelegate(p, _filter, false, multiValue);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected final void _verifyNoTrailingTokens(JsonParser p, DeserializationContext ctxt,
+ JavaType bindType)
+ throws IOException
+ {
+ JsonToken t = p.nextToken();
+ if (t != null) {
+ Class<?> bt = ClassUtil.rawClass(bindType);
+ if (bt == null) {
+ if (_valueToUpdate != null) {
+ bt = _valueToUpdate.getClass();
+ }
+ }
+ ctxt.reportTrailingTokens(bt, p, t);
+ }
+ }
+
/*
/**********************************************************
/* Internal methods, format auto-detection
@@ -1747,7 +1782,7 @@
@SuppressWarnings("resource")
protected <T> MappingIterator<T> _detectBindAndReadValues(DataFormatReaders.Match match, boolean forceClosing)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (!match.hasMatch()) {
_reportUnkownFormat(_dataFormatReaders, match);
@@ -1778,10 +1813,11 @@
* Method called to indicate that format detection failed to detect format
* of given input
*/
- protected void _reportUnkownFormat(DataFormatReaders detector, DataFormatReaders.Match match) throws JsonProcessingException
+ protected void _reportUnkownFormat(DataFormatReaders detector, DataFormatReaders.Match match)
+ throws JsonProcessingException
{
// 17-Aug-2015, tatu: Unfortunately, no parser/generator available so:
- throw new JsonParseException(null, "Can not detect format from input, does not look like any of detectable formats "
+ throw new JsonParseException(null, "Cannot detect format from input, does not look like any of detectable formats "
+detector.toString());
}
@@ -1798,7 +1834,7 @@
{
if (schema != null) {
if (!_parserFactory.canUseSchema(schema)) {
- throw new IllegalArgumentException("Can not use FormatSchema of type "+schema.getClass().getName()
+ throw new IllegalArgumentException("Cannot use FormatSchema of type "+schema.getClass().getName()
+" for format "+_parserFactory.getFormatName());
}
}
@@ -1813,13 +1849,6 @@
return _context.createInstance(_config, p, _injectableValues);
}
- protected void _reportUndetectableSource(Object src) throws JsonProcessingException
- {
- // 17-Aug-2015, tatu: Unfortunately, no parser/generator available so:
- throw new JsonParseException(null, "Can not use source of type "
- +src.getClass().getName()+" with format auto-detection: must be byte- not char-based");
- }
-
protected InputStream _inputStream(URL src) throws IOException {
return src.openStream();
}
@@ -1828,6 +1857,13 @@
return new FileInputStream(f);
}
+ protected void _reportUndetectableSource(Object src) throws JsonProcessingException
+ {
+ // 17-Aug-2015, tatu: Unfortunately, no parser/generator available so:
+ throw new JsonParseException(null, "Cannot use source of type "
+ +src.getClass().getName()+" with format auto-detection: must be byte- not char-based");
+ }
+
/*
/**********************************************************
/* Helper methods, locating deserializers etc
@@ -1847,9 +1883,9 @@
// Sanity check: must have actual type...
JavaType t = _valueType;
if (t == null) {
- ctxt.reportMappingException("No value type configured for ObjectReader");
+ ctxt.reportBadDefinition((JavaType) null,
+ "No value type configured for ObjectReader");
}
-
// First: have we already seen it?
JsonDeserializer<Object> deser = _rootDeserializers.get(t);
if (deser != null) {
@@ -1858,7 +1894,7 @@
// Nope: need to ask provider to resolve it
deser = ctxt.findRootValueDeserializer(t);
if (deser == null) { // can this happen?
- ctxt.reportMappingException("Can not find a deserializer for type %s", t);
+ ctxt.reportBadDefinition(t, "Cannot find a deserializer for type "+t);
}
_rootDeserializers.put(t, deser);
return deser;
@@ -1875,8 +1911,8 @@
// Nope: need to ask provider to resolve it
deser = ctxt.findRootValueDeserializer(JSON_NODE_TYPE);
if (deser == null) { // can this happen?
- ctxt.reportMappingException("Can not find a deserializer for type %s",
- JSON_NODE_TYPE);
+ ctxt.reportBadDefinition(JSON_NODE_TYPE,
+ "Cannot find a deserializer for type "+JSON_NODE_TYPE);
}
_rootDeserializers.put(JSON_NODE_TYPE, deser);
}
@@ -1890,7 +1926,7 @@
*/
protected JsonDeserializer<Object> _prefetchRootDeserializer(JavaType valueType)
{
- if (valueType == null || !_config.isEnabled(DeserializationFeature.EAGER_DESERIALIZER_FETCH)) {
+ if ((valueType == null) || !_config.isEnabled(DeserializationFeature.EAGER_DESERIALIZER_FETCH)) {
return null;
}
// already cached?
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java b/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java
index 3a6988b..5098612 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java
@@ -2,7 +2,9 @@
import java.io.*;
import java.text.*;
-import java.util.*;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.*;
@@ -222,6 +224,9 @@
* @since 2.5
*/
protected ObjectWriter _new(ObjectWriter base, SerializationConfig config) {
+ if (config == _config) {
+ return this;
+ }
return new ObjectWriter(base, config);
}
@@ -233,6 +238,9 @@
* @since 2.5
*/
protected ObjectWriter _new(GeneratorSettings genSettings, Prefetch prefetch) {
+ if ((_generatorSettings == genSettings) && (_prefetch == prefetch)) {
+ return this;
+ }
return new ObjectWriter(this, _config, genSettings, prefetch);
}
@@ -264,8 +272,7 @@
* with specified feature enabled.
*/
public ObjectWriter with(SerializationFeature feature) {
- SerializationConfig newConfig = _config.with(feature);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(feature));
}
/**
@@ -273,8 +280,7 @@
* with specified features enabled.
*/
public ObjectWriter with(SerializationFeature first, SerializationFeature... other) {
- SerializationConfig newConfig = _config.with(first, other);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(first, other));
}
/**
@@ -282,8 +288,7 @@
* with specified features enabled.
*/
public ObjectWriter withFeatures(SerializationFeature... features) {
- SerializationConfig newConfig = _config.withFeatures(features);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withFeatures(features));
}
/**
@@ -291,8 +296,7 @@
* with specified feature enabled.
*/
public ObjectWriter without(SerializationFeature feature) {
- SerializationConfig newConfig = _config.without(feature);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.without(feature));
}
/**
@@ -300,8 +304,7 @@
* with specified features enabled.
*/
public ObjectWriter without(SerializationFeature first, SerializationFeature... other) {
- SerializationConfig newConfig = _config.without(first, other);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.without(first, other));
}
/**
@@ -309,8 +312,7 @@
* with specified features enabled.
*/
public ObjectWriter withoutFeatures(SerializationFeature... features) {
- SerializationConfig newConfig = _config.withoutFeatures(features);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withoutFeatures(features));
}
/*
@@ -323,32 +325,28 @@
* @since 2.5
*/
public ObjectWriter with(JsonGenerator.Feature feature) {
- SerializationConfig newConfig = _config.with(feature);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(feature));
}
/**
* @since 2.5
*/
public ObjectWriter withFeatures(JsonGenerator.Feature... features) {
- SerializationConfig newConfig = _config.withFeatures(features);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withFeatures(features));
}
/**
* @since 2.5
*/
public ObjectWriter without(JsonGenerator.Feature feature) {
- SerializationConfig newConfig = _config.without(feature);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.without(feature));
}
/**
* @since 2.5
*/
public ObjectWriter withoutFeatures(JsonGenerator.Feature... features) {
- SerializationConfig newConfig = _config.withoutFeatures(features);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withoutFeatures(features));
}
/*
@@ -361,34 +359,30 @@
* @since 2.7
*/
public ObjectWriter with(FormatFeature feature) {
- SerializationConfig newConfig = _config.with(feature);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(feature));
}
/**
* @since 2.7
*/
public ObjectWriter withFeatures(FormatFeature... features) {
- SerializationConfig newConfig = _config.withFeatures(features);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withFeatures(features));
}
/**
* @since 2.7
*/
public ObjectWriter without(FormatFeature feature) {
- SerializationConfig newConfig = _config.without(feature);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.without(feature));
}
/**
* @since 2.7
*/
public ObjectWriter withoutFeatures(FormatFeature... features) {
- SerializationConfig newConfig = _config.withoutFeatures(features);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withoutFeatures(features));
}
-
+
/*
/**********************************************************
/* Life-cycle, fluent factories, type-related
@@ -405,10 +399,8 @@
*
* @since 2.5
*/
- public ObjectWriter forType(JavaType rootType)
- {
- Prefetch pf = _prefetch.forRootType(this, rootType);
- return (pf == _prefetch) ? this : _new(_generatorSettings, pf);
+ public ObjectWriter forType(JavaType rootType) {
+ return _new(_generatorSettings, _prefetch.forRootType(this, rootType));
}
/**
@@ -475,8 +467,7 @@
* rather construct and returns a newly configured instance.
*/
public ObjectWriter with(DateFormat df) {
- SerializationConfig newConfig = _config.with(df);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(df));
}
/**
@@ -492,8 +483,10 @@
* provider for resolving filter instances by id.
*/
public ObjectWriter with(FilterProvider filterProvider) {
- return (filterProvider == _config.getFilterProvider()) ? this
- : _new(this, _config.withFilters(filterProvider));
+ if (filterProvider == _config.getFilterProvider()) {
+ return this;
+ }
+ return _new(this, _config.withFilters(filterProvider));
}
/**
@@ -501,11 +494,7 @@
* printer (or, if null, will not do any pretty-printing)
*/
public ObjectWriter with(PrettyPrinter pp) {
- GeneratorSettings genSet = _generatorSettings.with(pp);
- if (genSet == _generatorSettings) {
- return this;
- }
- return _new(genSet, _prefetch);
+ return _new(_generatorSettings.with(pp), _prefetch);
}
/**
@@ -520,16 +509,14 @@
* and empty String ("") for "do NOT add root wrapper"
*/
public ObjectWriter withRootName(String rootName) {
- SerializationConfig newConfig = _config.withRootName(rootName);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withRootName(rootName));
}
/**
* @since 2.6
*/
public ObjectWriter withRootName(PropertyName rootName) {
- SerializationConfig newConfig = _config.withRootName(rootName);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withRootName(rootName));
}
/**
@@ -543,8 +530,7 @@
* @since 2.6
*/
public ObjectWriter withoutRootName() {
- SerializationConfig newConfig = _config.withRootName(PropertyName.NO_NAME);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withRootName(PropertyName.NO_NAME));
}
/**
@@ -555,12 +541,8 @@
* rather construct and returns a newly configured instance.
*/
public ObjectWriter with(FormatSchema schema) {
- GeneratorSettings genSet = _generatorSettings.with(schema);
- if (genSet == _generatorSettings) {
- return this;
- }
_verifySchemaType(schema);
- return _new(genSet, _prefetch);
+ return _new(_generatorSettings.with(schema), _prefetch);
}
/**
@@ -580,18 +562,15 @@
* rather construct and returns a newly configured instance.
*/
public ObjectWriter withView(Class<?> view) {
- SerializationConfig newConfig = _config.withView(view);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withView(view));
}
public ObjectWriter with(Locale l) {
- SerializationConfig newConfig = _config.with(l);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(l));
}
public ObjectWriter with(TimeZone tz) {
- SerializationConfig newConfig = _config.with(tz);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(tz));
}
/**
@@ -601,19 +580,14 @@
* @since 2.1
*/
public ObjectWriter with(Base64Variant b64variant) {
- SerializationConfig newConfig = _config.with(b64variant);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(b64variant));
}
/**
* @since 2.3
*/
public ObjectWriter with(CharacterEscapes escapes) {
- GeneratorSettings genSet = _generatorSettings.with(escapes);
- if (genSet == _generatorSettings) {
- return this;
- }
- return _new(genSet, _prefetch);
+ return _new(_generatorSettings.with(escapes), _prefetch);
}
/**
@@ -627,8 +601,7 @@
* @since 2.3
*/
public ObjectWriter with(ContextAttributes attrs) {
- SerializationConfig newConfig = _config.with(attrs);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.with(attrs));
}
/**
@@ -638,46 +611,35 @@
* @since 2.3
*/
public ObjectWriter withAttributes(Map<?,?> attrs) {
- SerializationConfig newConfig = _config.withAttributes(attrs);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withAttributes(attrs));
}
/**
* @since 2.3
*/
public ObjectWriter withAttribute(Object key, Object value) {
- SerializationConfig newConfig = _config.withAttribute(key, value);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withAttribute(key, value));
}
/**
* @since 2.3
*/
public ObjectWriter withoutAttribute(Object key) {
- SerializationConfig newConfig = _config.withoutAttribute(key);
- return (newConfig == _config) ? this : _new(this, newConfig);
+ return _new(this, _config.withoutAttribute(key));
}
/**
* @since 2.5
*/
public ObjectWriter withRootValueSeparator(String sep) {
- GeneratorSettings genSet = _generatorSettings.withRootValueSeparator(sep);
- if (genSet == _generatorSettings) {
- return this;
- }
- return _new(genSet, _prefetch);
+ return _new(_generatorSettings.withRootValueSeparator(sep), _prefetch);
}
/**
* @since 2.5
*/
public ObjectWriter withRootValueSeparator(SerializableString sep) {
- GeneratorSettings genSet = _generatorSettings.withRootValueSeparator(sep);
- if (genSet == _generatorSettings) {
- return this;
- }
- return _new(genSet, _prefetch);
+ return _new(_generatorSettings.withRootValueSeparator(sep), _prefetch);
}
/*
@@ -869,7 +831,7 @@
}
/**
- * @since 2.8.5
+ * @since 2.9
*/
@Deprecated
public boolean isEnabled(JsonParser.Feature f) {
@@ -877,7 +839,7 @@
}
/**
- * @since 2.8.5
+ * @since 2.9
*/
public boolean isEnabled(JsonGenerator.Feature f) {
return _generatorFactory.isEnabled(f);
@@ -943,7 +905,7 @@
gen.flush();
}
} catch (Exception e) {
- ClassUtil.closeOnFailAndThrowAsIAE(null, toClose, e);
+ ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
return;
}
toClose.close();
@@ -1029,14 +991,14 @@
SegmentedStringWriter sw = new SegmentedStringWriter(_generatorFactory._getBufferRecycler());
try {
_configAndWriteValue(_generatorFactory.createGenerator(sw), value);
- } catch (JsonProcessingException e) { // to support [JACKSON-758]
+ } catch (JsonProcessingException e) {
throw e;
} catch (IOException e) { // shouldn't really happen, but is declared as possibility so:
throw JsonMappingException.fromUnexpectedIOE(e);
}
return sw.getAndClear();
}
-
+
/**
* Method that can be used to serialize any Java value as
* a byte array. Functionally equivalent to calling
@@ -1137,7 +1099,7 @@
{
if (schema != null) {
if (!_generatorFactory.canUseSchema(schema)) {
- throw new IllegalArgumentException("Can not use FormatSchema of type "+schema.getClass().getName()
+ throw new IllegalArgumentException("Cannot use FormatSchema of type "+schema.getClass().getName()
+" for format "+_generatorFactory.getFormatName());
}
}
@@ -1157,7 +1119,7 @@
try {
_prefetch.serialize(gen, value, _serializerProvider());
} catch (Exception e) {
- ClassUtil.closeOnFailAndThrowAsIAE(gen, e);
+ ClassUtil.closeOnFailAndThrowAsIOE(gen, e);
return;
}
gen.close();
@@ -1177,7 +1139,7 @@
toClose = null;
tmpToClose.close();
} catch (Exception e) {
- ClassUtil.closeOnFailAndThrowAsIAE(gen, toClose, e);
+ ClassUtil.closeOnFailAndThrowAsIOE(gen, toClose, e);
return;
}
gen.close();
@@ -1276,11 +1238,13 @@
if (rootValueSeparator == null) {
return this;
}
- } else if (sep.equals(rootValueSeparator)) {
+ return new GeneratorSettings(prettyPrinter, schema, characterEscapes, null);
+ }
+ if (sep.equals(_rootValueSeparatorAsString())) {
return this;
}
return new GeneratorSettings(prettyPrinter, schema, characterEscapes,
- (sep == null) ? null : new SerializedString(sep));
+ new SerializedString(sep));
}
public GeneratorSettings withRootValueSeparator(SerializableString sep) {
@@ -1288,15 +1252,18 @@
if (rootValueSeparator == null) {
return this;
}
- } else {
- if (rootValueSeparator != null
- && sep.getValue().equals(rootValueSeparator.getValue())) {
- return this;
- }
+ return new GeneratorSettings(prettyPrinter, schema, characterEscapes, null);
+ }
+ if (sep.equals(rootValueSeparator)) {
+ return this;
}
return new GeneratorSettings(prettyPrinter, schema, characterEscapes, sep);
}
+ private final String _rootValueSeparatorAsString() {
+ return (rootValueSeparator == null) ? null : rootValueSeparator.getValue();
+ }
+
/**
* @since 2.6
*/
@@ -1355,7 +1322,7 @@
private final JsonSerializer<Object> valueSerializer;
/**
- * When dealing with polymorphic types, we can not pre-fetch
+ * When dealing with polymorphic types, we cannot pre-fetch
* serializer, but can pre-fetch {@link TypeSerializer}.
*/
private final TypeSerializer typeSerializer;
diff --git a/src/main/java/com/fasterxml/jackson/databind/PropertyMetadata.java b/src/main/java/com/fasterxml/jackson/databind/PropertyMetadata.java
index 52ec5b5..9b57186 100644
--- a/src/main/java/com/fasterxml/jackson/databind/PropertyMetadata.java
+++ b/src/main/java/com/fasterxml/jackson/databind/PropertyMetadata.java
@@ -1,9 +1,12 @@
package com.fasterxml.jackson.databind;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+
/**
* Simple container class used for storing "additional" metadata about
* properties. Carved out to reduce number of distinct properties that
- * actual property implementations and placeholders need to store;
+ * actual property implementations and place holders need to store;
* since instances are immutable, they can be freely shared.
*
* @since 2.3
@@ -13,12 +16,51 @@
{
private static final long serialVersionUID = -1;
- public final static PropertyMetadata STD_REQUIRED = new PropertyMetadata(Boolean.TRUE, null, null, null);
+ public final static PropertyMetadata STD_REQUIRED = new PropertyMetadata(Boolean.TRUE,
+ null, null, null, null, null, null);
- public final static PropertyMetadata STD_OPTIONAL = new PropertyMetadata(Boolean.FALSE, null, null, null);
+ public final static PropertyMetadata STD_OPTIONAL = new PropertyMetadata(Boolean.FALSE,
+ null, null, null, null, null, null);
- public final static PropertyMetadata STD_REQUIRED_OR_OPTIONAL = new PropertyMetadata(null, null, null, null);
-
+ public final static PropertyMetadata STD_REQUIRED_OR_OPTIONAL = new PropertyMetadata(null,
+ null, null, null, null, null, null);
+
+ /**
+ * Helper class used for containing information about expected merge
+ * information for this property, if merging is expected.
+ *
+ * @since 2.9
+ */
+ public final static class MergeInfo
+ // NOTE: need not be Serializable, not persisted
+ {
+ public final AnnotatedMember getter;
+
+ /**
+ * Flag that is set if the information came from global defaults,
+ * and not from explicit per-property annotations or per-type
+ * config overrides.
+ */
+ public final boolean fromDefaults;
+
+ protected MergeInfo(AnnotatedMember getter, boolean fromDefaults) {
+ this.getter = getter;
+ this.fromDefaults = fromDefaults;
+ }
+
+ public static MergeInfo createForDefaults(AnnotatedMember getter) {
+ return new MergeInfo(getter, true);
+ }
+
+ public static MergeInfo createForTypeOverride(AnnotatedMember getter) {
+ return new MergeInfo(getter, false);
+ }
+
+ public static MergeInfo createForPropertyOverride(AnnotatedMember getter) {
+ return new MergeInfo(getter, false);
+ }
+ }
+
/**
* Three states: required, not required and unknown; unknown represented
* as null.
@@ -38,38 +80,53 @@
protected final Integer _index;
/**
- * Optional default value, as String, for property; not used cor
+ * Optional default value, as String, for property; not used for
* any functionality by core databind, offered as metadata for
* extensions.
*/
protected final String _defaultValue;
-
+
+ /**
+ * Settings regarding merging, if property is determined to possibly
+ * be mergeable (possibly since global settings may be omitted for
+ * non-mergeable types).
+ *<p>
+ * NOTE: transient since it is assumed that this information is only
+ * relevant during initial setup and not needed after full initialization.
+ * May be changed if this proves necessary.
+ *
+ * @since 2.9
+ */
+ protected final transient MergeInfo _mergeInfo;
+
+ /**
+ * Settings regarding handling of incoming `null`s, both for value itself
+ * and, for structured types, content values (array/Collection elements,
+ * Map values).
+ *
+ * @since 2.9
+ */
+ protected Nulls _valueNulls, _contentNulls;
+
/*
/**********************************************************
/* Construction, configuration
/**********************************************************
*/
-
- @Deprecated // since 2.4
- protected PropertyMetadata(Boolean req, String desc) { this(req, desc, null, null); }
/**
- * @since 2.5
+ * @since 2.9
*/
- protected PropertyMetadata(Boolean req, String desc, Integer index, String def)
+ protected PropertyMetadata(Boolean req, String desc, Integer index, String def,
+ MergeInfo mergeInfo, Nulls valueNulls, Nulls contentNulls)
{
_required = req;
_description = desc;
_index = index;
_defaultValue = (def == null || def.isEmpty()) ? null : def;
- }
-
- /**
- * @since 2.4 Use variant that takes more arguments.
- */
- @Deprecated
- public static PropertyMetadata construct(boolean req, String desc) {
- return construct(req, desc, null, null);
+ _mergeInfo = mergeInfo;
+ _valueNulls = valueNulls;
+ _contentNulls = contentNulls;
}
/**
@@ -78,7 +135,8 @@
public static PropertyMetadata construct(Boolean req, String desc, Integer index,
String defaultValue) {
if ((desc != null) || (index != null) || (defaultValue != null)) {
- return new PropertyMetadata(req, desc, index, defaultValue);
+ return new PropertyMetadata(req, desc, index, defaultValue,
+ null, null, null);
}
if (req == null) {
return STD_REQUIRED_OR_OPTIONAL;
@@ -90,7 +148,8 @@
public static PropertyMetadata construct(boolean req, String desc, Integer index,
String defaultValue) {
if (desc != null || index != null || defaultValue != null) {
- return new PropertyMetadata(req, desc, index, defaultValue);
+ return new PropertyMetadata(req, desc, index, defaultValue,
+ null, null, null);
}
return req ? STD_REQUIRED : STD_OPTIONAL;
}
@@ -101,7 +160,9 @@
*/
protected Object readResolve()
{
- if (_description == null && _index == null && _defaultValue == null) {
+ if ((_description == null) && (_index == null) && (_defaultValue == null)
+ && (_mergeInfo == null)
+ && (_valueNulls == null) && (_contentNulls == null)) {
if (_required == null) {
return STD_REQUIRED_OR_OPTIONAL;
}
@@ -111,7 +172,25 @@
}
public PropertyMetadata withDescription(String desc) {
- return new PropertyMetadata(_required, desc, _index, _defaultValue);
+ return new PropertyMetadata(_required, desc, _index, _defaultValue,
+ _mergeInfo, _valueNulls, _contentNulls);
+ }
+
+ /**
+ * @since 2.9
+ */
+ public PropertyMetadata withMergeInfo(MergeInfo mergeInfo) {
+ return new PropertyMetadata(_required, _description, _index, _defaultValue,
+ mergeInfo, _valueNulls, _contentNulls);
+ }
+
+ /**
+ * @since 2.9
+ */
+ public PropertyMetadata withNulls(Nulls valueNulls,
+ Nulls contentNulls) {
+ return new PropertyMetadata(_required, _description, _index, _defaultValue,
+ _mergeInfo, valueNulls, contentNulls);
}
public PropertyMetadata withDefaultValue(String def) {
@@ -120,14 +199,16 @@
return this;
}
def = null;
- } else if (_defaultValue.equals(def)) {
+ } else if (def.equals(_defaultValue)) {
return this;
}
- return new PropertyMetadata(_required, _description, _index, def);
+ return new PropertyMetadata(_required, _description, _index, def,
+ _mergeInfo, _valueNulls, _contentNulls);
}
public PropertyMetadata withIndex(Integer index) {
- return new PropertyMetadata(_required, _description, index, _defaultValue);
+ return new PropertyMetadata(_required, _description, index, _defaultValue,
+ _mergeInfo, _valueNulls, _contentNulls);
}
public PropertyMetadata withRequired(Boolean b) {
@@ -135,14 +216,13 @@
if (_required == null) {
return this;
}
- } else {
- if (_required != null && _required.booleanValue() == b.booleanValue()) {
- return this;
- }
+ } else if (b.equals(_required)) {
+ return this;
}
- return new PropertyMetadata(b, _description, _index, _defaultValue);
+ return new PropertyMetadata(b, _description, _index, _defaultValue,
+ _mergeInfo, _valueNulls, _contentNulls);
}
-
+
/*
/**********************************************************
/* Accessors
@@ -157,21 +237,15 @@
public String getDefaultValue() { return _defaultValue; }
/**
- * @deprecated Since 2.6: typo in name, use {@link #hasDefaultValue()} instead.
- */
- @Deprecated
- public boolean hasDefuaultValue() { return hasDefaultValue(); }
-
- /**
* Accessor for determining whether property has declared "default value",
* which may be used by extension modules.
*
* @since 2.6
*/
public boolean hasDefaultValue() { return (_defaultValue != null); }
-
+
public boolean isRequired() { return (_required != null) && _required.booleanValue(); }
-
+
public Boolean getRequired() { return _required; }
/**
@@ -183,4 +257,19 @@
* @since 2.4
*/
public boolean hasIndex() { return _index != null; }
+
+ /**
+ * @since 2.9
+ */
+ public MergeInfo getMergeInfo() { return _mergeInfo; }
+
+ /**
+ * @since 2.9
+ */
+ public Nulls getValueNulls() { return _valueNulls; }
+
+ /**
+ * @since 2.9
+ */
+ public Nulls getContentNulls() { return _contentNulls; }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/PropertyName.java b/src/main/java/com/fasterxml/jackson/databind/PropertyName.java
index 10c24b1..5fcd044 100644
--- a/src/main/java/com/fasterxml/jackson/databind/PropertyName.java
+++ b/src/main/java/com/fasterxml/jackson/databind/PropertyName.java
@@ -4,6 +4,7 @@
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.core.util.InternCache;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Simple value class used for containing names of properties as defined
@@ -62,17 +63,23 @@
public PropertyName(String simpleName, String namespace)
{
- _simpleName = (simpleName == null) ? "" : simpleName;
+ _simpleName = ClassUtil.nonNullString(simpleName);
_namespace = namespace;
}
// To support JDK serialization, recovery of Singleton instance
protected Object readResolve() {
- if (_simpleName == null || _USE_DEFAULT.equals(_simpleName)) {
- return USE_DEFAULT;
- }
- if (_simpleName.equals(_NO_NAME) && _namespace == null) {
- return NO_NAME;
+ if (_namespace == null) {
+ if (_simpleName == null || _USE_DEFAULT.equals(_simpleName)) {
+ return USE_DEFAULT;
+ }
+ // 30-Oct-2016, tatu: I don't see how this could ever occur...
+ // or how to distinguish USE_DEFAULT/NO_NAME from serialized
+ /*
+ if (_simpleName.equals(_NO_NAME)) {
+ return NO_NAME;
+ }
+ */
}
return this;
}
@@ -182,10 +189,8 @@
* @since 2.3
*/
public boolean hasSimpleName(String str) {
- if (str == null) {
- return _simpleName == null;
- }
- return str.equals(_simpleName);
+ // _simpleName never null so...
+ return _simpleName.equals(str);
}
public boolean hasNamespace() {
diff --git a/src/main/java/com/fasterxml/jackson/databind/SequenceWriter.java b/src/main/java/com/fasterxml/jackson/databind/SequenceWriter.java
index c6d8bc3..3902cf6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/SequenceWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/SequenceWriter.java
@@ -172,7 +172,7 @@
if (_cfgCloseCloseable && (value instanceof Closeable)) {
return _writeCloseableValue(value, type);
}
- /* 15-Dec-2014, tatu: I wonder if this could be come problematic. It shouldn't
+ /* 15-Dec-2014, tatu: I wonder if this could become problematic. It shouldn't
* really, since trying to use differently paramterized types in a sequence
* is likely to run into other issues. But who knows; if it does become an
* issue, may need to implement alternative, JavaType-based map.
@@ -196,7 +196,7 @@
return this;
}
- // NOTE: redundant wrt variant that takes Iterable, but can not remove or even
+ // NOTE: redundant wrt variant that takes Iterable, but cannot remove or even
// deprecate due to backwards-compatibility needs
public <C extends Collection<?>> SequenceWriter writeAll(C container) throws IOException {
for (Object value : container) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java b/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java
index f57590f..eece8c6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java
@@ -1,7 +1,6 @@
package com.fasterxml.jackson.databind;
import java.text.DateFormat;
-import java.util.*;
import com.fasterxml.jackson.annotation.*;
@@ -10,14 +9,10 @@
import com.fasterxml.jackson.core.util.Instantiatable;
import com.fasterxml.jackson.databind.cfg.*;
-import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
import com.fasterxml.jackson.databind.introspect.SimpleMixInResolver;
-import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
-import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
-import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.RootNameLookup;
/**
@@ -41,10 +36,6 @@
// since 2.6
protected final static PrettyPrinter DEFAULT_PRETTY_PRINTER = new DefaultPrettyPrinter();
- // since 2.7
- // Default is "USE_DEFAULTS, USE_DEFAULTS"
- protected final static JsonInclude.Value DEFAULT_INCLUSION = JsonInclude.Value.empty();
-
/*
/**********************************************************
/* Configured helper objects
@@ -104,36 +95,21 @@
* @since 2.7
*/
protected final int _formatWriteFeaturesToChange;
-
- /*
- /**********************************************************
- /* Other configuration
- /**********************************************************
- */
-
- /**
- * Which Bean/Map properties are to be included in serialization?
- * Default settings is to include all regardless of value; can be
- * changed to only include non-null properties, or properties
- * with non-default values.
- *<p>
- * NOTE: type changed in 2.7, to include both value and content
- * inclusion options./
- */
- protected final JsonInclude.Value _serializationInclusion;
/*
/**********************************************************
- /* Life-cycle, constructors
+ /* Life-cycle, primary constructors for new instances
/**********************************************************
*/
/**
* Constructor used by ObjectMapper to create default configuration object instance.
+ *
+ * @since 2.9
*/
public SerializationConfig(BaseSettings base,
- SubtypeResolver str, SimpleMixInResolver mixins,
- RootNameLookup rootNames, ConfigOverrides configOverrides)
+ SubtypeResolver str, SimpleMixInResolver mixins, RootNameLookup rootNames,
+ ConfigOverrides configOverrides)
{
super(base, str, mixins, rootNames, configOverrides);
_serFeatures = collectFeatureDefaults(SerializationFeature.class);
@@ -143,25 +119,38 @@
_generatorFeaturesToChange = 0;
_formatWriteFeatures = 0;
_formatWriteFeaturesToChange = 0;
- _serializationInclusion = DEFAULT_INCLUSION;
}
/**
- * @deprecated Since 2.8, remove from 2.9 or later
+ * Copy-constructor used for making a copy to be used by new {@link ObjectMapper}.
+ *
+ * @since 2.9
*/
- @Deprecated
- public SerializationConfig(BaseSettings base,
- SubtypeResolver str, SimpleMixInResolver mixins,
- RootNameLookup rootNames)
+ protected SerializationConfig(SerializationConfig src,
+ SimpleMixInResolver mixins, RootNameLookup rootNames,
+ ConfigOverrides configOverrides)
{
- this(base, str, mixins, rootNames, null);
+ super(src, mixins, rootNames, configOverrides);
+ _serFeatures = src._serFeatures;
+ _filterProvider = src._filterProvider;
+ _defaultPrettyPrinter = src._defaultPrettyPrinter;
+ _generatorFeatures = src._generatorFeatures;
+ _generatorFeaturesToChange = src._generatorFeaturesToChange;
+ _formatWriteFeatures = src._formatWriteFeatures;
+ _formatWriteFeaturesToChange = src._formatWriteFeaturesToChange;
}
-
+
+ /*
+ /**********************************************************
+ /* Life-cycle, secondary constructors to support
+ /* "mutant factories", with single property changes
+ /**********************************************************
+ */
+
private SerializationConfig(SerializationConfig src, SubtypeResolver str)
{
super(src, str);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = src._generatorFeatures;
@@ -177,7 +166,6 @@
{
super(src, mapperFeatures);
_serFeatures = serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = generatorFeatures;
@@ -190,7 +178,6 @@
{
super(src, base);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = src._generatorFeatures;
@@ -203,7 +190,6 @@
{
super(src);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = filters;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = src._generatorFeatures;
@@ -216,20 +202,6 @@
{
super(src, view);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
- _filterProvider = src._filterProvider;
- _defaultPrettyPrinter = src._defaultPrettyPrinter;
- _generatorFeatures = src._generatorFeatures;
- _generatorFeaturesToChange = src._generatorFeaturesToChange;
- _formatWriteFeatures = src._formatWriteFeatures;
- _formatWriteFeaturesToChange = src._formatWriteFeaturesToChange;
- }
-
- private SerializationConfig(SerializationConfig src, JsonInclude.Value incl)
- {
- super(src);
- _serFeatures = src._serFeatures;
- _serializationInclusion = incl;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = src._generatorFeatures;
@@ -242,7 +214,6 @@
{
super(src, rootName);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = src._generatorFeatures;
@@ -258,7 +229,6 @@
{
super(src, attrs);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = src._generatorFeatures;
@@ -274,7 +244,6 @@
{
super(src, mixins);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = src._defaultPrettyPrinter;
_generatorFeatures = src._generatorFeatures;
@@ -290,7 +259,6 @@
{
super(src);
_serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
_filterProvider = src._filterProvider;
_defaultPrettyPrinter = defaultPP;
_generatorFeatures = src._generatorFeatures;
@@ -299,126 +267,23 @@
_formatWriteFeaturesToChange = src._formatWriteFeaturesToChange;
}
- /**
- * Copy-constructor used for making a copy to be used by new {@link ObjectMapper}.
- *
- * @since 2.8
- */
- protected SerializationConfig(SerializationConfig src, SimpleMixInResolver mixins,
- RootNameLookup rootNames, ConfigOverrides configOverrides)
- {
- super(src, mixins, rootNames, configOverrides);
- _serFeatures = src._serFeatures;
- _serializationInclusion = src._serializationInclusion;
- _filterProvider = src._filterProvider;
- _defaultPrettyPrinter = src._defaultPrettyPrinter;
- _generatorFeatures = src._generatorFeatures;
- _generatorFeaturesToChange = src._generatorFeaturesToChange;
- _formatWriteFeatures = src._formatWriteFeatures;
- _formatWriteFeaturesToChange = src._formatWriteFeaturesToChange;
- }
-
/*
/**********************************************************
- /* Life-cycle, factory methods from MapperConfig
+ /* Life-cycle, factory methods from MapperConfig(Base)
/**********************************************************
*/
- /**
- * Fluent factory method that will construct and return a new configuration
- * object instance with specified features enabled.
- */
- @Override
- public SerializationConfig with(MapperFeature... features)
- {
- int newMapperFlags = _mapperFeatures;
- for (MapperFeature f : features) {
- newMapperFlags |= f.getMask();
- }
- return (newMapperFlags == _mapperFeatures) ? this
- : new SerializationConfig(this, newMapperFlags, _serFeatures,
+ @Override // since 2.9
+ protected final SerializationConfig _withBase(BaseSettings newBase) {
+ return (_base == newBase) ? this : new SerializationConfig(this, newBase);
+ }
+
+ @Override // since 2.9
+ protected final SerializationConfig _withMapperFeatures(int mapperFeatures) {
+ return new SerializationConfig(this, mapperFeatures, _serFeatures,
_generatorFeatures, _generatorFeaturesToChange,
_formatWriteFeatures, _formatWriteFeaturesToChange);
}
-
- /**
- * Fluent factory method that will construct and return a new configuration
- * object instance with specified features disabled.
- */
- @Override
- public SerializationConfig without(MapperFeature... features)
- {
- int newMapperFlags = _mapperFeatures;
- for (MapperFeature f : features) {
- newMapperFlags &= ~f.getMask();
- }
- return (newMapperFlags == _mapperFeatures) ? this
- : new SerializationConfig(this, newMapperFlags, _serFeatures,
- _generatorFeatures, _generatorFeaturesToChange,
- _formatWriteFeatures, _formatWriteFeaturesToChange);
- }
-
- @Override
- public SerializationConfig with(MapperFeature feature, boolean state)
- {
- int newMapperFlags;
- if (state) {
- newMapperFlags = _mapperFeatures | feature.getMask();
- } else {
- newMapperFlags = _mapperFeatures & ~feature.getMask();
- }
- return (newMapperFlags == _mapperFeatures) ? this
- : new SerializationConfig(this, newMapperFlags, _serFeatures,
- _generatorFeatures, _generatorFeaturesToChange,
- _formatWriteFeatures, _formatWriteFeaturesToChange);
- }
-
- @Override
- public SerializationConfig with(AnnotationIntrospector ai) {
- return _withBase(_base.withAnnotationIntrospector(ai));
- }
-
- @Override
- public SerializationConfig withAppendedAnnotationIntrospector(AnnotationIntrospector ai) {
- return _withBase(_base.withAppendedAnnotationIntrospector(ai));
- }
-
- @Override
- public SerializationConfig withInsertedAnnotationIntrospector(AnnotationIntrospector ai) {
- return _withBase(_base.withInsertedAnnotationIntrospector(ai));
- }
-
- @Override
- public SerializationConfig with(ClassIntrospector ci) {
- return _withBase(_base.withClassIntrospector(ci));
- }
-
- /**
- * In addition to constructing instance with specified date format,
- * will enable or disable <code>SerializationFeature.WRITE_DATES_AS_TIMESTAMPS</code>
- * (enable if format set as null; disable if non-null)
- */
- @Override
- public SerializationConfig with(DateFormat df) {
- SerializationConfig cfg = new SerializationConfig(this, _base.withDateFormat(df));
- // Also need to toggle this feature based on existence of date format:
- if (df == null) {
- cfg = cfg.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
- } else {
- cfg = cfg.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
- }
- return cfg;
- }
-
- @Override
- public SerializationConfig with(HandlerInstantiator hi) {
- return _withBase(_base.withHandlerInstantiator(hi));
- }
-
- @Override
- public SerializationConfig with(PropertyNamingStrategy pns) {
- return _withBase(_base.withPropertyNamingStrategy(pns));
- }
@Override
public SerializationConfig withRootName(PropertyName rootName) {
@@ -438,52 +303,34 @@
}
@Override
- public SerializationConfig with(TypeFactory tf) {
- return _withBase(_base.withTypeFactory(tf));
- }
-
- @Override
- public SerializationConfig with(TypeResolverBuilder<?> trb) {
- return _withBase(_base.withTypeResolverBuilder(trb));
- }
-
- @Override
public SerializationConfig withView(Class<?> view) {
return (_view == view) ? this : new SerializationConfig(this, view);
}
-
- @Override
- public SerializationConfig with(VisibilityChecker<?> vc) {
- return _withBase(_base.withVisibilityChecker(vc));
- }
-
- @Override
- public SerializationConfig withVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility) {
- return _withBase(_base.withVisibility(forMethod, visibility));
- }
-
- @Override
- public SerializationConfig with(Locale l) {
- return _withBase(_base.with(l));
- }
-
- @Override
- public SerializationConfig with(TimeZone tz) {
- return _withBase(_base.with(tz));
- }
-
- @Override
- public SerializationConfig with(Base64Variant base64) {
- return _withBase(_base.with(base64));
- }
-
+
@Override
public SerializationConfig with(ContextAttributes attrs) {
return (attrs == _attributes) ? this : new SerializationConfig(this, attrs);
}
- private final SerializationConfig _withBase(BaseSettings newBase) {
- return (_base == newBase) ? this : new SerializationConfig(this, newBase);
+ /*
+ /**********************************************************
+ /* Factory method overrides
+ /**********************************************************
+ */
+
+ /**
+ * In addition to constructing instance with specified date format,
+ * will enable or disable <code>SerializationFeature.WRITE_DATES_AS_TIMESTAMPS</code>
+ * (enable if format set as null; disable if non-null)
+ */
+ @Override
+ public SerializationConfig with(DateFormat df) {
+ SerializationConfig cfg = super.with(df);
+ // Also need to toggle this feature based on existence of date format:
+ if (df == null) {
+ return cfg.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ }
+ return cfg.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
/*
@@ -751,24 +598,20 @@
}
/**
- * @deprecated Since 2.7 use {@link #withPropertyInclusion} instead
+ * Mutant factory method for constructing a new instance with different
+ * default inclusion criteria configuration.
+ *
+ * @since 2.7
+ *
+ * @deprecated Since 2.9; not needed any more
*/
@Deprecated
- public SerializationConfig withSerializationInclusion(JsonInclude.Include incl) {
- return withPropertyInclusion(DEFAULT_INCLUSION.withValueInclusion(incl));
+ public SerializationConfig withPropertyInclusion(JsonInclude.Value incl) {
+ _configOverrides.setDefaultInclusion(incl);
+ return this;
}
/**
- * @since 2.7
- */
- public SerializationConfig withPropertyInclusion(JsonInclude.Value incl) {
- if (_serializationInclusion.equals(incl)) {
- return this;
- }
- return new SerializationConfig(this, incl);
- }
-
- /**
* @since 2.6
*/
public SerializationConfig withDefaultPrettyPrinter(PrettyPrinter pp) {
@@ -834,85 +677,20 @@
/*
/**********************************************************
- /* MapperConfig implementation/overrides: introspection
- /**********************************************************
- */
-
- @Override
- public AnnotationIntrospector getAnnotationIntrospector()
- {
- if (isEnabled(MapperFeature.USE_ANNOTATIONS)) {
- return super.getAnnotationIntrospector();
- }
- return AnnotationIntrospector.nopInstance();
- }
-
- /**
- * Accessor for getting bean description that only contains class
- * annotations: useful if no getter/setter/creator information is needed.
- */
- @Override
- public BeanDescription introspectClassAnnotations(JavaType type) {
- return getClassIntrospector().forClassAnnotations(this, type, this);
- }
-
- /**
- * Accessor for getting bean description that only contains immediate class
- * annotations: ones from the class, and its direct mix-in, if any, but
- * not from super types.
- */
- @Override
- public BeanDescription introspectDirectClassAnnotations(JavaType type) {
- return getClassIntrospector().forDirectClassAnnotations(this, type, this);
- }
-
- /*
- /**********************************************************
/* Configuration: default settings with per-type overrides
/**********************************************************
*/
-
+
/**
* @deprecated Since 2.7 use {@link #getDefaultPropertyInclusion} instead
*/
@Deprecated
public JsonInclude.Include getSerializationInclusion()
{
- JsonInclude.Include incl = _serializationInclusion.getValueInclusion();
+ JsonInclude.Include incl = getDefaultPropertyInclusion().getValueInclusion();
return (incl == JsonInclude.Include.USE_DEFAULTS) ? JsonInclude.Include.ALWAYS : incl;
}
- @Override
- public JsonInclude.Value getDefaultPropertyInclusion() {
- return _serializationInclusion;
- }
-
- @Override
- public JsonInclude.Value getDefaultPropertyInclusion(Class<?> baseType) {
- ConfigOverride overrides = findConfigOverride(baseType);
- if (overrides != null) {
- JsonInclude.Value v = overrides.getInclude();
- if (v != null) {
- return v;
- }
- }
- return _serializationInclusion;
- }
-
- @Override
- public JsonInclude.Value getDefaultPropertyInclusion(Class<?> baseType,
- JsonInclude.Value defaultIncl)
- {
- ConfigOverride overrides = findConfigOverride(baseType);
- if (overrides != null) {
- JsonInclude.Value v = overrides.getInclude();
- if (v != null) {
- return v;
- }
- }
- return defaultIncl;
- }
-
/*
/**********************************************************
/* Configuration: other
@@ -970,7 +748,7 @@
public FilterProvider getFilterProvider() {
return _filterProvider;
}
-
+
/**
* Accessor for configured blueprint "default" {@link PrettyPrinter} to
* use, if default pretty-printing is enabled.
@@ -999,15 +777,4 @@
public <T extends BeanDescription> T introspect(JavaType type) {
return (T) getClassIntrospector().forSerialization(this, type, this);
}
-
- /*
- /**********************************************************
- /* Debug support
- /**********************************************************
- */
-
- @Override
- public String toString() {
- return "[SerializationConfig: flags=0x"+Integer.toHexString(_serFeatures)+"]";
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java
index e8d1cb3..1b46dc0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java
@@ -275,8 +275,17 @@
* Feature that determines whether Map entries with null values are
* to be serialized (true) or not (false).
*<p>
+ * NOTE: unlike other {@link SerializationFeature}s, this feature <b>cannot</b> be
+ * dynamically changed on per-call basis, because its effect is considered during
+ * construction of serializers and property handlers.
+ *<p>
* Feature is enabled by default.
+ *
+ * @deprecated Since 2.9 there are better mechanism for specifying filtering; specifically
+ * using {@link com.fasterxml.jackson.annotation.JsonInclude} or configuration overrides
+ * (see {@link ObjectMapper#configOverride(Class)}}).
*/
+ @Deprecated // since 2.9
WRITE_NULL_MAP_VALUES(true),
/**
@@ -288,7 +297,7 @@
* Note that this does not change behavior of {@link java.util.Map}s, or
* "Collection-like" types.
*<p>
- * NOTE: unlike other {@link SerializationFeature}s, this feature <b>can not</b> be
+ * NOTE: unlike other {@link SerializationFeature}s, this feature <b>cannot</b> be
* dynamically changed on per-call basis, because its effect is considered during
* construction of serializers and property handlers.
*<p>
diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java
index bce67fa..ac24e11 100644
--- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java
+++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java
@@ -12,6 +12,8 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.cfg.ContextAttributes;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -211,9 +213,6 @@
protected SerializerProvider(SerializerProvider src,
SerializationConfig config, SerializerFactory f)
{
- if (config == null) {
- throw new NullPointerException();
- }
_serializerFactory = f;
_config = config;
@@ -273,7 +272,7 @@
public void setDefaultKeySerializer(JsonSerializer<Object> ks)
{
if (ks == null) {
- throw new IllegalArgumentException("Can not pass null JsonSerializer");
+ throw new IllegalArgumentException("Cannot pass null JsonSerializer");
}
_keySerializer = ks;
}
@@ -290,7 +289,7 @@
public void setNullValueSerializer(JsonSerializer<Object> nvs)
{
if (nvs == null) {
- throw new IllegalArgumentException("Can not pass null JsonSerializer");
+ throw new IllegalArgumentException("Cannot pass null JsonSerializer");
}
_nullValueSerializer = nvs;
}
@@ -307,7 +306,7 @@
public void setNullKeySerializer(JsonSerializer<Object> nks)
{
if (nks == null) {
- throw new IllegalArgumentException("Can not pass null JsonSerializer");
+ throw new IllegalArgumentException("Cannot pass null JsonSerializer");
}
_nullKeySerializer = nks;
}
@@ -445,6 +444,17 @@
return _config.getFilterProvider();
}
+ /**
+ *<p>
+ * NOTE: current implementation simply returns `null` as generator is not yet
+ * assigned to this provider.
+ *
+ * @since 2.8
+ */
+ public JsonGenerator getGenerator() {
+ return null;
+ }
+
/*
/**********************************************************
/* Access to Object Id aspects
@@ -519,6 +529,8 @@
* full generics-aware type instead of raw class.
* This is necessary for accurate handling of external type information,
* to handle polymorphic types.
+ *<p>
+ * Note: this call will also contextualize serializer before returning it.
*
* @param property When creating secondary serializers, property for which
* serializer is needed: annotations of the property (or bean that contains it)
@@ -528,6 +540,9 @@
public JsonSerializer<Object> findValueSerializer(JavaType valueType, BeanProperty property)
throws JsonMappingException
{
+ if (valueType == null) {
+ reportMappingProblem("Null passed for `valueType` of `findValueSerializer()`");
+ }
// (see comments from above method)
JsonSerializer<Object> ser = _knownSerializers.untypedValueSerializer(valueType);
if (ser == null) {
@@ -822,7 +837,7 @@
/**
* Method called to find a serializer to use for null values for given
* declared type. Note that type is completely based on declared type,
- * since nulls in Java have no type and thus runtime type can not be
+ * since nulls in Java have no type and thus runtime type cannot be
* determined.
*
* @since 2.0
@@ -852,7 +867,7 @@
/**
* Method called to get the serializer to use if provider
- * can not determine an actual type-specific serializer
+ * cannot determine an actual type-specific serializer
* to use; typically when none of {@link SerializerFactory}
* instances are able to construct a serializer.
*<p>
@@ -913,6 +928,29 @@
Object serDef)
throws JsonMappingException;
+ /**
+ * Method that can be called to construct and configure {@link JsonInclude}
+ * filter instance,
+ * given a {@link Class} to instantiate (with default constructor, by default).
+ *
+ * @param forProperty (optional) If filter is created for a property, that property;
+ * `null` if filter created via defaulting, global or per-type.
+ *
+ * @since 2.9
+ */
+ public abstract Object includeFilterInstance(BeanPropertyDefinition forProperty,
+ Class<?> filterClass)
+ throws JsonMappingException;
+
+ /**
+ * Follow-up method that may be called after calling {@link #includeFilterInstance},
+ * to check handling of `null` values by the filter.
+ *
+ * @since 2.9
+ */
+ public abstract boolean includeFilterSuppressNulls(Object filter)
+ throws JsonMappingException;
+
/*
/**********************************************************
/* Support for contextualization
@@ -1097,34 +1135,6 @@
*/
/**
- * Factory method for constructing a {@link JsonMappingException};
- * usually only indirectly used by calling
- * {@link #reportMappingProblem(String, Object...)}.
- *
- * @since 2.6
- */
- public JsonMappingException mappingException(String message, Object... args) {
- if (args != null && args.length > 0) {
- message = String.format(message, args);
- }
- return JsonMappingException.from(getGenerator(), message);
- }
-
- /**
- * Factory method for constructing a {@link JsonMappingException};
- * usually only indirectly used by calling
- * {@link #reportMappingProblem(Throwable, String, Object...)}
- *
- * @since 2.8
- */
- protected JsonMappingException mappingException(Throwable t, String message, Object... args) {
- if (args != null && args.length > 0) {
- message = String.format(message, args);
- }
- return JsonMappingException.from(getGenerator(), message, t);
- }
-
- /**
* Helper method called to indicate problem; default behavior is to construct and
* throw a {@link JsonMappingException}, but in future may collect more than one
* and only throw after certain number, or at the end of serialization.
@@ -1136,17 +1146,6 @@
}
/**
- * Helper method called to indicate problem; default behavior is to construct and
- * throw a {@link JsonMappingException}, but in future may collect more than one
- * and only throw after certain number, or at the end of serialization.
- *
- * @since 2.8
- */
- public void reportMappingProblem(Throwable t, String message, Object... args) throws JsonMappingException {
- throw mappingException(t, message, args);
- }
-
- /**
* Helper method called to indicate problem in POJO (serialization) definitions or settings
* regarding specific Java type, unrelated to actual JSON content to map.
* Default behavior is to construct and throw a {@link JsonMappingException}.
@@ -1154,13 +1153,14 @@
* @since 2.9
*/
public <T> T reportBadTypeDefinition(BeanDescription bean,
- String message, Object... args) throws JsonMappingException {
- if (args != null && args.length > 0) {
- message = String.format(message, args);
+ String msg, Object... msgArgs) throws JsonMappingException {
+ String beanDesc = "N/A";
+ if (bean != null) {
+ beanDesc = ClassUtil.nameOf(bean.getBeanClass());
}
- String beanDesc = (bean == null) ? "N/A" : _desc(bean.getType().getGenericSignature());
- throw mappingException("Invalid type definition for type %s: %s",
- beanDesc, message);
+ msg = String.format("Invalid type definition for type %s: %s",
+ beanDesc, _format(msg, msgArgs));
+ throw InvalidDefinitionException.from(getGenerator(), msg, bean, null);
}
/**
@@ -1171,21 +1171,98 @@
* @since 2.9
*/
public <T> T reportBadPropertyDefinition(BeanDescription bean, BeanPropertyDefinition prop,
- String message, Object... args) throws JsonMappingException {
- if (args != null && args.length > 0) {
- message = String.format(message, args);
+ String message, Object... msgArgs) throws JsonMappingException {
+ message = _format(message, msgArgs);
+ String propName = "N/A";
+ if (prop != null) {
+ propName = _quotedString(prop.getName());
}
- String propName = (prop == null) ? "N/A" : _quotedString(prop.getName());
- String beanDesc = (bean == null) ? "N/A" : _desc(bean.getType().getGenericSignature());
- throw mappingException("Invalid definition for property %s (of type %s): %s",
+ String beanDesc = "N/A";
+ if (bean != null) {
+ beanDesc = ClassUtil.nameOf(bean.getBeanClass());
+ }
+ message = String.format("Invalid definition for property %s (of type %s): %s",
propName, beanDesc, message);
+ throw InvalidDefinitionException.from(getGenerator(), message, bean, prop);
+ }
+
+ @Override
+ public <T> T reportBadDefinition(JavaType type, String msg) throws JsonMappingException {
+ throw InvalidDefinitionException.from(getGenerator(), msg, type);
}
/**
+ * @since 2.9
+ */
+ public <T> T reportBadDefinition(JavaType type, String msg, Throwable cause)
+ throws JsonMappingException {
+ InvalidDefinitionException e = InvalidDefinitionException.from(getGenerator(), msg, type);
+ e.initCause(cause);
+ throw e;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public <T> T reportBadDefinition(Class<?> raw, String msg, Throwable cause)
+ throws JsonMappingException {
+ InvalidDefinitionException e = InvalidDefinitionException.from(getGenerator(), msg, constructType(raw));
+ e.initCause(cause);
+ throw e;
+ }
+
+ /**
+ * Helper method called to indicate problem; default behavior is to construct and
+ * throw a {@link JsonMappingException}, but in future may collect more than one
+ * and only throw after certain number, or at the end of serialization.
+ *
* @since 2.8
*/
- public JsonGenerator getGenerator() {
- return null;
+ public void reportMappingProblem(Throwable t, String message, Object... msgArgs) throws JsonMappingException {
+ message = _format(message, msgArgs);
+ throw JsonMappingException.from(getGenerator(), message, t);
+ }
+
+ @Override
+ public JsonMappingException invalidTypeIdException(JavaType baseType, String typeId,
+ String extraDesc) {
+ String msg = String.format("Could not resolve type id '%s' as a subtype of %s",
+ typeId, baseType);
+ return InvalidTypeIdException.from(null, _colonConcat(msg, extraDesc), baseType, typeId);
+ }
+
+ /*
+ /********************************************************
+ /* Error reporting, deprecated methods
+ /********************************************************
+ */
+
+ /**
+ * Factory method for constructing a {@link JsonMappingException};
+ * usually only indirectly used by calling
+ * {@link #reportMappingProblem(String, Object...)}.
+ *
+ * @since 2.6
+ *
+ * @deprecated Since 2.9
+ */
+ @Deprecated // since 2.9
+ public JsonMappingException mappingException(String message, Object... msgArgs) {
+ return JsonMappingException.from(getGenerator(), _format(message, msgArgs));
+ }
+
+ /**
+ * Factory method for constructing a {@link JsonMappingException};
+ * usually only indirectly used by calling
+ * {@link #reportMappingProblem(Throwable, String, Object...)}
+ *
+ * @since 2.8
+ *
+ * @deprecated Since 2.9
+ */
+ @Deprecated // since 2.9
+ protected JsonMappingException mappingException(Throwable t, String message, Object... msgArgs) {
+ return JsonMappingException.from(getGenerator(), _format(message, msgArgs), t);
}
/*
@@ -1204,14 +1281,15 @@
return;
}
}
- reportMappingProblem("Incompatible types: declared root type (%s) vs %s",
- rootType, value.getClass().getName());
+ reportBadDefinition(rootType, String.format(
+ "Incompatible types: declared root type (%s) vs %s",
+ rootType, ClassUtil.classNameOf(value)));
}
/**
* Method that will try to find a serializer, either from cache
* or by constructing one; but will not return an "unknown" serializer
- * if this can not be done but rather returns null.
+ * if this cannot be done but rather returns null.
*
* @return Serializer if one can be found, null if not.
*/
@@ -1257,11 +1335,10 @@
try {
ser = _createUntypedSerializer(fullType);
} catch (IllegalArgumentException iae) {
- /* We better only expose checked exceptions, since those
- * are what caller is expected to handle
- */
- reportMappingProblem(iae, iae.getMessage());
- return null; // never gets here
+ // We better only expose checked exceptions, since those
+ // are what caller is expected to handle
+ ser = null; // doesn't matter but compiler whines otherwise
+ reportMappingProblem(iae, ClassUtil.exceptionMessage(iae));
}
if (ser != null) {
@@ -1278,11 +1355,10 @@
try {
ser = _createUntypedSerializer(type);
} catch (IllegalArgumentException iae) {
- /* We better only expose checked exceptions, since those
- * are what caller is expected to handle
- */
- reportMappingProblem(iae, iae.getMessage());
- return null; // never gets here
+ // We better only expose checked exceptions, since those
+ // are what caller is expected to handle
+ ser = null;
+ reportMappingProblem(iae, ClassUtil.exceptionMessage(iae));
}
if (ser != null) {
@@ -1303,6 +1379,10 @@
* since there's one instance per serialization).
* Perhaps not-yet-resolved instance might be exposed too early to callers.
*/
+ // 13-Apr-2018, tatu: Problem does NOT occur any more with late 2.8.x and 2.9.x
+ // versions, likely due to concurrency fixes for `AnnotatedClass` introspection.
+ // This sync block could probably be removed; but to minimize any risk of
+ // regression sync block will only be removed from 3.0.
synchronized (_serializerCache) {
// 17-Feb-2013, tatu: Used to call deprecated method (that passed property)
return (JsonSerializer<Object>)_serializerFactory.createSerializer(this, type);
@@ -1340,26 +1420,12 @@
/**********************************************************
*/
- protected String _desc(Object value) {
- if (value == null) {
- return "N/A";
- }
- return "'"+value+"'";
- }
-
- protected String _quotedString(Object value) {
- if (value == null) {
- return "N/A";
- }
- return String.valueOf(value);
- }
-
protected final DateFormat _dateFormat()
{
if (_dateFormat != null) {
return _dateFormat;
}
- /* At this point, all timezone configuration should have occured, with respect
+ /* At this point, all timezone configuration should have occurred, with respect
* to default dateformat configuration. But we still better clone
* an instance as formatters are stateful, not thread-safe.
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java b/src/main/java/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java
index 3bab933..2c6841d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java
@@ -31,52 +31,66 @@
@com.fasterxml.jackson.annotation.JacksonAnnotation
public @interface JsonPOJOBuilder
{
- /**
- * Property to use for re-defining which zero-argument method
- * is considered the actual "build-method": method called after
- * all data has been bound, and the actual instance needs to
- * be instantiated.
- *<p>
- * Default value is "build".
- */
- public String buildMethodName() default "build";
+ /**
+ * @since 2.9
+ */
+ public final static String DEFAULT_BUILD_METHOD = "build";
- /**
- * Property used for (re)defining name prefix to use for
- * auto-detecting "with-methods": methods that are similar to
- * "set-methods" (in that they take an argument), but that
- * may also return the new builder instance to use
- * (which may be 'this', or a new modified builder instance).
- * Note that in addition to this prefix, it is also possible
- * to use {@link com.fasterxml.jackson.annotation.JsonProperty}
- * annotation to indicate "with-methods" (as well as
- * {@link com.fasterxml.jackson.annotation.JsonSetter}).
- *<p>
- * Default value is "with", so that method named "withValue()"
- * would be used for binding JSON property "value" (using type
- * indicated by the argument; or one defined with annotations.
- */
- public String withPrefix() default "with";
+ /**
+ * @since 2.9
+ */
+ public final static String DEFAULT_WITH_PREFIX = "with";
+
+ /**
+ * Property to use for re-defining which zero-argument method
+ * is considered the actual "build-method": method called after
+ * all data has been bound, and the actual instance needs to
+ * be instantiated.
+ *<p>
+ * Default value is "build".
+ */
+ public String buildMethodName() default DEFAULT_BUILD_METHOD;
+
+ /**
+ * Property used for (re)defining name prefix to use for
+ * auto-detecting "with-methods": methods that are similar to
+ * "set-methods" (in that they take an argument), but that
+ * may also return the new builder instance to use
+ * (which may be 'this', or a new modified builder instance).
+ * Note that in addition to this prefix, it is also possible
+ * to use {@link com.fasterxml.jackson.annotation.JsonProperty}
+ * annotation to indicate "with-methods" (as well as
+ * {@link com.fasterxml.jackson.annotation.JsonSetter}).
+ *<p>
+ * Default value is "with", so that method named "withValue()"
+ * would be used for binding JSON property "value" (using type
+ * indicated by the argument; or one defined with annotations.
+ */
+ public String withPrefix() default DEFAULT_WITH_PREFIX;
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
-
- /**
- * Simple value container for containing values read from
- * {@link JsonPOJOBuilder} annotation instance.
- */
- public class Value
- {
- public final String buildMethodName;
- public final String withPrefix;
- public Value(JsonPOJOBuilder ann)
- {
- buildMethodName = ann.buildMethodName();
- withPrefix = ann.withPrefix();
- }
- }
+ /**
+ * Simple value container for containing values read from
+ * {@link JsonPOJOBuilder} annotation instance.
+ */
+ public class Value
+ {
+ public final String buildMethodName;
+ public final String withPrefix;
+
+ public Value(JsonPOJOBuilder ann) {
+ this(ann.buildMethodName(), ann.withPrefix());
+ }
+
+ public Value(String buildMethodName, String withPrefix)
+ {
+ this.buildMethodName = buildMethodName;
+ this.withPrefix = withPrefix;
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/annotation/JsonSerialize.java b/src/main/java/com/fasterxml/jackson/databind/annotation/JsonSerialize.java
index e48b578..19767fe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/annotation/JsonSerialize.java
+++ b/src/main/java/com/fasterxml/jackson/databind/annotation/JsonSerialize.java
@@ -130,7 +130,7 @@
/**
* Which helper object is to be used to convert type into something
* that Jackson knows how to serialize; either because base type
- * can not be serialized easily, or just to alter serialization.
+ * cannot be serialized easily, or just to alter serialization.
*
* @since 2.2
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/annotation/NoClass.java b/src/main/java/com/fasterxml/jackson/databind/annotation/NoClass.java
index 86055b9..ff8ebae 100644
--- a/src/main/java/com/fasterxml/jackson/databind/annotation/NoClass.java
+++ b/src/main/java/com/fasterxml/jackson/databind/annotation/NoClass.java
@@ -2,7 +2,7 @@
/**
* Marker class used with annotations to indicate "no class". This is
- * a silly but necessary work-around -- annotations can not take nulls
+ * a silly but necessary work-around -- annotations cannot take nulls
* as either default or explicit values. Hence for class values we must
* explicitly use a bogus placeholder to denote equivalent of
* "no class" (for which 'null' is usually the natural choice).
diff --git a/src/main/java/com/fasterxml/jackson/databind/annotation/package-info.java b/src/main/java/com/fasterxml/jackson/databind/annotation/package-info.java
index 3a73fd8..3025002 100644
--- a/src/main/java/com/fasterxml/jackson/databind/annotation/package-info.java
+++ b/src/main/java/com/fasterxml/jackson/databind/annotation/package-info.java
@@ -1,7 +1,7 @@
/**
* Annotations that directly depend on classes in databinding bundle
- * (not just Jackson core) and can not be included
- * in Jackson core annotations package (because it can not have any
+ * (not just Jackson core) and cannot be included
+ * in Jackson core annotations package (because it cannot have any
* external dependencies).
*/
package com.fasterxml.jackson.databind.annotation;
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 3421e87..e289ab6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
@@ -4,13 +4,10 @@
import java.util.Locale;
import java.util.TimeZone;
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.Base64Variant;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
-import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.StdDateFormat;
@@ -55,17 +52,6 @@
protected final AnnotationIntrospector _annotationIntrospector;
/**
- * Object used for determining whether specific property elements
- * (method, constructors, fields) can be auto-detected based on
- * their visibility (access modifiers). Can be changed to allow
- * different minimum visibility levels for auto-detection. Note
- * that this is the global handler; individual types (classes)
- * can further override active checker used (using
- * {@link JsonAutoDetect} annotation)
- */
- protected final VisibilityChecker<?> _visibilityChecker;
-
- /**
* Custom property naming strategy in use, if any.
*/
protected final PropertyNamingStrategy _propertyNamingStrategy;
@@ -145,13 +131,12 @@
*/
public BaseSettings(ClassIntrospector ci, AnnotationIntrospector ai,
- VisibilityChecker<?> vc, PropertyNamingStrategy pns, TypeFactory tf,
+ PropertyNamingStrategy pns, TypeFactory tf,
TypeResolverBuilder<?> typer, DateFormat dateFormat, HandlerInstantiator hi,
Locale locale, TimeZone tz, Base64Variant defaultBase64)
{
_classIntrospector = ci;
_annotationIntrospector = ai;
- _visibilityChecker = vc;
_propertyNamingStrategy = pns;
_typeFactory = tf;
_typeResolverBuilder = typer;
@@ -162,6 +147,26 @@
_defaultBase64 = defaultBase64;
}
+ /**
+ * Turns out we are not necessarily 100% stateless, alas, since {@link ClassIntrospector}
+ * typically has a cache. So this method is needed for deep copy() of Mapper.
+ *
+ * @since 2.9.6
+ */
+ public BaseSettings copy() {
+ return new BaseSettings(_classIntrospector.copy(),
+ _annotationIntrospector,
+ _propertyNamingStrategy,
+ _typeFactory,
+ _typeResolverBuilder,
+ _dateFormat,
+ _handlerInstantiator,
+ _locale,
+ _timeZone,
+ _defaultBase64);
+
+ }
+
/*
/**********************************************************
/* Factory methods
@@ -172,7 +177,7 @@
if (_classIntrospector == ci) {
return this;
}
- return new BaseSettings(ci, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ return new BaseSettings(ci, _annotationIntrospector, _propertyNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64);
}
@@ -181,7 +186,7 @@
if (_annotationIntrospector == ai) {
return this;
}
- return new BaseSettings(_classIntrospector, ai, _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ return new BaseSettings(_classIntrospector, ai, _propertyNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64);
}
@@ -193,16 +198,8 @@
public BaseSettings withAppendedAnnotationIntrospector(AnnotationIntrospector ai) {
return withAnnotationIntrospector(AnnotationIntrospectorPair.create(_annotationIntrospector, ai));
}
-
- public BaseSettings withVisibilityChecker(VisibilityChecker<?> vc) {
- if (_visibilityChecker == vc) {
- return this;
- }
- return new BaseSettings(_classIntrospector, _annotationIntrospector, vc, _propertyNamingStrategy, _typeFactory,
- _typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
- _timeZone, _defaultBase64);
- }
+ /*
public BaseSettings withVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility) {
return new BaseSettings(_classIntrospector, _annotationIntrospector,
_visibilityChecker.withVisibility(forMethod, visibility),
@@ -210,12 +207,13 @@
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64);
}
+ */
public BaseSettings withPropertyNamingStrategy(PropertyNamingStrategy pns) {
if (_propertyNamingStrategy == pns) {
return this;
}
- return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, pns, _typeFactory,
+ return new BaseSettings(_classIntrospector, _annotationIntrospector, pns, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64);
}
@@ -224,7 +222,7 @@
if (_typeFactory == tf) {
return this;
}
- return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, tf,
+ return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, tf,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64);
}
@@ -233,7 +231,7 @@
if (_typeResolverBuilder == typer) {
return this;
}
- return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _typeFactory,
typer, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64);
}
@@ -247,7 +245,7 @@
if ((df != null) && hasExplicitTimeZone()) {
df = _force(df, _timeZone);
}
- return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _typeFactory,
_typeResolverBuilder, df, _handlerInstantiator, _locale,
_timeZone, _defaultBase64);
}
@@ -256,7 +254,7 @@
if (_handlerInstantiator == hi) {
return this;
}
- return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, hi, _locale,
_timeZone, _defaultBase64);
}
@@ -265,7 +263,7 @@
if (_locale == l) {
return this;
}
- return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, l,
_timeZone, _defaultBase64);
}
@@ -286,7 +284,7 @@
DateFormat df = _force(_dateFormat, tz);
return new BaseSettings(_classIntrospector, _annotationIntrospector,
- _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ _propertyNamingStrategy, _typeFactory,
_typeResolverBuilder, df, _handlerInstantiator, _locale,
tz, _defaultBase64);
}
@@ -299,7 +297,7 @@
return this;
}
return new BaseSettings(_classIntrospector, _annotationIntrospector,
- _visibilityChecker, _propertyNamingStrategy, _typeFactory,
+ _propertyNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, base64);
}
@@ -318,10 +316,6 @@
return _annotationIntrospector;
}
- public VisibilityChecker<?> getVisibilityChecker() {
- return _visibilityChecker;
- }
-
public PropertyNamingStrategy getPropertyNamingStrategy() {
return _propertyNamingStrategy;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigFeature.java b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigFeature.java
index daf9f25..91edeca 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigFeature.java
@@ -3,7 +3,7 @@
/**
* Interface that actual SerializationFeature enumerations used by
* {@link MapperConfig} implementations must implement.
- * Necessary since enums can not be extended using normal
+ * Necessary since enums cannot be extended using normal
* inheritance, but can implement interfaces
*/
public interface ConfigFeature
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java
index 18e6304..7557d9e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverride.java
@@ -1,8 +1,10 @@
package com.fasterxml.jackson.databind.cfg;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonSetter;
/**
* Configuration object that is accessed by databinding functionality
@@ -21,37 +23,115 @@
protected JsonFormat.Value _format;
/**
- * Definitions of inclusion overrides, if any.
+ * Definitions of inclusion defaults to use for properties included in this POJO type.
+ * Overrides global defaults, may be overridden by per-property-type (see
+ * {@link #_includeAsProperty}) and per-property overrides (annotations).
*/
protected JsonInclude.Value _include;
/**
+ * Definitions of inclusion defaults for properties of this specified type (regardless
+ * of POJO in which they are included).
+ * Overrides global defaults, per-POJO inclusion defaults (see {#link {@link #_include}}),
+ * may be overridden by per-property overrides.
+ *
+ * @since 2.9
+ */
+ protected JsonInclude.Value _includeAsProperty;
+
+ /**
* Definitions of property ignoral (whether to serialize, deserialize
* given logical property) overrides, if any.
*/
protected JsonIgnoreProperties.Value _ignorals;
/**
+ * Definitions of setter overrides regarding null handling
+ *
+ * @since 2.9
+ */
+ protected JsonSetter.Value _setterInfo;
+
+ /**
+ * Overrides for auto-detection visibility rules for this type.
+ *
+ * @since 2.9
+ */
+ protected JsonAutoDetect.Value _visibility;
+
+ /**
* Flag that indicates whether "is ignorable type" is specified for this type;
* and if so, is it to be ignored (true) or not ignored (false); `null` is
* used to indicate "not specified", in which case other configuration (class
* annotation) is used.
*/
protected Boolean _isIgnoredType;
+
+ /**
+ * Flag that indicates whether properties of this type default to being merged
+ * or not.
+ */
+ protected Boolean _mergeable;
protected ConfigOverride() { }
protected ConfigOverride(ConfigOverride src) {
_format = src._format;
_include = src._include;
+ _includeAsProperty = src._includeAsProperty;
_ignorals = src._ignorals;
+ _setterInfo = src._setterInfo;
+ _visibility = src._visibility;
_isIgnoredType = src._isIgnoredType;
+ _mergeable = src._mergeable;
+ }
+
+ /**
+ * Accessor for immutable "empty" instance that has no configuration overrides defined.
+ *
+ * @since 2.9
+ */
+ public static ConfigOverride empty() {
+ return Empty.INSTANCE;
}
public JsonFormat.Value getFormat() { return _format; }
public JsonInclude.Value getInclude() { return _include; }
+
+ /**
+ * @since 2.9
+ */
+ public JsonInclude.Value getIncludeAsProperty() { return _includeAsProperty; }
+
public JsonIgnoreProperties.Value getIgnorals() { return _ignorals; }
public Boolean getIsIgnoredType() {
return _isIgnoredType;
}
+
+ /**
+ * @since 2.9
+ */
+ public JsonSetter.Value getSetterInfo() { return _setterInfo; }
+
+ /**
+ * @since 2.9
+ */
+ public JsonAutoDetect.Value getVisibility() { return _visibility; }
+
+ /**
+ * @since 2.9
+ */
+ public Boolean getMergeable() { return _mergeable; }
+
+ /**
+ * Implementation used solely for "empty" instance; has no mutators
+ * and is not changed by core functionality.
+ *
+ * @since 2.9
+ */
+ final static class Empty extends ConfigOverride {
+ final static Empty INSTANCE = new Empty();
+
+ private Empty() { }
+ }
}
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 9fd944e..49c622c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
@@ -2,6 +2,10 @@
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
+
/**
* Container for individual {@link ConfigOverride} values.
*
@@ -12,28 +16,82 @@
{
private static final long serialVersionUID = 1L;
+ /**
+ * Per-type override definitions
+ */
protected Map<Class<?>, MutableConfigOverride> _overrides;
+ // // // Global defaulting
+
+ /**
+ * @since 2.9
+ */
+ protected JsonInclude.Value _defaultInclusion;
+
+ /**
+ * @since 2.9
+ */
+ protected JsonSetter.Value _defaultSetterInfo;
+
+ /**
+ * @since 2.9
+ */
+ protected VisibilityChecker<?> _visibilityChecker;
+
+ /**
+ * @since 2.9
+ */
+ protected Boolean _defaultMergeable;
+
+ /*
+ /**********************************************************
+ /* Life cycle
+ /**********************************************************
+ */
+
public ConfigOverrides() {
- _overrides = null;
+ this(null,
+ // !!! TODO: change to (ALWAYS, ALWAYS)?
+ JsonInclude.Value.empty(),
+ JsonSetter.Value.empty(),
+ VisibilityChecker.Std.defaultInstance(),
+ null
+ );
}
- protected ConfigOverrides(Map<Class<?>, MutableConfigOverride> overrides) {
+ protected ConfigOverrides(Map<Class<?>, MutableConfigOverride> overrides,
+ JsonInclude.Value defIncl,
+ JsonSetter.Value defSetter,
+ VisibilityChecker<?> defVisibility,
+ Boolean defMergeable) {
_overrides = overrides;
+ _defaultInclusion = defIncl;
+ _defaultSetterInfo = defSetter;
+ _visibilityChecker = defVisibility;
+ _defaultMergeable = defMergeable;
}
public ConfigOverrides copy()
{
+ Map<Class<?>, MutableConfigOverride> newOverrides;
if (_overrides == null) {
- return new ConfigOverrides();
+ newOverrides = null;
+ } else {
+ newOverrides = _newMap();
+ for (Map.Entry<Class<?>, MutableConfigOverride> entry : _overrides.entrySet()) {
+ newOverrides.put(entry.getKey(), entry.getValue().copy());
+ }
}
- Map<Class<?>, MutableConfigOverride> newOverrides = _newMap();
- for (Map.Entry<Class<?>, MutableConfigOverride> entry : _overrides.entrySet()) {
- newOverrides.put(entry.getKey(), entry.getValue().copy());
- }
- return new ConfigOverrides(newOverrides);
+ return new ConfigOverrides(newOverrides,
+ _defaultInclusion, _defaultSetterInfo, _visibilityChecker, _defaultMergeable);
}
+ /*
+ /**********************************************************
+ /* Per-type override access
+ /**********************************************************
+ */
+
public ConfigOverride findOverride(Class<?> type) {
if (_overrides == null) {
return null;
@@ -53,6 +111,65 @@
return override;
}
+ /*
+ /**********************************************************
+ /* Global defaults access
+ /**********************************************************
+ */
+
+ public JsonInclude.Value getDefaultInclusion() {
+ return _defaultInclusion;
+ }
+
+ public JsonSetter.Value getDefaultSetterInfo() {
+ return _defaultSetterInfo;
+ }
+
+ public Boolean getDefaultMergeable() {
+ return _defaultMergeable;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public VisibilityChecker<?> getDefaultVisibility() {
+ return _visibilityChecker;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public void setDefaultInclusion(JsonInclude.Value v) {
+ _defaultInclusion = v;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public void setDefaultSetterInfo(JsonSetter.Value v) {
+ _defaultSetterInfo = v;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public void setDefaultMergeable(Boolean v) {
+ _defaultMergeable = v;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public void setDefaultVisibility(VisibilityChecker<?> v) {
+ _visibilityChecker = v;
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
protected Map<Class<?>, MutableConfigOverride> _newMap() {
return new HashMap<Class<?>, MutableConfigOverride>();
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java b/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java
index a0170b9..521cf8b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java
@@ -69,7 +69,7 @@
protected final static Object NULL_SURROGATE = new Object();
/**
- * Shared attributes that we can not modify in-place.
+ * Shared attributes that we cannot modify in-place.
*/
protected final Map<?,?> _shared;
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
index de0c6fe..20b048f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
@@ -99,7 +99,7 @@
public DeserializerFactoryConfig withAdditionalDeserializers(Deserializers additional)
{
if (additional == null) {
- throw new IllegalArgumentException("Can not pass null Deserializers");
+ throw new IllegalArgumentException("Cannot pass null Deserializers");
}
Deserializers[] all = ArrayBuilders.insertInListNoDup(_additionalDeserializers, additional);
return new DeserializerFactoryConfig(all, _additionalKeyDeserializers, _modifiers,
@@ -115,7 +115,7 @@
public DeserializerFactoryConfig withAdditionalKeyDeserializers(KeyDeserializers additional)
{
if (additional == null) {
- throw new IllegalArgumentException("Can not pass null KeyDeserializers");
+ throw new IllegalArgumentException("Cannot pass null KeyDeserializers");
}
KeyDeserializers[] all = ArrayBuilders.insertInListNoDup(_additionalKeyDeserializers, additional);
return new DeserializerFactoryConfig(_additionalDeserializers, all, _modifiers,
@@ -131,7 +131,7 @@
public DeserializerFactoryConfig withDeserializerModifier(BeanDeserializerModifier modifier)
{
if (modifier == null) {
- throw new IllegalArgumentException("Can not pass null modifier");
+ throw new IllegalArgumentException("Cannot pass null modifier");
}
BeanDeserializerModifier[] all = ArrayBuilders.insertInListNoDup(_modifiers, modifier);
return new DeserializerFactoryConfig(_additionalDeserializers, _additionalKeyDeserializers, all,
@@ -148,7 +148,7 @@
public DeserializerFactoryConfig withAbstractTypeResolver(AbstractTypeResolver resolver)
{
if (resolver == null) {
- throw new IllegalArgumentException("Can not pass null resolver");
+ throw new IllegalArgumentException("Cannot pass null resolver");
}
AbstractTypeResolver[] all = ArrayBuilders.insertInListNoDup(_abstractTypeResolvers, resolver);
return new DeserializerFactoryConfig(_additionalDeserializers, _additionalKeyDeserializers, _modifiers,
@@ -168,7 +168,7 @@
public DeserializerFactoryConfig withValueInstantiators(ValueInstantiators instantiators)
{
if (instantiators == null) {
- throw new IllegalArgumentException("Can not pass null resolver");
+ throw new IllegalArgumentException("Cannot pass null resolver");
}
ValueInstantiators[] all = ArrayBuilders.insertInListNoDup(_valueInstantiators, instantiators);
return new DeserializerFactoryConfig(_additionalDeserializers, _additionalKeyDeserializers, _modifiers,
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/HandlerInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/cfg/HandlerInstantiator.java
index 2106979..757a379 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/HandlerInstantiator.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/HandlerInstantiator.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.introspect.Annotated;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
@@ -160,4 +161,22 @@
Class<?> implClass) {
return null;
}
+
+ /**
+ * Method called to construct a Filter (any Object with implementation of
+ * <code>equals(Object)</code> that determines if given value is to be
+ * excluded (true) or included (false)) to be used based on
+ * {@link com.fasterxml.jackson.annotation.JsonInclude} annotation (or
+ * equivalent).
+ *<p>
+ * Default implementation returns `null` to indicate that default instantiation
+ * (use zero-arg constructor of the <code>filterClass</code>) should be
+ * used.
+ *
+ * @since 2.9
+ */
+ public Object includeFilterInstance(SerializationConfig config,
+ BeanPropertyDefinition forProperty, Class<?> filterClass) {
+ return null;
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java
index 1ae5732..7bc3e7f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java
@@ -13,6 +13,7 @@
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
+import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
@@ -36,7 +37,7 @@
implements ClassIntrospector.MixInResolver,
java.io.Serializable
{
- private static final long serialVersionUID = 1L; // since 2.5
+ private static final long serialVersionUID = 2L; // since 2.9
/**
* @since 2.7
@@ -57,7 +58,7 @@
* Immutable container object for simple configuration settings.
*/
protected final BaseSettings _base;
-
+
/*
/**********************************************************
/* Life-cycle: constructors
@@ -232,22 +233,12 @@
* Non-final since it is actually overridden by sub-classes (for now?)
*/
public AnnotationIntrospector getAnnotationIntrospector() {
- return _base.getAnnotationIntrospector();
+ if (isEnabled(MapperFeature.USE_ANNOTATIONS)) {
+ return _base.getAnnotationIntrospector();
+ }
+ return NopAnnotationIntrospector.instance;
}
- /**
- * Accessor for object used for determining whether specific property elements
- * (method, constructors, fields) can be auto-detected based on
- * their visibility (access modifiers). Can be changed to allow
- * different minimum visibility levels for auto-detection. Note
- * that this is the global handler; individual types (classes)
- * can further override active checker used (using
- * {@link JsonAutoDetect} annotation)
- */
- public VisibilityChecker<?> getDefaultVisibilityChecker() {
- return _base.getVisibilityChecker();
- }
-
public final PropertyNamingStrategy getPropertyNamingStrategy() {
return _base.getPropertyNamingStrategy();
}
@@ -255,7 +246,7 @@
public final HandlerInstantiator getHandlerInstantiator() {
return _base.getHandlerInstantiator();
}
-
+
/*
/**********************************************************
/* Configuration: type and subtype handling
@@ -319,12 +310,14 @@
public BeanDescription introspectClassAnnotations(Class<?> cls) {
return introspectClassAnnotations(constructType(cls));
}
-
+
/**
* Accessor for getting bean description that only contains class
* annotations: useful if no getter/setter/creator information is needed.
*/
- public abstract BeanDescription introspectClassAnnotations(JavaType type);
+ public BeanDescription introspectClassAnnotations(JavaType type) {
+ return getClassIntrospector().forClassAnnotations(this, type, this);
+ }
/**
* Accessor for getting bean description that only contains immediate class
@@ -334,12 +327,15 @@
public BeanDescription introspectDirectClassAnnotations(Class<?> cls) {
return introspectDirectClassAnnotations(constructType(cls));
}
+
/**
* Accessor for getting bean description that only contains immediate class
* annotations: ones from the class, and its direct mix-in, if any, but
* not from super types.
*/
- public abstract BeanDescription introspectDirectClassAnnotations(JavaType type);
+ public final BeanDescription introspectDirectClassAnnotations(JavaType type) {
+ return getClassIntrospector().forDirectClassAnnotations(this, type, this);
+ }
/*
/**********************************************************
@@ -348,6 +344,33 @@
*/
/**
+ * Accessor for finding {@link ConfigOverride} to use for
+ * properties of given type, if any exist; or return `null` if not.
+ *<p>
+ * Note that only directly associated override
+ * is found; no type hierarchy traversal is performed.
+ *
+ * @since 2.8
+ *
+ * @return Override object to use for the type, if defined; null if none.
+ */
+ public abstract ConfigOverride findConfigOverride(Class<?> type);
+
+ /**
+ * Accessor for finding {@link ConfigOverride} to use for
+ * properties of given type, if any exist; or if none, return an immutable
+ * "empty" instance with no overrides.
+ *<p>
+ * Note that only directly associated override
+ * is found; no type hierarchy traversal is performed.
+ *
+ * @since 2.9
+ *
+ * @return Override object to use for the type, never null (but may be empty)
+ */
+ public abstract ConfigOverride getConfigOverride(Class<?> type);
+
+ /**
* Accessor for default property inclusion to use for serialization,
* used unless overridden by per-type or per-property overrides.
*
@@ -374,8 +397,52 @@
*
* @since 2.8.2
*/
- public abstract JsonInclude.Value getDefaultPropertyInclusion(Class<?> baseType,
- JsonInclude.Value defaultIncl);
+ public JsonInclude.Value getDefaultPropertyInclusion(Class<?> baseType,
+ JsonInclude.Value defaultIncl)
+ {
+ JsonInclude.Value v = getConfigOverride(baseType).getInclude();
+ if (v != null) {
+ return v;
+ }
+ return defaultIncl;
+ }
+
+ /**
+ * Accessor for default property inclusion to use for serialization,
+ * considering possible per-type override for given base type and
+ * possible per-type override for given property type.<br>
+ * NOTE: if no override found, defaults to value returned by
+ * {@link #getDefaultPropertyInclusion()}.
+ *
+ * @param baseType Type of the instance containing the targeted property.
+ * @param propertyType Type of the property to look up inclusion setting for.
+ *
+ * @since 2.9
+ */
+ public abstract JsonInclude.Value getDefaultInclusion(Class<?> baseType,
+ Class<?> propertyType);
+
+ /**
+ * Accessor for default property inclusion to use for serialization,
+ * considering possible per-type override for given base type and
+ * possible per-type override for given property type; but
+ * if none found, returning given <code>defaultIncl</code>
+ *
+ * @param baseType Type of the instance containing the targeted property.
+ * @param propertyType Type of the property to look up inclusion setting for.
+ * @param defaultIncl Inclusion setting to return if no overrides found.
+ *
+ * @since 2.9
+ */
+ public JsonInclude.Value getDefaultInclusion(Class<?> baseType,
+ Class<?> propertyType, JsonInclude.Value defaultIncl)
+ {
+ JsonInclude.Value baseOverride = getConfigOverride(baseType).getInclude();
+ JsonInclude.Value propOverride = getConfigOverride(propertyType).getIncludeAsProperty();
+
+ JsonInclude.Value result = JsonInclude.Value.mergeAll(defaultIncl, baseOverride, propOverride);
+ return result;
+ }
/**
* Accessor for default format settings to use for serialization (and, to a degree
@@ -406,15 +473,60 @@
AnnotatedClass actualClass);
/**
- * Accessor for finding possible {@link ConfigOverride} to use for
- * properties of given type. Note that only directly associate override
- * is found; no type hierarchy traversal is performed.
- *
- * @since 2.8
- *
- * @return Override object if there is an override for specified type; `null` if not
+ * Accessor for object used for determining whether specific property elements
+ * (method, constructors, fields) can be auto-detected based on
+ * their visibility (access modifiers). Can be changed to allow
+ * different minimum visibility levels for auto-detection. Note
+ * that this is the global handler; individual types (classes)
+ * can further override active checker used (using
+ * {@link JsonAutoDetect} annotation)
*/
- public abstract ConfigOverride findConfigOverride(Class<?> type);
+ public abstract VisibilityChecker<?> getDefaultVisibilityChecker();
+
+ /**
+ * Accessor for object used for determining whether specific property elements
+ * (method, constructors, fields) can be auto-detected based on
+ * their visibility (access modifiers). This is based on global defaults
+ * (as would be returned by {@link #getDefaultVisibilityChecker()}, but
+ * then modified by possible class annotation (see {@link JsonAutoDetect})
+ * and/or per-type config override (see {@link ConfigOverride#getVisibility()}).
+ *
+ * @since 2.9
+ */
+ public abstract VisibilityChecker<?> getDefaultVisibilityChecker(Class<?> baseType,
+ AnnotatedClass actualClass);
+
+ /**
+ * Accessor for the baseline setter info used as the global baseline,
+ * not considering possible per-type overrides.
+ *
+ * @return Global base settings; never null
+ *
+ * @since 2.9
+ */
+ public abstract JsonSetter.Value getDefaultSetterInfo();
+
+ /**
+ * Accessor for the baseline merge info used as the global baseline,
+ * not considering possible per-type overrides.
+ *
+ * @return Global base settings, if any; `null` if none.
+ *
+ * @since 2.9
+ */
+ public abstract Boolean getDefaultMergeable();
+
+ /**
+ * Accessor for the baseline merge info used for given type, including global
+ * defaults if no type-specific overrides defined.
+ *
+ * @return Type-specific settings (if any); global defaults (same as
+ * {@link #getDefaultMergeable()}) otherwise, if any defined; or `null`
+ * if neither defined
+ *
+ * @since 2.9
+ */
+ public abstract Boolean getDefaultMergeable(Class<?> baseType);
/*
/**********************************************************
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java
index 6f90f9b..a7ca2e5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.core.Base64Variant;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
@@ -23,8 +24,24 @@
extends MapperConfig<T>
implements java.io.Serializable
{
+ /**
+ * @since 2.9
+ */
+ protected final static ConfigOverride EMPTY_OVERRIDE = ConfigOverride.empty();
+
private final static int DEFAULT_MAPPER_FEATURES = collectFeatureDefaults(MapperFeature.class);
+ /**
+ * @since 2.9
+ */
+ private final static int AUTO_DETECT_MASK =
+ MapperFeature.AUTO_DETECT_FIELDS.getMask()
+ | MapperFeature.AUTO_DETECT_GETTERS.getMask()
+ | MapperFeature.AUTO_DETECT_IS_GETTERS.getMask()
+ | MapperFeature.AUTO_DETECT_SETTERS.getMask()
+ | MapperFeature.AUTO_DETECT_CREATORS.getMask()
+ ;
+
/*
/**********************************************************
/* Immutable config
@@ -33,7 +50,7 @@
/**
* Mix-in annotation mappings to use, if any: immutable,
- * can not be changed once defined.
+ * cannot be changed once defined.
*
* @since 2.6
*/
@@ -102,8 +119,8 @@
* @since 2.8
*/
protected MapperConfigBase(BaseSettings base,
- SubtypeResolver str, SimpleMixInResolver mixins,
- RootNameLookup rootNames, ConfigOverrides configOverrides)
+ SubtypeResolver str, SimpleMixInResolver mixins, RootNameLookup rootNames,
+ ConfigOverrides configOverrides)
{
super(base, DEFAULT_MAPPER_FEATURES);
_mixIns = mixins;
@@ -117,14 +134,25 @@
}
/**
- * @deprecated Since 2.8, remove from 2.9 or later
+ * Copy constructor usually called to make a copy for use by
+ * ObjectMapper that is copied.
+ *
+ * @since 2.8
*/
- @Deprecated
- protected MapperConfigBase(BaseSettings base,
- SubtypeResolver str, SimpleMixInResolver mixins,
- RootNameLookup rootNames)
+ protected MapperConfigBase(MapperConfigBase<CFG,T> src,
+ SimpleMixInResolver mixins, RootNameLookup rootNames,
+ ConfigOverrides configOverrides)
{
- this(base, str, mixins, rootNames, null);
+ // 18-Apr-2018, tatu: [databind#1898] need to force copying of `ClassIntrospector`
+ // (to clear its cache) to avoid leakage
+ super(src, src._base.copy());
+ _mixIns = mixins;
+ _subtypeResolver = src._subtypeResolver;
+ _rootNames = rootNames;
+ _rootName = src._rootName;
+ _view = src._view;
+ _attributes = src._attributes;
+ _configOverrides = configOverrides;
}
/**
@@ -215,7 +243,7 @@
_attributes = src._attributes;
_configOverrides = src._configOverrides;
}
-
+
/**
* @since 2.3
*/
@@ -231,58 +259,86 @@
_configOverrides = src._configOverrides;
}
+ /*
+ /**********************************************************
+ /* Abstract fluent factory methods to be implemented by subtypes
+ /**********************************************************
+ */
+
/**
- * @since 2.8
+ * @since 2.9 (in this case, demoted from sub-classes)
*/
- protected MapperConfigBase(MapperConfigBase<CFG,T> src, SimpleMixInResolver mixins,
- RootNameLookup rootNames, ConfigOverrides configOverrides)
- {
- super(src);
- _mixIns = mixins;
- _subtypeResolver = src._subtypeResolver;
- _rootNames = rootNames;
- _rootName = src._rootName;
- _view = src._view;
- _attributes = src._attributes;
- _configOverrides = configOverrides;
- }
+ protected abstract T _withBase(BaseSettings newBase);
+
+ /**
+ * @since 2.9 (in this case, demoted from sub-classes)
+ */
+ protected abstract T _withMapperFeatures(int mapperFeatures);
/*
/**********************************************************
- /* Overrides
+ /* Additional shared fluent factory methods; features
/**********************************************************
*/
-
- // note: demoted in 2.8 from sub-classes, as there's no difference
+
+ /**
+ * Fluent factory method that will construct and return a new configuration
+ * object instance with specified features enabled.
+ */
+ @SuppressWarnings("unchecked")
@Override
- public VisibilityChecker<?> getDefaultVisibilityChecker()
+ public final T with(MapperFeature... features)
{
- VisibilityChecker<?> vchecker = super.getDefaultVisibilityChecker();
- // then global overrides (disabling)
- if (!isEnabled(MapperFeature.AUTO_DETECT_SETTERS)) {
- vchecker = vchecker.withSetterVisibility(Visibility.NONE);
+ int newMapperFlags = _mapperFeatures;
+ for (MapperFeature f : features) {
+ newMapperFlags |= f.getMask();
}
- if (!isEnabled(MapperFeature.AUTO_DETECT_CREATORS)) {
- vchecker = vchecker.withCreatorVisibility(Visibility.NONE);
+ if (newMapperFlags == _mapperFeatures) {
+ return (T) this;
}
- if (!isEnabled(MapperFeature.AUTO_DETECT_GETTERS)) {
- vchecker = vchecker.withGetterVisibility(Visibility.NONE);
+ return _withMapperFeatures(newMapperFlags);
+ }
+
+ /**
+ * Fluent factory method that will construct and return a new configuration
+ * object instance with specified features disabled.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public final T without(MapperFeature... features)
+ {
+ int newMapperFlags = _mapperFeatures;
+ for (MapperFeature f : features) {
+ newMapperFlags &= ~f.getMask();
}
- if (!isEnabled(MapperFeature.AUTO_DETECT_IS_GETTERS)) {
- vchecker = vchecker.withIsGetterVisibility(Visibility.NONE);
+ if (newMapperFlags == _mapperFeatures) {
+ return (T) this;
}
- if (!isEnabled(MapperFeature.AUTO_DETECT_FIELDS)) {
- vchecker = vchecker.withFieldVisibility(Visibility.NONE);
+ return _withMapperFeatures(newMapperFlags);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final T with(MapperFeature feature, boolean state)
+ {
+ int newMapperFlags;
+ if (state) {
+ newMapperFlags = _mapperFeatures | feature.getMask();
+ } else {
+ newMapperFlags = _mapperFeatures & ~feature.getMask();
}
- return vchecker;
+ if (newMapperFlags == _mapperFeatures) {
+ return (T) this;
+ }
+ return _withMapperFeatures(newMapperFlags);
}
/*
/**********************************************************
- /* Addition fluent factory methods, common to all sub-types
+ /* Additional shared fluent factory methods; introspectors
/**********************************************************
*/
-
+
/**
* Method for constructing and returning a new instance with different
* {@link AnnotationIntrospector} to use (replacing old one).
@@ -290,19 +346,25 @@
* NOTE: make sure to register new instance with <code>ObjectMapper</code>
* if directly calling this method.
*/
- public abstract T with(AnnotationIntrospector ai);
+ public final T with(AnnotationIntrospector ai) {
+ return _withBase(_base.withAnnotationIntrospector(ai));
+ }
/**
* Method for constructing and returning a new instance with additional
* {@link AnnotationIntrospector} appended (as the lowest priority one)
*/
- public abstract T withAppendedAnnotationIntrospector(AnnotationIntrospector introspector);
+ public final T withAppendedAnnotationIntrospector(AnnotationIntrospector ai) {
+ return _withBase(_base.withAppendedAnnotationIntrospector(ai));
+ }
/**
* Method for constructing and returning a new instance with additional
* {@link AnnotationIntrospector} inserted (as the highest priority one)
*/
- public abstract T withInsertedAnnotationIntrospector(AnnotationIntrospector introspector);
+ public final T withInsertedAnnotationIntrospector(AnnotationIntrospector ai) {
+ return _withBase(_base.withInsertedAnnotationIntrospector(ai));
+ }
/**
* Method for constructing and returning a new instance with different
@@ -312,122 +374,15 @@
* NOTE: make sure to register new instance with <code>ObjectMapper</code>
* if directly calling this method.
*/
- public abstract T with(ClassIntrospector ci);
-
- /**
- * Method for constructing and returning a new instance with different
- * {@link DateFormat}
- * to use.
- *<p>
- * NOTE: make sure to register new instance with <code>ObjectMapper</code>
- * if directly calling this method.
- */
- public abstract T with(DateFormat df);
-
- /**
- * Method for constructing and returning a new instance with different
- * {@link HandlerInstantiator}
- * to use.
- *<p>
- * NOTE: make sure to register new instance with <code>ObjectMapper</code>
- * if directly calling this method.
- */
- public abstract T with(HandlerInstantiator hi);
-
- /**
- * Method for constructing and returning a new instance with different
- * {@link PropertyNamingStrategy}
- * to use.
- *<p>
- * NOTE: make sure to register new instance with <code>ObjectMapper</code>
- * if directly calling this method.
- */
- public abstract T with(PropertyNamingStrategy strategy);
-
- /**
- * Method for constructing and returning a new instance with different
- * root name to use (none, if null).
- *<p>
- * Note that when a root name is set to a non-Empty String, this will automatically force use
- * of root element wrapping with given name. If empty String passed, will
- * disable root name wrapping; and if null used, will instead use
- * <code>SerializationFeature</code> to determine if to use wrapping, and annotation
- * (or default name) for actual root name to use.
- *
- * @param rootName to use: if null, means "use default" (clear setting);
- * if empty String ("") means that no root name wrapping is used;
- * otherwise defines root name to use.
- *
- * @since 2.6
- */
- public abstract T withRootName(PropertyName rootName);
-
- public T withRootName(String rootName) {
- if (rootName == null) {
- return withRootName((PropertyName) null);
- }
- return withRootName(PropertyName.construct(rootName));
+ public final T with(ClassIntrospector ci) {
+ return _withBase(_base.withClassIntrospector(ci));
}
-
- /**
- * Method for constructing and returning a new instance with different
- * {@link SubtypeResolver}
- * to use.
- *<p>
- * NOTE: make sure to register new instance with <code>ObjectMapper</code>
- * if directly calling this method.
- */
- public abstract T with(SubtypeResolver str);
-
- /**
- * Method for constructing and returning a new instance with different
- * {@link TypeFactory}
- * to use.
- */
- public abstract T with(TypeFactory typeFactory);
- /**
- * Method for constructing and returning a new instance with different
- * {@link TypeResolverBuilder} to use.
+ /*
+ /**********************************************************
+ /* Additional shared fluent factory methods; attributes
+ /**********************************************************
*/
- public abstract T with(TypeResolverBuilder<?> trb);
-
- /**
- * Method for constructing and returning a new instance with different
- * view to use.
- */
- public abstract T withView(Class<?> view);
-
- /**
- * Method for constructing and returning a new instance with different
- * {@link VisibilityChecker}
- * to use.
- */
- public abstract T with(VisibilityChecker<?> vc);
-
- /**
- * Method for constructing and returning a new instance with different
- * minimal visibility level for specified property type
- */
- public abstract T withVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility);
-
- /**
- * Method for constructing and returning a new instance with different
- * default {@link java.util.Locale} to use for formatting.
- */
- public abstract T with(Locale l);
-
- /**
- * Method for constructing and returning a new instance with different
- * default {@link java.util.TimeZone} to use for formatting of date values.
- */
- public abstract T with(TimeZone tz);
-
- /**
- * Method for constructing and returning a new instance with different
- * default {@link Base64Variant} to use with base64-encoded binary values.
- */
- public abstract T with(Base64Variant base64);
/**
* Method for constructing an instance that has specified
@@ -466,13 +421,142 @@
public T withoutAttribute(Object key) {
return with(getAttributes().withoutSharedAttribute(key));
}
+
+ /*
+ /**********************************************************
+ /* Additional shared fluent factory methods; factories
+ /**********************************************************
+ */
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * {@link TypeFactory}
+ * to use.
+ */
+ public final T with(TypeFactory tf) {
+ return _withBase( _base.withTypeFactory(tf));
+ }
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * {@link TypeResolverBuilder} to use.
+ */
+ public final T with(TypeResolverBuilder<?> trb) {
+ return _withBase(_base.withTypeResolverBuilder(trb));
+ }
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * {@link PropertyNamingStrategy}
+ * to use.
+ *<p>
+ * NOTE: make sure to register new instance with <code>ObjectMapper</code>
+ * if directly calling this method.
+ */
+ public final T with(PropertyNamingStrategy pns) {
+ return _withBase(_base.withPropertyNamingStrategy(pns));
+ }
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * {@link HandlerInstantiator}
+ * to use.
+ *<p>
+ * NOTE: make sure to register new instance with <code>ObjectMapper</code>
+ * if directly calling this method.
+ */
+ public final T with(HandlerInstantiator hi) {
+ return _withBase(_base.withHandlerInstantiator(hi));
+ }
+
+ /*
+ /**********************************************************
+ /* Additional shared fluent factory methods; other
+ /**********************************************************
+ */
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * default {@link Base64Variant} to use with base64-encoded binary values.
+ */
+ public final T with(Base64Variant base64) {
+ return _withBase(_base.with(base64));
+ }
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * {@link DateFormat}
+ * to use.
+ *<p>
+ * NOTE: non-final since <code>SerializationConfig</code> needs to override this
+ */
+ public T with(DateFormat df) {
+ return _withBase(_base.withDateFormat(df));
+ }
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * default {@link java.util.Locale} to use for formatting.
+ */
+ public final T with(Locale l) {
+ return _withBase(_base.with(l));
+ }
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * default {@link java.util.TimeZone} to use for formatting of date values.
+ */
+ public final T with(TimeZone tz) {
+ return _withBase(_base.with(tz));
+ }
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * root name to use (none, if null).
+ *<p>
+ * Note that when a root name is set to a non-Empty String, this will automatically force use
+ * of root element wrapping with given name. If empty String passed, will
+ * disable root name wrapping; and if null used, will instead use
+ * <code>SerializationFeature</code> to determine if to use wrapping, and annotation
+ * (or default name) for actual root name to use.
+ *
+ * @param rootName to use: if null, means "use default" (clear setting);
+ * if empty String ("") means that no root name wrapping is used;
+ * otherwise defines root name to use.
+ *
+ * @since 2.6
+ */
+ public abstract T withRootName(PropertyName rootName);
+
+ public T withRootName(String rootName) {
+ if (rootName == null) {
+ return withRootName((PropertyName) null);
+ }
+ return withRootName(PropertyName.construct(rootName));
+ }
+ /**
+ * Method for constructing and returning a new instance with different
+ * {@link SubtypeResolver}
+ * to use.
+ *<p>
+ * NOTE: make sure to register new instance with <code>ObjectMapper</code>
+ * if directly calling this method.
+ */
+ public abstract T with(SubtypeResolver str);
+
+ /**
+ * Method for constructing and returning a new instance with different
+ * view to use.
+ */
+ public abstract T withView(Class<?> view);
+
/*
/**********************************************************
/* Simple accessors
/**********************************************************
*/
-
+
/**
* Accessor for object used for finding out all reachable subtypes
* for supertypes; needed when a logical type name is used instead
@@ -510,16 +594,48 @@
/*
/**********************************************************
- /* Property config override access
+ /* Configuration access; default/overrides
/**********************************************************
*/
-
+
+ @Override
+ public final ConfigOverride getConfigOverride(Class<?> type) {
+ ConfigOverride override = _configOverrides.findOverride(type);
+ return (override == null) ? EMPTY_OVERRIDE : override;
+ }
+
@Override
public final ConfigOverride findConfigOverride(Class<?> type) {
return _configOverrides.findOverride(type);
}
@Override
+ public final JsonInclude.Value getDefaultPropertyInclusion() {
+ return _configOverrides.getDefaultInclusion();
+ }
+
+ @Override
+ public final JsonInclude.Value getDefaultPropertyInclusion(Class<?> baseType) {
+ JsonInclude.Value v = getConfigOverride(baseType).getInclude();
+ JsonInclude.Value def = getDefaultPropertyInclusion();
+ if (def == null) {
+ return v;
+ }
+ return def.withOverrides(v);
+ }
+
+ @Override
+ public final JsonInclude.Value getDefaultInclusion(Class<?> baseType,
+ Class<?> propertyType) {
+ JsonInclude.Value v = getConfigOverride(propertyType).getIncludeAsProperty();
+ JsonInclude.Value def = getDefaultPropertyInclusion(baseType);
+ if (def == null) {
+ return v;
+ }
+ return def.withOverrides(v);
+ }
+
+ @Override
public final JsonFormat.Value getDefaultPropertyFormat(Class<?> type) {
ConfigOverride overrides = _configOverrides.findOverride(type);
if (overrides != null) {
@@ -556,6 +672,70 @@
return JsonIgnoreProperties.Value.merge(base, overrides);
}
+ @Override
+ public final VisibilityChecker<?> getDefaultVisibilityChecker()
+ {
+ VisibilityChecker<?> vchecker = _configOverrides.getDefaultVisibility();
+ // then global overrides (disabling)
+ // 05-Mar-2018, tatu: As per [databind#1947], need to see if any disabled
+ if ((_mapperFeatures & AUTO_DETECT_MASK) != AUTO_DETECT_MASK) {
+ if (!isEnabled(MapperFeature.AUTO_DETECT_FIELDS)) {
+ vchecker = vchecker.withFieldVisibility(Visibility.NONE);
+ }
+ if (!isEnabled(MapperFeature.AUTO_DETECT_GETTERS)) {
+ vchecker = vchecker.withGetterVisibility(Visibility.NONE);
+ }
+ if (!isEnabled(MapperFeature.AUTO_DETECT_IS_GETTERS)) {
+ vchecker = vchecker.withIsGetterVisibility(Visibility.NONE);
+ }
+ if (!isEnabled(MapperFeature.AUTO_DETECT_SETTERS)) {
+ vchecker = vchecker.withSetterVisibility(Visibility.NONE);
+ }
+ if (!isEnabled(MapperFeature.AUTO_DETECT_CREATORS)) {
+ vchecker = vchecker.withCreatorVisibility(Visibility.NONE);
+ }
+ }
+ return vchecker;
+ }
+
+ @Override // since 2.9
+ public final VisibilityChecker<?> getDefaultVisibilityChecker(Class<?> baseType,
+ AnnotatedClass actualClass) {
+ VisibilityChecker<?> vc = getDefaultVisibilityChecker();
+ AnnotationIntrospector intr = getAnnotationIntrospector();
+ if (intr != null) {
+ vc = intr.findAutoDetectVisibility(actualClass, vc);
+ }
+ ConfigOverride overrides = _configOverrides.findOverride(baseType);
+ if (overrides != null) {
+ vc = vc.withOverrides(overrides.getVisibility()); // ok to pass null
+ }
+ return vc;
+ }
+
+ @Override
+ public final JsonSetter.Value getDefaultSetterInfo() {
+ return _configOverrides.getDefaultSetterInfo();
+ }
+
+ @Override
+ public Boolean getDefaultMergeable() {
+ return _configOverrides.getDefaultMergeable();
+ }
+
+ @Override
+ public Boolean getDefaultMergeable(Class<?> baseType) {
+ Boolean b;
+ ConfigOverride cfg = _configOverrides.findOverride(baseType);
+ if (cfg != null) {
+ b = cfg.getMergeable();
+ if (b != null) {
+ return b;
+ }
+ }
+ return _configOverrides.getDefaultMergeable();
+ }
+
/*
/**********************************************************
/* Other config access
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java
index 902e18b..b30b99b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MutableConfigOverride.java
@@ -1,8 +1,10 @@
package com.fasterxml.jackson.databind.cfg;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonSetter;
/**
* Extension of {@link ConfigOverride} that allows changing of
@@ -24,8 +26,8 @@
protected MutableConfigOverride(MutableConfigOverride src) {
super(src);
}
-
- protected MutableConfigOverride copy() {
+
+ public MutableConfigOverride copy() {
return new MutableConfigOverride(this);
}
@@ -33,19 +35,62 @@
_format = v;
return this;
}
-
+
+ /**
+ * Override inclusion setting for all properties contained in POJOs of the
+ * associated type.
+ *
+ * @param v Inclusion setting to apply contained properties.
+ */
public MutableConfigOverride setInclude(JsonInclude.Value v) {
_include = v;
return this;
}
+ /**
+ * Override inclusion setting for properties of the associated type
+ * regardless of the type of the POJO containing it.
+ *
+ * @param v Inclusion setting to apply for properties of associated type.
+ *
+ * @since 2.9
+ */
+ public MutableConfigOverride setIncludeAsProperty(JsonInclude.Value v) {
+ _includeAsProperty = v;
+ return this;
+ }
+
public MutableConfigOverride setIgnorals(JsonIgnoreProperties.Value v) {
_ignorals = v;
return this;
}
-
+
public MutableConfigOverride setIsIgnoredType(Boolean v) {
_isIgnoredType = v;
return this;
}
+
+ /**
+ * @since 2.9
+ */
+ public MutableConfigOverride setSetterInfo(JsonSetter.Value v) {
+ _setterInfo = v;
+ return this;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public MutableConfigOverride setVisibility(JsonAutoDetect.Value v) {
+ _visibility = v;
+ return this;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public MutableConfigOverride setMergeable(Boolean v) {
+ _mergeable = v;
+ return this;
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/SerializerFactoryConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/SerializerFactoryConfig.java
index e997a49..8437633 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/SerializerFactoryConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/SerializerFactoryConfig.java
@@ -56,7 +56,7 @@
public SerializerFactoryConfig withAdditionalSerializers(Serializers additional)
{
if (additional == null) {
- throw new IllegalArgumentException("Can not pass null Serializers");
+ throw new IllegalArgumentException("Cannot pass null Serializers");
}
Serializers[] all = ArrayBuilders.insertInListNoDup(_additionalSerializers, additional);
return new SerializerFactoryConfig(all, _additionalKeySerializers, _modifiers);
@@ -65,7 +65,7 @@
public SerializerFactoryConfig withAdditionalKeySerializers(Serializers additional)
{
if (additional == null) {
- throw new IllegalArgumentException("Can not pass null Serializers");
+ throw new IllegalArgumentException("Cannot pass null Serializers");
}
Serializers[] all = ArrayBuilders.insertInListNoDup(_additionalKeySerializers, additional);
return new SerializerFactoryConfig(_additionalSerializers, all, _modifiers);
@@ -74,7 +74,7 @@
public SerializerFactoryConfig withSerializerModifier(BeanSerializerModifier modifier)
{
if (modifier == null) {
- throw new IllegalArgumentException("Can not pass null modifier");
+ throw new IllegalArgumentException("Cannot pass null modifier");
}
BeanSerializerModifier[] modifiers = ArrayBuilders.insertInListNoDup(_modifiers, modifier);
return new SerializerFactoryConfig(_additionalSerializers, _additionalKeySerializers, modifiers);
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 8515376..1e1d80e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader;
+import com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
@@ -35,6 +36,8 @@
protected final Map<String, SettableBeanProperty> _backRefProperties;
+ protected transient Map<String,SettableBeanProperty> _properties;
+
// support for "native" types, which require special care:
protected final boolean _acceptString;
@@ -48,12 +51,21 @@
/**********************************************************
*/
+ /**
+ * @since 2.9
+ *
+ * @param props Regular properties: currently only needed to support property-annotated
+ * Object Id handling with property inclusion (needed for determining type of Object Id
+ * to bind)
+ */
public AbstractDeserializer(BeanDeserializerBuilder builder,
- BeanDescription beanDesc, Map<String, SettableBeanProperty> backRefProps)
+ BeanDescription beanDesc, Map<String, SettableBeanProperty> backRefProps,
+ Map<String, SettableBeanProperty> props)
{
_baseType = beanDesc.getType();
_objectIdReader = builder.getObjectIdReader();
_backRefProperties = backRefProps;
+ _properties = props;
Class<?> cls = _baseType.getRawClass();
_acceptString = cls.isAssignableFrom(String.class);
_acceptBoolean = (cls == Boolean.TYPE) || cls.isAssignableFrom(Boolean.class);
@@ -61,6 +73,12 @@
_acceptDouble = (cls == Double.TYPE) || cls.isAssignableFrom(Double.class);
}
+ @Deprecated // since 2.9
+ public AbstractDeserializer(BeanDeserializerBuilder builder,
+ BeanDescription beanDesc, Map<String, SettableBeanProperty> backRefProps) {
+ this(builder, beanDesc, backRefProps, null);
+ }
+
protected AbstractDeserializer(BeanDescription beanDesc)
{
_baseType = beanDesc.getType();
@@ -77,7 +95,7 @@
* @since 2.9
*/
protected AbstractDeserializer(AbstractDeserializer base,
- ObjectIdReader objectIdReader)
+ ObjectIdReader objectIdReader, Map<String, SettableBeanProperty> props)
{
_baseType = base._baseType;
_backRefProperties = base._backRefProperties;
@@ -87,8 +105,9 @@
_acceptDouble = base._acceptDouble;
_objectIdReader = objectIdReader;
+ _properties = props;
}
-
+
/**
* Factory method used when constructing instances for non-POJO types, like
* {@link java.util.Map}s.
@@ -103,37 +122,55 @@
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
- // First: may have an override for Object Id:
final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
- final AnnotatedMember accessor = (property == null || intr == null)
- ? null : property.getMember();
- if (accessor != null && intr != null) {
- ObjectIdInfo objectIdInfo = intr.findObjectIdInfo(accessor);
- if (objectIdInfo != null) { // some code duplication here as well (from BeanDeserializerFactory)
- // 2.1: allow modifications by "id ref" annotations as well:
- objectIdInfo = intr.findObjectReferenceInfo(accessor, objectIdInfo);
-
- Class<?> implClass = objectIdInfo.getGeneratorType();
- // 02-May-2017, tatu: Alas, properties are NOT available for abstract classes; can not
- // support this particular type
- if (implClass == ObjectIdGenerators.PropertyGenerator.class) {
- ctxt.reportMappingException(
-"Invalid Object Id definition for abstract type %s: can not use `PropertyGenerator` on polymorphic types using property annotation",
-handledType().getName());
+ if (property != null && intr != null) {
+ final AnnotatedMember accessor = property.getMember();
+ if (accessor != null) {
+ ObjectIdInfo objectIdInfo = intr.findObjectIdInfo(accessor);
+ if (objectIdInfo != null) { // some code duplication here as well (from BeanDeserializerFactory)
+ JavaType idType;
+ ObjectIdGenerator<?> idGen;
+ SettableBeanProperty idProp = null;
+ ObjectIdResolver resolver = ctxt.objectIdResolverInstance(accessor, objectIdInfo);
+
+ // 2.1: allow modifications by "id ref" annotations as well:
+ objectIdInfo = intr.findObjectReferenceInfo(accessor, objectIdInfo);
+ Class<?> implClass = objectIdInfo.getGeneratorType();
+
+ if (implClass == ObjectIdGenerators.PropertyGenerator.class) {
+ PropertyName propName = objectIdInfo.getPropertyName();
+ idProp = (_properties == null) ? null : _properties.get(propName.getSimpleName());
+ if (idProp == null) {
+ ctxt.reportBadDefinition(_baseType, String.format(
+ "Invalid Object Id definition for %s: cannot find property with name '%s'",
+ handledType().getName(), propName));
+ }
+ idType = idProp.getType();
+ idGen = new PropertyBasedObjectIdGenerator(objectIdInfo.getScope());
+/*
+ ctxt.reportBadDefinition(_baseType, String.format(
+/
+"Invalid Object Id definition for abstract type %s: cannot use `PropertyGenerator` on polymorphic types using property annotation",
+handledType().getName()));
+*/
+ } else { // other types simpler
+ resolver = ctxt.objectIdResolverInstance(accessor, objectIdInfo);
+ JavaType type = ctxt.constructType(implClass);
+ idType = ctxt.getTypeFactory().findTypeParameters(type, ObjectIdGenerator.class)[0];
+ idGen = ctxt.objectIdGeneratorInstance(accessor, objectIdInfo);
+ }
+ JsonDeserializer<?> deser = ctxt.findRootValueDeserializer(idType);
+ ObjectIdReader oir = ObjectIdReader.construct(idType, objectIdInfo.getPropertyName(),
+ idGen, deser, idProp, resolver);
+ return new AbstractDeserializer(this, oir, null);
}
- ObjectIdResolver resolver = ctxt.objectIdResolverInstance(accessor, objectIdInfo);
- JavaType type = ctxt.constructType(implClass);
- JavaType idType = ctxt.getTypeFactory().findTypeParameters(type, ObjectIdGenerator.class)[0];
- SettableBeanProperty idProp = null;
- ObjectIdGenerator<?> idGen = ctxt.objectIdGeneratorInstance(accessor, objectIdInfo);
- JsonDeserializer<?> deser = ctxt.findRootValueDeserializer(idType);
- ObjectIdReader oir = ObjectIdReader.construct(idType, objectIdInfo.getPropertyName(),
- idGen, deser, idProp, resolver);
- return new AbstractDeserializer(this, oir);
}
}
- // either way, need to resolve serializer:
- return this;
+ if (_properties == null) {
+ return this;
+ }
+ // Need to ensure properties are dropped at this point, regardless
+ return new AbstractDeserializer(this, _objectIdReader, null);
}
/*
@@ -149,7 +186,17 @@
@Override
public boolean isCachable() { return true; }
-
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ /* 23-Oct-2016, tatu: Not exactly sure what to do with this; polymorphic
+ * type handling seems bit risky so for now claim it "may or may not be"
+ * possible, which does allow explicit per-type/per-property merging attempts,
+ * but avoids general-configuration merges
+ */
+ return null;
+ }
+
/**
* Overridden to return true for those instances that are
* handling value for which Object Identity handling is enabled
@@ -197,10 +244,8 @@
&& _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) {
return _deserializeFromObjectId(p, ctxt);
}
-
}
}
-
// First: support "natural" values (which are always serialized without type info!)
Object result = _deserializeIfNatural(p, ctxt);
if (result != null) {
@@ -213,7 +258,11 @@
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- return ctxt.handleMissingInstantiator(_baseType.getRawClass(), p,
+ // 16-Oct-2016, tatu: Let's pass non-null value instantiator so that we will
+ // get proper exception type; needed to establish there are no creators
+ // (since without ValueInstantiator this would not be known for certain)
+ ValueInstantiator bogus = new ValueInstantiator.Base(_baseType);
+ return ctxt.handleMissingInstantiator(_baseType.getRawClass(), bogus, p,
"abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information");
}
@@ -222,7 +271,7 @@
/* Internal methods
/**********************************************************
*/
-
+
protected Object _deserializeIfNatural(JsonParser p, DeserializationContext ctxt) throws IOException
{
/* There is a chance we might be "natural" types
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 3dd3c4b..fb97cd4 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java
@@ -1,18 +1,23 @@
package com.fasterxml.jackson.databind.deser;
-import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
+import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonCreator.Mode;
import com.fasterxml.jackson.core.JsonLocation;
+import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
+import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate;
import com.fasterxml.jackson.databind.deser.impl.CreatorCollector;
+import com.fasterxml.jackson.databind.deser.impl.JavaUtilCollectionsDeserializers;
import com.fasterxml.jackson.databind.deser.std.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.ext.OptionalHandlerFactory;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.NamedType;
@@ -38,7 +43,7 @@
{
private final static Class<?> CLASS_OBJECT = Object.class;
private final static Class<?> CLASS_STRING = String.class;
- private final static Class<?> CLASS_CHAR_BUFFER = CharSequence.class;
+ private final static Class<?> CLASS_CHAR_SEQUENCE = CharSequence.class;
private final static Class<?> CLASS_ITERABLE = Iterable.class;
private final static Class<?> CLASS_MAP_ENTRY = Map.Entry.class;
@@ -56,7 +61,10 @@
final static HashMap<String, Class<? extends Map>> _mapFallbacks =
new HashMap<String, Class<? extends Map>>();
static {
- _mapFallbacks.put(Map.class.getName(), LinkedHashMap.class);
+ @SuppressWarnings("rawtypes")
+ final Class<? extends Map> DEFAULT_MAP = LinkedHashMap.class;
+ _mapFallbacks.put(Map.class.getName(), DEFAULT_MAP);
+ _mapFallbacks.put(AbstractMap.class.getName(), DEFAULT_MAP);
_mapFallbacks.put(ConcurrentMap.class.getName(), ConcurrentHashMap.class);
_mapFallbacks.put(SortedMap.class.getName(), TreeMap.class);
@@ -73,12 +81,21 @@
final static HashMap<String, Class<? extends Collection>> _collectionFallbacks =
new HashMap<String, Class<? extends Collection>>();
static {
- _collectionFallbacks.put(Collection.class.getName(), ArrayList.class);
- _collectionFallbacks.put(List.class.getName(), ArrayList.class);
- _collectionFallbacks.put(Set.class.getName(), HashSet.class);
+ @SuppressWarnings("rawtypes")
+ final Class<? extends Collection> DEFAULT_LIST = ArrayList.class;
+ @SuppressWarnings("rawtypes")
+ final Class<? extends Collection> DEFAULT_SET = HashSet.class;
+
+ _collectionFallbacks.put(Collection.class.getName(), DEFAULT_LIST);
+ _collectionFallbacks.put(List.class.getName(), DEFAULT_LIST);
+ _collectionFallbacks.put(Set.class.getName(), DEFAULT_SET);
_collectionFallbacks.put(SortedSet.class.getName(), TreeSet.class);
_collectionFallbacks.put(Queue.class.getName(), LinkedList.class);
+ // 09-Feb-2019, tatu: How did we miss these? Related in [databind#2251] problem
+ _collectionFallbacks.put(AbstractList.class.getName(), DEFAULT_LIST);
+ _collectionFallbacks.put(AbstractSet.class.getName(), DEFAULT_SET);
+
// then JDK 1.6 types:
/* 17-May-2013, tatu: [databind#216] Should be fine to use straight Class references EXCEPT
* that some god-forsaken platforms (... looking at you, Android) do not
@@ -211,14 +228,14 @@
if (_factoryConfig.hasAbstractTypeResolvers()) {
for (AbstractTypeResolver resolver : _factoryConfig.abstractTypeResolvers()) {
JavaType concrete = resolver.findTypeMapping(config, type);
- if (concrete != null && concrete.getRawClass() != currClass) {
+ if ((concrete != null) && !concrete.hasRawClass(currClass)) {
return concrete;
}
}
}
return null;
}
-
+
/*
/**********************************************************
/* JsonDeserializerFactory impl (partial): ValueInstantiators
@@ -238,29 +255,28 @@
final DeserializationConfig config = ctxt.getConfig();
ValueInstantiator instantiator = null;
- // [JACKSON-633] Check @JsonValueInstantiator before anything else
+ // Check @JsonValueInstantiator before anything else
AnnotatedClass ac = beanDesc.getClassInfo();
Object instDef = ctxt.getAnnotationIntrospector().findValueInstantiator(ac);
if (instDef != null) {
instantiator = _valueInstantiatorInstance(config, ac, instDef);
}
if (instantiator == null) {
- /* Second: see if some of standard Jackson/JDK types might provide value
- * instantiators.
- */
+ // Second: see if some of standard Jackson/JDK types might provide value
+ // instantiators.
instantiator = _findStdValueInstantiator(config, beanDesc);
if (instantiator == null) {
instantiator = _constructDefaultValueInstantiator(ctxt, beanDesc);
}
}
-
+
// finally: anyone want to modify ValueInstantiator?
if (_factoryConfig.hasValueInstantiators()) {
for (ValueInstantiators insts : _factoryConfig.valueInstantiators()) {
instantiator = insts.findValueInstantiator(config, beanDesc, instantiator);
// let's do sanity check; easier to spot buggy handlers
if (instantiator == null) {
- ctxt.reportMappingException(
+ ctxt.reportBadTypeDefinition(beanDesc,
"Broken registered ValueInstantiators (of type %s): returned null ValueInstantiator",
insts.getClass().getName());
}
@@ -271,7 +287,8 @@
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");
+ throw new IllegalArgumentException("Argument #"+nonAnnotatedParam.getIndex()
+ +" of constructor "+ctor+" has no property name annotation; must have name when multiple-parameter constructor annotated as Creator");
}
return instantiator;
@@ -285,6 +302,19 @@
if (raw == JsonLocation.class) {
return new JsonLocationInstantiator();
}
+ // [databind#1868]: empty List/Set/Map
+ if (Collection.class.isAssignableFrom(raw)) {
+ if (Collections.EMPTY_SET.getClass() == raw) {
+ return new ConstantValueInstantiator(Collections.EMPTY_SET);
+ }
+ if (Collections.EMPTY_LIST.getClass() == raw) {
+ return new ConstantValueInstantiator(Collections.EMPTY_LIST);
+ }
+ } else if (Map.class.isAssignableFrom(raw)) {
+ if (Collections.EMPTY_MAP.getClass() == raw) {
+ return new ConstantValueInstantiator(Collections.EMPTY_MAP);
+ }
+ }
return null;
}
@@ -301,8 +331,8 @@
// need to construct suitable visibility checker:
final DeserializationConfig config = ctxt.getConfig();
- VisibilityChecker<?> vchecker = config.getDefaultVisibilityChecker();
- vchecker = intr.findAutoDetectVisibility(beanDesc.getClassInfo(), vchecker);
+ VisibilityChecker<?> vchecker = config.getDefaultVisibilityChecker(beanDesc.getBeanClass(),
+ beanDesc.getClassInfo());
/* 24-Sep-2014, tatu: Tricky part first; need to merge resolved property information
* (which has creator parameters sprinkled around) with actual creator
@@ -321,7 +351,7 @@
if (beanDesc.getType().isConcrete()) {
_addDeserializerConstructors(ctxt, beanDesc, vchecker, intr, creators, creatorDefs);
}
- return creators.constructValueInstantiator(config);
+ return creators.constructValueInstantiator(ctxt);
}
protected Map<AnnotatedWithParams,BeanPropertyDefinition[]> _findCreatorsFromProperties(DeserializationContext ctxt,
@@ -344,8 +374,9 @@
result.put(owner, defs);
} else {
if (defs[index] != null) {
- throw new IllegalStateException("Conflict: parameter #"+index+" of "+owner
- +" bound to more than one property; "+defs[index]+" vs "+propDef);
+ ctxt.reportBadTypeDefinition(beanDesc,
+"Conflict: parameter #%d of %s bound to more than one property; %s vs %s",
+index, owner, defs[index], propDef);
}
}
defs[index] = propDef;
@@ -391,22 +422,18 @@
config.canOverrideAccessModifiers());
}
- protected void _addDeserializerConstructors
- (DeserializationContext ctxt, BeanDescription beanDesc, VisibilityChecker<?> vchecker,
+ /*
+ /**********************************************************
+ /* Creator introspection
+ /**********************************************************
+ */
+
+ protected void _addDeserializerConstructors(DeserializationContext ctxt,
+ BeanDescription beanDesc, VisibilityChecker<?> vchecker,
AnnotationIntrospector intr, CreatorCollector creators,
Map<AnnotatedWithParams,BeanPropertyDefinition[]> creatorParams)
- throws JsonMappingException
+ throws JsonMappingException
{
- // First things first: the "default constructor" (zero-arg
- // constructor; whether implicit or explicit) is NOT included
- // in list of constructors, so needs to be handled separately.
- AnnotatedConstructor defaultCtor = beanDesc.findDefaultConstructor();
- if (defaultCtor != null) {
- if (!creators.hasDefaultCreator() || intr.hasCreatorAnnotation(defaultCtor)) {
- creators.setDefaultCreator(defaultCtor);
- }
- }
-
// 25-Jan-2017, tatu: As per [databind#1501], [databind#1502], [databind#1503], best
// for now to skip attempts at using anything but no-args constructor (see
// `InnerClassProperty` construction for that)
@@ -416,33 +443,74 @@
return;
}
- // may need to keep track for [#725]
- List<AnnotatedConstructor> implicitCtors = null;
+ // First things first: the "default constructor" (zero-arg
+ // constructor; whether implicit or explicit) is NOT included
+ // in list of constructors, so needs to be handled separately.
+ AnnotatedConstructor defaultCtor = beanDesc.findDefaultConstructor();
+ if (defaultCtor != null) {
+ if (!creators.hasDefaultCreator() || _hasCreatorAnnotation(ctxt, defaultCtor)) {
+ creators.setDefaultCreator(defaultCtor);
+ }
+ }
+ // 21-Sep-2017, tatu: First let's handle explicitly annotated ones
+ List<CreatorCandidate> nonAnnotated = new LinkedList<>();
+ int explCount = 0;
for (AnnotatedConstructor ctor : beanDesc.getConstructors()) {
- final boolean isCreator = intr.hasCreatorAnnotation(ctor);
- BeanPropertyDefinition[] propDefs = creatorParams.get(ctor);
- final int argCount = ctor.getParameterCount();
+ JsonCreator.Mode creatorMode = intr.findCreatorAnnotation(ctxt.getConfig(), ctor);
+ if (Mode.DISABLED == creatorMode) {
+ continue;
+ }
+ if (creatorMode == null) {
+ // let's check Visibility here, to avoid further processing for non-visible?
+ if (vchecker.isCreatorVisible(ctor)) {
+ nonAnnotated.add(CreatorCandidate.construct(intr, ctor, creatorParams.get(ctor)));
+ }
+ continue;
+ }
+ switch (creatorMode) {
+ case DELEGATING:
+ _addExplicitDelegatingCreator(ctxt, beanDesc, creators,
+ CreatorCandidate.construct(intr, ctor, null));
+ break;
+ case PROPERTIES:
+ _addExplicitPropertyCreator(ctxt, beanDesc, creators,
+ CreatorCandidate.construct(intr, ctor, creatorParams.get(ctor)));
+ break;
+ default:
+ _addExplicitAnyCreator(ctxt, beanDesc, creators,
+ CreatorCandidate.construct(intr, ctor, creatorParams.get(ctor)));
+ break;
+ }
+ ++explCount;
+ }
+ // And only if and when those handled, consider potentially visible ones
+ if (explCount > 0) { // TODO: split method into two since we could have expl factories
+ return;
+ }
+ List<AnnotatedWithParams> implicitCtors = null;
+ for (CreatorCandidate candidate : nonAnnotated) {
+ final int argCount = candidate.paramCount();
+ final AnnotatedWithParams ctor = candidate.creator();
// some single-arg factory methods (String, number) are auto-detected
if (argCount == 1) {
- BeanPropertyDefinition argDef = (propDefs == null) ? null : propDefs[0];
- boolean useProps = _checkIfCreatorPropertyBased(intr, ctor, argDef);
+ BeanPropertyDefinition propDef = candidate.propertyDef(0);
+ boolean useProps = _checkIfCreatorPropertyBased(intr, ctor, propDef);
if (useProps) {
SettableBeanProperty[] properties = new SettableBeanProperty[1];
- PropertyName name = (argDef == null) ? null : argDef.getFullName();
- AnnotatedParameter arg = ctor.getParameter(0);
- properties[0] = constructCreatorProperty(ctxt, beanDesc, name, 0, arg,
- intr.findInjectableValueId(arg));
- creators.addPropertyCreator(ctor, isCreator, properties);
+ PropertyName name = candidate.paramName(0);
+ properties[0] = constructCreatorProperty(ctxt, beanDesc, name, 0,
+ candidate.parameter(0), candidate.injection(0));
+ creators.addPropertyCreator(ctor, false, properties);
} else {
- /*boolean added = */ _handleSingleArgumentConstructor(ctxt, beanDesc, vchecker, intr, creators,
- ctor, isCreator,
+ /*boolean added = */ _handleSingleArgumentCreator(creators,
+ ctor, false,
vchecker.isCreatorVisible(ctor));
// one more thing: sever link to creator property, to avoid possible later
// problems with "unresolved" constructor property
- if (argDef != null) {
- ((POJOPropertyBuilder) argDef).removeConstructors();
+ if (propDef != null) {
+ ((POJOPropertyBuilder) propDef).removeConstructors();
}
}
// regardless, fully handled
@@ -453,7 +521,7 @@
// 14-Mar-2015, tatu (2.6): Or, as per [#725], implicit names will also
// do, with some constraints. But that will require bit post processing...
- AnnotatedParameter nonAnnotatedParam = null;
+ int nonAnnotatedParamIndex = -1;
SettableBeanProperty[] properties = new SettableBeanProperty[argCount];
int explicitNameCount = 0;
int implicitWithCreatorCount = 0;
@@ -461,8 +529,8 @@
for (int i = 0; i < argCount; ++i) {
final AnnotatedParameter param = ctor.getParameter(i);
- BeanPropertyDefinition propDef = (propDefs == null) ? null : propDefs[i];
- Object injectId = intr.findInjectableValueId(param);
+ BeanPropertyDefinition propDef = candidate.propertyDef(i);
+ JacksonInject.Value injectId = intr.findInjectableValue(param);
final PropertyName name = (propDef == null) ? null : propDef.getFullName();
if (propDef != null && propDef.isExplicitlyNamed()) {
@@ -477,56 +545,61 @@
}
NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param);
if (unwrapper != null) {
+ _reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
+ /*
properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
++explicitNameCount;
+ */
continue;
}
// One more thing: implicit names are ok iff ctor has creator annotation
+ /*
if (isCreator && (name != null && !name.isEmpty())) {
++implicitWithCreatorCount;
properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId);
continue;
}
- if (nonAnnotatedParam == null) {
- nonAnnotatedParam = param;
+ */
+ if (nonAnnotatedParamIndex < 0) {
+ nonAnnotatedParamIndex = i;
}
}
final int namedCount = explicitNameCount + implicitWithCreatorCount;
// Ok: if named or injectable, we have more work to do
- if (isCreator || (explicitNameCount > 0) || (injectCount > 0)) {
+ if ((explicitNameCount > 0) || (injectCount > 0)) {
// simple case; everything covered:
if ((namedCount + injectCount) == argCount) {
- creators.addPropertyCreator(ctor, isCreator, properties);
+ creators.addPropertyCreator(ctor, false, properties);
continue;
}
if ((explicitNameCount == 0) && ((injectCount + 1) == argCount)) {
// Secondary: all but one injectable, one un-annotated (un-named)
- creators.addDelegatingCreator(ctor, isCreator, properties);
+ creators.addDelegatingCreator(ctor, false, properties, 0);
continue;
}
// otherwise, epic fail?
// 16-Mar-2015, tatu: due to [#725], need to be more permissive. For now let's
// only report problem if there's no implicit name
- PropertyName impl = _findImplicitParamName(nonAnnotatedParam, intr);
+ PropertyName impl = candidate.findImplicitParamName(nonAnnotatedParamIndex);
if (impl == null || impl.isEmpty()) {
// Let's consider non-static inner class as a special case...
- int ix = nonAnnotatedParam.getIndex();
// 25-Jan-2017, tatu: Non-static inner classes skipped altogether, now
/*
- if ((ix == 0) && isNonStaticInnerClass) {
+ if ((nonAnnotatedParamIndex == 0) && isNonStaticInnerClass) {
throw new IllegalArgumentException("Non-static inner classes like "
- +ctor.getDeclaringClass().getName()+" can not use @JsonCreator for constructors");
+ +ctor.getDeclaringClass().getName()+" cannot use @JsonCreator for constructors");
}
*/
- throw new IllegalArgumentException("Argument #"+ix
- +" of constructor "+ctor+" has no property name annotation; must have name when multiple-parameter constructor annotated as Creator");
+ ctxt.reportBadTypeDefinition(beanDesc,
+"Argument #%d of constructor %s has no property name annotation; must have name when multiple-parameter constructor annotated as Creator",
+nonAnnotatedParamIndex, ctor);
}
}
// [#725]: as a fallback, all-implicit names may work as well
if (!creators.hasDefaultCreator()) {
if (implicitCtors == null) {
- implicitCtors = new LinkedList<AnnotatedConstructor>();
+ implicitCtors = new LinkedList<>();
}
implicitCtors.add(ctor);
}
@@ -540,12 +613,184 @@
}
}
- protected void _checkImplicitlyNamedConstructors(DeserializationContext ctxt,
+ /**
+ * Helper method called when there is the explicit "is-creator" with mode of "delegating"
+ *
+ * @since 2.9.2
+ */
+ protected void _addExplicitDelegatingCreator(DeserializationContext ctxt,
+ BeanDescription beanDesc, CreatorCollector creators,
+ CreatorCandidate candidate)
+ throws JsonMappingException
+ {
+ // Somewhat simple: find injectable values, if any, ensure there is one
+ // and just one delegated argument; report violations if any
+
+ int ix = -1;
+ final int argCount = candidate.paramCount();
+ SettableBeanProperty[] properties = new SettableBeanProperty[argCount];
+ for (int i = 0; i < argCount; ++i) {
+ AnnotatedParameter param = candidate.parameter(i);
+ JacksonInject.Value injectId = candidate.injection(i);
+ if (injectId != null) {
+ properties[i] = constructCreatorProperty(ctxt, beanDesc, null, i, param, injectId);
+ continue;
+ }
+ if (ix < 0) {
+ ix = i;
+ continue;
+ }
+ // Illegal to have more than one value to delegate to
+ ctxt.reportBadTypeDefinition(beanDesc,
+ "More than one argument (#%d and #%d) left as delegating for Creator %s: only one allowed",
+ ix, i, candidate);
+ }
+ // Also, let's require that one Delegating argument does eixt
+ if (ix < 0) {
+ ctxt.reportBadTypeDefinition(beanDesc,
+ "No argument left as delegating for Creator %s: exactly one required", candidate);
+ }
+ // 17-Jan-2018, tatu: as per [databind#1853] need to ensure we will distinguish
+ // "well-known" single-arg variants (String, int/long, boolean) from "generic" delegating...
+ if (argCount == 1) {
+ _handleSingleArgumentCreator(creators, candidate.creator(), true, true);
+ // one more thing: sever link to creator property, to avoid possible later
+ // problems with "unresolved" constructor property
+ BeanPropertyDefinition paramDef = candidate.propertyDef(0);
+ if (paramDef != null) {
+ ((POJOPropertyBuilder) paramDef).removeConstructors();
+ }
+ return;
+ }
+ creators.addDelegatingCreator(candidate.creator(), true, properties, ix);
+ }
+
+ /**
+ * Helper method called when there is the explicit "is-creator" with mode of "properties-based"
+ *
+ * @since 2.9.2
+ */
+ protected void _addExplicitPropertyCreator(DeserializationContext ctxt,
+ BeanDescription beanDesc, CreatorCollector creators,
+ CreatorCandidate candidate)
+ throws JsonMappingException
+ {
+ final int paramCount = candidate.paramCount();
+ SettableBeanProperty[] properties = new SettableBeanProperty[paramCount];
+
+ for (int i = 0; i < paramCount; ++i) {
+ JacksonInject.Value injectId = candidate.injection(i);
+ AnnotatedParameter param = candidate.parameter(i);
+ PropertyName name = candidate.paramName(i);
+ if (name == null) {
+ // 21-Sep-2017, tatu: Looks like we want to block accidental use of Unwrapped,
+ // as that will not work with Creators well at all
+ NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(param);
+ if (unwrapper != null) {
+ _reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
+ /*
+ properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
+ ++explicitNameCount;
+ */
+ }
+ name = candidate.findImplicitParamName(i);
+ // Must be injectable or have name; without either won't work
+ if ((name == null) && (injectId == null)) {
+ ctxt.reportBadTypeDefinition(beanDesc,
+"Argument #%d has no property name, is not Injectable: can not use as Creator %s", i, candidate);
+ }
+ }
+ properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId);
+ }
+ creators.addPropertyCreator(candidate.creator(), true, properties);
+ }
+
+ /**
+ * Helper method called when there is the explicit "is-creator", but no mode declaration.
+ *
+ * @since 2.9.2
+ */
+ protected void _addExplicitAnyCreator(DeserializationContext ctxt,
+ BeanDescription beanDesc, CreatorCollector creators,
+ CreatorCandidate candidate)
+ throws JsonMappingException
+ {
+ // Looks like there's bit of magic regarding 1-parameter creators; others simpler:
+ if (1 != candidate.paramCount()) {
+ // Ok: for delegates, we want one and exactly one parameter without
+ // injection AND without name
+ int oneNotInjected = candidate.findOnlyParamWithoutInjection();
+ if (oneNotInjected >= 0) {
+ // getting close; but most not have name
+ if (candidate.paramName(oneNotInjected) == null) {
+ _addExplicitDelegatingCreator(ctxt, beanDesc, creators, candidate);
+ return;
+ }
+ }
+ _addExplicitPropertyCreator(ctxt, beanDesc, creators, candidate);
+ return;
+ }
+ AnnotatedParameter param = candidate.parameter(0);
+ JacksonInject.Value injectId = candidate.injection(0);
+ PropertyName paramName = candidate.explicitParamName(0);
+ BeanPropertyDefinition paramDef = candidate.propertyDef(0);
+
+ // If there's injection or explicit name, should be properties-based
+ boolean useProps = (paramName != null) || (injectId != null);
+ if (!useProps && (paramDef != null)) {
+ // One more thing: if implicit name matches property with a getter
+ // or field, we'll consider it property-based as well
+
+ // 25-May-2018, tatu: as per [databind#2051], looks like we have to get
+ // not implicit name, but name with possible strategy-based-rename
+// paramName = candidate.findImplicitParamName(0);
+ paramName = candidate.paramName(0);
+ useProps = (paramName != null) && paramDef.couldSerialize();
+ }
+ if (useProps) {
+ SettableBeanProperty[] properties = new SettableBeanProperty[] {
+ constructCreatorProperty(ctxt, beanDesc, paramName, 0, param, injectId)
+ };
+ creators.addPropertyCreator(candidate.creator(), true, properties);
+ return;
+ }
+ _handleSingleArgumentCreator(creators, candidate.creator(), true, true);
+
+ // one more thing: sever link to creator property, to avoid possible later
+ // problems with "unresolved" constructor property
+ if (paramDef != null) {
+ ((POJOPropertyBuilder) paramDef).removeConstructors();
+ }
+ }
+
+ private boolean _checkIfCreatorPropertyBased(AnnotationIntrospector intr,
+ AnnotatedWithParams creator, BeanPropertyDefinition propDef)
+ {
+ // If explicit name, or inject id, property-based
+ if (((propDef != null) && propDef.isExplicitlyNamed())
+ || (intr.findInjectableValue(creator.getParameter(0)) != null)) {
+ return true;
+ }
+ if (propDef != null) {
+ // One more thing: if implicit name matches property with a getter
+ // or field, we'll consider it property-based as well
+ String implName = propDef.getName();
+ if (implName != null && !implName.isEmpty()) {
+ if (propDef.couldSerialize()) {
+ return true;
+ }
+ }
+ }
+ // in absence of everything else, default to delegating
+ return false;
+ }
+
+ private void _checkImplicitlyNamedConstructors(DeserializationContext ctxt,
BeanDescription beanDesc, VisibilityChecker<?> vchecker,
AnnotationIntrospector intr, CreatorCollector creators,
- List<AnnotatedConstructor> implicitCtors) throws JsonMappingException
+ List<AnnotatedWithParams> implicitCtors) throws JsonMappingException
{
- AnnotatedConstructor found = null;
+ AnnotatedWithParams found = null;
SettableBeanProperty[] foundProps = null;
// Further checks: (a) must have names for all parameters, (b) only one visible
@@ -553,7 +798,7 @@
// `@JsonCreator` (or equivalent) annotation, we need to do bit more re-inspection...
main_loop:
- for (AnnotatedConstructor ctor : implicitCtors) {
+ for (AnnotatedWithParams ctor : implicitCtors) {
if (!vchecker.isCreatorVisible(ctor)) {
continue;
}
@@ -571,7 +816,7 @@
properties[i] = constructCreatorProperty(ctxt, beanDesc, name, param.getIndex(),
param, /*injectId*/ null);
}
- if (found != null) { // only one allowed
+ if (found != null) { // only one allowed; but multiple not an error
found = null;
break;
}
@@ -594,45 +839,160 @@
}
}
- protected boolean _checkIfCreatorPropertyBased(AnnotationIntrospector intr,
- AnnotatedWithParams creator, BeanPropertyDefinition propDef)
+ protected void _addDeserializerFactoryMethods
+ (DeserializationContext ctxt, BeanDescription beanDesc, VisibilityChecker<?> vchecker,
+ AnnotationIntrospector intr, CreatorCollector creators,
+ Map<AnnotatedWithParams,BeanPropertyDefinition[]> creatorParams)
+ throws JsonMappingException
{
- JsonCreator.Mode mode = intr.findCreatorBinding(creator);
+ List<CreatorCandidate> nonAnnotated = new LinkedList<>();
+ int explCount = 0;
- if (mode == JsonCreator.Mode.PROPERTIES) {
- return true;
+ // 21-Sep-2017, tatu: First let's handle explicitly annotated ones
+ for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {
+ JsonCreator.Mode creatorMode = intr.findCreatorAnnotation(ctxt.getConfig(), factory);
+ final int argCount = factory.getParameterCount();
+ if (creatorMode == null) {
+ // Only potentially accept 1-argument factory methods
+ if ((argCount == 1) && vchecker.isCreatorVisible(factory)) {
+ nonAnnotated.add(CreatorCandidate.construct(intr, factory, null));
+ }
+ continue;
+ }
+ if (creatorMode == Mode.DISABLED) {
+ continue;
+ }
+
+ // zero-arg method factory methods fine, as long as explicit
+ if (argCount == 0) {
+ creators.setDefaultCreator(factory);
+ continue;
+ }
+
+ switch (creatorMode) {
+ case DELEGATING:
+ _addExplicitDelegatingCreator(ctxt, beanDesc, creators,
+ CreatorCandidate.construct(intr, factory, null));
+ break;
+ case PROPERTIES:
+ _addExplicitPropertyCreator(ctxt, beanDesc, creators,
+ CreatorCandidate.construct(intr, factory, creatorParams.get(factory)));
+ break;
+ case DEFAULT:
+ default:
+ _addExplicitAnyCreator(ctxt, beanDesc, creators,
+ CreatorCandidate.construct(intr, factory, creatorParams.get(factory)));
+ break;
+ }
+ ++explCount;
}
- if (mode == JsonCreator.Mode.DELEGATING) {
- return false;
+ // And only if and when those handled, consider potentially visible ones
+ if (explCount > 0) { // TODO: split method into two since we could have expl factories
+ return;
}
- // If explicit name, or inject id, property-based
- if (((propDef != null) && propDef.isExplicitlyNamed())
- || (intr.findInjectableValueId(creator.getParameter(0)) != null)) {
- return true;
- }
- if (propDef != null) {
- // One more thing: if implicit name matches property with a getter
- // or field, we'll consider it property-based as well
- String implName = propDef.getName();
- if (implName != null && !implName.isEmpty()) {
- if (propDef.couldSerialize()) {
- return true;
+ // And then implicitly found
+ for (CreatorCandidate candidate : nonAnnotated) {
+ final int argCount = candidate.paramCount();
+ AnnotatedWithParams factory = candidate.creator();
+ final BeanPropertyDefinition[] propDefs = creatorParams.get(factory);
+ // some single-arg factory methods (String, number) are auto-detected
+ if (argCount != 1) {
+ continue; // 2 and more args? Must be explicit, handled earlier
+ }
+ BeanPropertyDefinition argDef = candidate.propertyDef(0);
+ boolean useProps = _checkIfCreatorPropertyBased(intr, factory, argDef);
+ if (!useProps) { // not property based but delegating
+ /*boolean added=*/ _handleSingleArgumentCreator(creators,
+ factory, false, vchecker.isCreatorVisible(factory));
+ // 23-Sep-2016, tatu: [databind#1383]: Need to also sever link to avoid possible
+ // later problems with "unresolved" constructor property
+ if (argDef != null) {
+ ((POJOPropertyBuilder) argDef).removeConstructors();
+ }
+ continue;
+ }
+ AnnotatedParameter nonAnnotatedParam = null;
+ SettableBeanProperty[] properties = new SettableBeanProperty[argCount];
+ int implicitNameCount = 0;
+ int explicitNameCount = 0;
+ int injectCount = 0;
+
+ for (int i = 0; i < argCount; ++i) {
+ final AnnotatedParameter param = factory.getParameter(i);
+ BeanPropertyDefinition propDef = (propDefs == null) ? null : propDefs[i];
+ JacksonInject.Value injectable = intr.findInjectableValue(param);
+ final PropertyName name = (propDef == null) ? null : propDef.getFullName();
+
+ if (propDef != null && propDef.isExplicitlyNamed()) {
+ ++explicitNameCount;
+ properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable);
+ continue;
+ }
+ if (injectable != null) {
+ ++injectCount;
+ properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable);
+ continue;
+ }
+ NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param);
+ if (unwrapper != null) {
+ _reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
+ /*
+ properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
+ ++implicitNameCount;
+ */
+ continue;
+ }
+ // One more thing: implicit names are ok iff ctor has creator annotation
+ /*
+ if (isCreator) {
+ if (name != null && !name.isEmpty()) {
+ ++implicitNameCount;
+ properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable);
+ continue;
+ }
+ }
+ */
+ /* 25-Sep-2014, tatu: Actually, we may end up "losing" naming due to higher-priority constructor
+ * (see TestCreators#testConstructorCreator() test). And just to avoid running into that problem,
+ * let's add one more work around
+ */
+ /*
+ PropertyName name2 = _findExplicitParamName(param, intr);
+ if (name2 != null && !name2.isEmpty()) {
+ // Hmmh. Ok, fine. So what are we to do with it... ?
+ // For now... skip. May need to revisit this, should this become problematic
+ continue main_loop;
+ }
+ */
+ if (nonAnnotatedParam == null) {
+ nonAnnotatedParam = param;
+ }
+ }
+ final int namedCount = explicitNameCount + implicitNameCount;
+
+ // Ok: if named or injectable, we have more work to do
+ if (explicitNameCount > 0 || injectCount > 0) {
+ // simple case; everything covered:
+ if ((namedCount + injectCount) == argCount) {
+ creators.addPropertyCreator(factory, false, properties);
+ } else if ((explicitNameCount == 0) && ((injectCount + 1) == argCount)) {
+ // secondary: all but one injectable, one un-annotated (un-named)
+ creators.addDelegatingCreator(factory, false, properties, 0);
+ } else { // otherwise, epic fail
+ ctxt.reportBadTypeDefinition(beanDesc,
+"Argument #%d of factory method %s has no property name annotation; must have name when multiple-parameter constructor annotated as Creator",
+ nonAnnotatedParam.getIndex(), factory);
}
}
}
- // in absence of everything else, default to delegating
- return false;
}
-
- protected boolean _handleSingleArgumentConstructor(DeserializationContext ctxt,
- BeanDescription beanDesc, VisibilityChecker<?> vchecker,
- AnnotationIntrospector intr, CreatorCollector creators,
- AnnotatedConstructor ctor, boolean isCreator, boolean isVisible)
- throws JsonMappingException
+
+ protected boolean _handleSingleArgumentCreator(CreatorCollector creators,
+ AnnotatedWithParams ctor, boolean isCreator, boolean isVisible)
{
// otherwise either 'simple' number, String, or general delegate:
Class<?> type = ctor.getRawParameterType(0);
- if (type == String.class || type == CharSequence.class) {
+ if (type == String.class || type == CLASS_CHAR_SEQUENCE) {
if (isCreator || isVisible) {
creators.addStringCreator(ctor, isCreator);
}
@@ -664,166 +1024,21 @@
}
// Delegating Creator ok iff it has @JsonCreator (etc)
if (isCreator) {
- creators.addDelegatingCreator(ctor, isCreator, null);
+ creators.addDelegatingCreator(ctor, isCreator, null, 0);
return true;
}
return false;
}
- protected void _addDeserializerFactoryMethods
- (DeserializationContext ctxt, BeanDescription beanDesc, VisibilityChecker<?> vchecker,
- AnnotationIntrospector intr, CreatorCollector creators,
- Map<AnnotatedWithParams,BeanPropertyDefinition[]> creatorParams)
+ // 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
+ // of unwrapped values through creator properties, so fail fast
+ protected void _reportUnwrappedCreatorProperty(DeserializationContext ctxt,
+ BeanDescription beanDesc, AnnotatedParameter param)
throws JsonMappingException
{
- final DeserializationConfig config = ctxt.getConfig();
- for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {
- final boolean isCreator = intr.hasCreatorAnnotation(factory);
- final int argCount = factory.getParameterCount();
- // zero-arg methods must be annotated; if so, are "default creators" [JACKSON-850]
- if (argCount == 0) {
- if (isCreator) {
- creators.setDefaultCreator(factory);
- }
- continue;
- }
-
- final BeanPropertyDefinition[] propDefs = creatorParams.get(factory);
- // some single-arg factory methods (String, number) are auto-detected
- if (argCount == 1) {
- BeanPropertyDefinition argDef = (propDefs == null) ? null : propDefs[0];
- boolean useProps = _checkIfCreatorPropertyBased(intr, factory, argDef);
- if (!useProps) { // not property based but delegating
- /*boolean added=*/ _handleSingleArgumentFactory(config, beanDesc, vchecker, intr, creators,
- factory, isCreator);
- // 23-Sep-2016, tatu: [databind#1383]: Need to also sever link to avoid possible
- // later problems with "unresolved" constructor property
- if (argDef != null) {
- ((POJOPropertyBuilder) argDef).removeConstructors();
- }
- continue;
- }
- // fall through if there's name
- } else {
- // more than 2 args, must have @JsonCreator
- if (!isCreator) {
- continue;
- }
- }
- // 1 or more args; all params must have name annotations
- AnnotatedParameter nonAnnotatedParam = null;
- SettableBeanProperty[] properties = new SettableBeanProperty[argCount];
- int implicitNameCount = 0;
- int explicitNameCount = 0;
- int injectCount = 0;
-
- for (int i = 0; i < argCount; ++i) {
- final AnnotatedParameter param = factory.getParameter(i);
- BeanPropertyDefinition propDef = (propDefs == null) ? null : propDefs[i];
- Object injectId = intr.findInjectableValueId(param);
- final PropertyName name = (propDef == null) ? null : propDef.getFullName();
-
- if (propDef != null && propDef.isExplicitlyNamed()) {
- ++explicitNameCount;
- properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId);
- continue;
- }
- if (injectId != null) {
- ++injectCount;
- properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId);
- continue;
- }
- NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param);
- if (unwrapper != null) {
- properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
- ++implicitNameCount;
- continue;
- }
- // One more thing: implicit names are ok iff ctor has creator annotation
- if (isCreator) {
- if (name != null && !name.isEmpty()) {
- ++implicitNameCount;
- properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId);
- continue;
- }
- }
- /* 25-Sep-2014, tatu: Actually, we may end up "losing" naming due to higher-priority constructor
- * (see TestCreators#testConstructorCreator() test). And just to avoid running into that problem,
- * let's add one more work around
- */
- /*
- PropertyName name2 = _findExplicitParamName(param, intr);
- if (name2 != null && !name2.isEmpty()) {
- // Hmmh. Ok, fine. So what are we to do with it... ?
- // For now... skip. May need to revisit this, should this become problematic
- continue main_loop;
- }
- */
- if (nonAnnotatedParam == null) {
- nonAnnotatedParam = param;
- }
- }
- final int namedCount = explicitNameCount + implicitNameCount;
-
- // Ok: if named or injectable, we have more work to do
- if (isCreator || explicitNameCount > 0 || injectCount > 0) {
- // simple case; everything covered:
- if ((namedCount + injectCount) == argCount) {
- creators.addPropertyCreator(factory, isCreator, properties);
- } else if ((explicitNameCount == 0) && ((injectCount + 1) == argCount)) {
- // [712] secondary: all but one injectable, one un-annotated (un-named)
- creators.addDelegatingCreator(factory, isCreator, properties);
- } else { // otherwise, epic fail
- throw new IllegalArgumentException("Argument #"+nonAnnotatedParam.getIndex()
- +" of factory method "+factory+" has no property name annotation; must have name when multiple-parameter constructor annotated as Creator");
- }
- }
- }
- }
-
- protected boolean _handleSingleArgumentFactory(DeserializationConfig config,
- BeanDescription beanDesc, VisibilityChecker<?> vchecker,
- AnnotationIntrospector intr, CreatorCollector creators,
- AnnotatedMethod factory, boolean isCreator)
- throws JsonMappingException
- {
- Class<?> type = factory.getRawParameterType(0);
-
- if (type == String.class || type == CharSequence.class) {
- if (isCreator || vchecker.isCreatorVisible(factory)) {
- creators.addStringCreator(factory, isCreator);
- }
- return true;
- }
- if (type == int.class || type == Integer.class) {
- if (isCreator || vchecker.isCreatorVisible(factory)) {
- creators.addIntCreator(factory, isCreator);
- }
- return true;
- }
- if (type == long.class || type == Long.class) {
- if (isCreator || vchecker.isCreatorVisible(factory)) {
- creators.addLongCreator(factory, isCreator);
- }
- return true;
- }
- if (type == double.class || type == Double.class) {
- if (isCreator || vchecker.isCreatorVisible(factory)) {
- creators.addDoubleCreator(factory, isCreator);
- }
- return true;
- }
- if (type == boolean.class || type == Boolean.class) {
- if (isCreator || vchecker.isCreatorVisible(factory)) {
- creators.addBooleanCreator(factory, isCreator);
- }
- return true;
- }
- if (isCreator) {
- creators.addDelegatingCreator(factory, isCreator, null);
- return true;
- }
- return false;
+ ctxt.reportBadDefinition(beanDesc.getType(), String.format(
+ "Cannot define Creator parameter %d as `@JsonUnwrapped`: combination not yet supported",
+ param.getIndex()));
}
/**
@@ -834,7 +1049,7 @@
protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt,
BeanDescription beanDesc, PropertyName name, int index,
AnnotatedParameter param,
- Object injectableValueId)
+ JacksonInject.Value injectable)
throws JsonMappingException
{
final DeserializationConfig config = ctxt.getConfig();
@@ -853,8 +1068,7 @@
}
JavaType type = resolveMemberAndTypeAnnotations(ctxt, param, param.getType());
BeanProperty.Std property = new BeanProperty.Std(name, type,
- intr.findWrapperName(param),
- beanDesc.getClassAnnotations(), param, metadata);
+ intr.findWrapperName(param), param, metadata);
// Type deserializer: either comes from property (and already resolved)
TypeDeserializer typeDeser = (TypeDeserializer) type.getTypeHandler();
// or if not, based on type being referenced:
@@ -863,6 +1077,9 @@
}
// Note: contextualization of typeDeser _should_ occur in constructor of CreatorProperty
// so it is not called directly here
+
+ Object injectableValueId = (injectable == null) ? null : injectable.getId();
+
SettableBeanProperty prop = new CreatorProperty(name, type, property.getWrapperName(),
typeDeser, beanDesc.getClassAnnotations(), param, index, injectableValueId,
metadata);
@@ -878,7 +1095,7 @@
return prop;
}
- protected PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr)
+ private PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr)
{
if (param != null && intr != null) {
PropertyName name = intr.findNameForDeserialization(param);
@@ -896,34 +1113,6 @@
return null;
}
- protected PropertyName _findImplicitParamName(AnnotatedParameter param, AnnotationIntrospector intr)
- {
- String str = intr.findImplicitPropertyName(param);
- if (str != null && !str.isEmpty()) {
- return PropertyName.construct(str);
- }
- return null;
- }
-
- @Deprecated // in 2.6, remove from 2.7
- protected PropertyName _findExplicitParamName(AnnotatedParameter param, AnnotationIntrospector intr)
- {
- if (param != null && intr != null) {
- return intr.findNameForDeserialization(param);
- }
- return null;
- }
-
- @Deprecated // in 2.6, remove from 2.7
- protected boolean _hasExplicitParamName(AnnotatedParameter param, AnnotationIntrospector intr)
- {
- if (param != null && intr != null) {
- PropertyName n = intr.findNameForDeserialization(param);
- return (n != null) && n.hasSimpleName();
- }
- return false;
- }
-
/*
/**********************************************************
/* JsonDeserializerFactory impl: array deserializers
@@ -954,7 +1143,8 @@
Class<?> raw = elemType.getRawClass();
if (elemType.isPrimitive()) {
return PrimitiveArrayDeserializers.forType(raw);
- } else if (raw == String.class) {
+ }
+ if (raw == String.class) {
return StringArrayDeserializer.instance;
}
}
@@ -1019,7 +1209,7 @@
if (implType == null) {
// [databind#292]: Actually, may be fine, but only if polymorphich deser enabled
if (type.getTypeHandler() == null) {
- throw new IllegalArgumentException("Can not find a deserializer for non-concrete Collection type "+type);
+ throw new IllegalArgumentException("Cannot find a deserializer for non-concrete Collection type "+type);
}
deser = AbstractDeserializer.constructForNonPOJO(beanDesc);
} else {
@@ -1032,12 +1222,17 @@
ValueInstantiator inst = findValueInstantiator(ctxt, beanDesc);
if (!inst.canCreateUsingDefault()) {
// [databind#161]: No default constructor for ArrayBlockingQueue...
- if (type.getRawClass() == ArrayBlockingQueue.class) {
+ if (type.hasRawClass(ArrayBlockingQueue.class)) {
return new ArrayBlockingQueueDeserializer(type, contentDeser, contentTypeDeser, inst);
}
+ // 10-Jan-2017, tatu: `java.util.Collections` types need help:
+ deser = JavaUtilCollectionsDeserializers.findForCollection(ctxt, type);
+ if (deser != null) {
+ return deser;
+ }
}
// Can use more optimal deserializer if content type is String, so:
- if (contentType.getRawClass() == String.class) {
+ if (contentType.hasRawClass(String.class)) {
// no value type deserializer because Strings are one of natural/native types:
deser = new StringCollectionDeserializer(type, contentDeser, inst);
} else {
@@ -1130,11 +1325,21 @@
// Value handling is identical for all, but EnumMap requires special handling for keys
Class<?> mapClass = type.getRawClass();
if (EnumMap.class.isAssignableFrom(mapClass)) {
+ ValueInstantiator inst;
+
+ // 06-Mar-2017, tatu: Should only need to check ValueInstantiator for
+ // custom sub-classes, see [databind#1544]
+ if (mapClass == EnumMap.class) {
+ inst = null;
+ } else {
+ inst = findValueInstantiator(ctxt, beanDesc);
+ }
Class<?> kt = keyType.getRawClass();
if (kt == null || !kt.isEnum()) {
- throw new IllegalArgumentException("Can not construct EnumMap; generic (key) type not available");
+ throw new IllegalArgumentException("Cannot construct EnumMap; generic (key) type not available");
}
- deser = new EnumMapDeserializer(type, null, contentDeser, contentTypeDeser);
+ deser = new EnumMapDeserializer(type, inst, null,
+ contentDeser, contentTypeDeser, null);
}
// Otherwise, generic handler works ok.
@@ -1160,10 +1365,16 @@
} else {
// [databind#292]: Actually, may be fine, but only if polymorphic deser enabled
if (type.getTypeHandler() == null) {
- throw new IllegalArgumentException("Can not find a deserializer for non-concrete Map type "+type);
+ throw new IllegalArgumentException("Cannot find a deserializer for non-concrete Map type "+type);
}
deser = AbstractDeserializer.constructForNonPOJO(beanDesc);
}
+ } else {
+ // 10-Jan-2017, tatu: `java.util.Collections` types need help:
+ deser = JavaUtilCollectionsDeserializers.findForMap(ctxt, type);
+ if (deser != null) {
+ return deser;
+ }
}
if (deser == null) {
ValueInstantiator inst = findValueInstantiator(ctxt, beanDesc);
@@ -1254,7 +1465,7 @@
: valueInstantiator.getFromObjectArguments(ctxt.getConfig());
// May have @JsonCreator for static factory method:
for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {
- if (ctxt.getAnnotationIntrospector().hasCreatorAnnotation(factory)) {
+ if (_hasCreatorAnnotation(ctxt, factory)) {
if (factory.getParameterCount() == 0) { // [databind#960]
deser = EnumDeserializer.deserializerForNoArgsCreator(config, enumClass, factory);
break;
@@ -1271,7 +1482,8 @@
// Need to consider @JsonValue if one found
if (deser == null) {
deser = new EnumDeserializer(constructEnumResolver(enumClass,
- config, beanDesc.findJsonValueMethod()));
+ config, beanDesc.findJsonValueAccessor()),
+ config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
}
}
@@ -1319,8 +1531,19 @@
if (deser == null) {
// Just one referential type as of JDK 1.7 / Java 7: AtomicReference (Java 8 adds Optional)
- if (AtomicReference.class.isAssignableFrom(type.getRawClass())) {
- return new AtomicReferenceDeserializer(type, contentTypeDeser, contentDeser);
+ if (type.isTypeOrSubTypeOf(AtomicReference.class)) {
+ Class<?> rawType = type.getRawClass();
+ ValueInstantiator inst;
+ if (rawType == AtomicReference.class) {
+ inst = null;
+ } else {
+ /* 23-Oct-2016, tatu: Note that subtypes are probably not supportable
+ * without either forcing merging (to avoid having to create instance)
+ * or something else...
+ */
+ inst = findValueInstantiator(ctxt, beanDesc);
+ }
+ return new AtomicReferenceDeserializer(type, inst, contentTypeDeser, contentDeser);
}
}
if (deser != null) {
@@ -1350,9 +1573,8 @@
AnnotationIntrospector ai = config.getAnnotationIntrospector();
TypeResolverBuilder<?> b = ai.findTypeResolver(config, ac, baseType);
- /* Ok: if there is no explicit type info handler, we may want to
- * use a default. If so, config object knows what to use.
- */
+ // Ok: if there is no explicit type info handler, we may want to
+ // use a default. If so, config object knows what to use.
Collection<NamedType> subtypes = null;
if (b == null) {
b = config.getDefaultTyper(baseType);
@@ -1366,11 +1588,20 @@
// (note: check for abstract type is not 100% mandatory, more of an optimization)
if ((b.getDefaultImpl() == null) && baseType.isAbstract()) {
JavaType defaultType = mapAbstractType(config, baseType);
- if (defaultType != null && defaultType.getRawClass() != baseType.getRawClass()) {
+ if ((defaultType != null) && !defaultType.hasRawClass(baseType.getRawClass())) {
b = b.defaultImpl(defaultType.getRawClass());
}
}
- return b.buildTypeDeserializer(config, baseType, subtypes);
+ // 05-Apt-2018, tatu: Since we get non-mapping exception due to various limitations,
+ // map to better type here
+ try {
+ return b.buildTypeDeserializer(config, baseType, subtypes);
+ } catch (IllegalArgumentException e0) {
+ InvalidDefinitionException e = InvalidDefinitionException.from((JsonParser) null,
+ ClassUtil.exceptionMessage(e0), baseType);
+ e.initCause(e0);
+ throw e;
+ }
}
/**
@@ -1449,11 +1680,10 @@
return StdKeyDeserializers.constructDelegatingKeyDeserializer(config, type, valueDesForKey);
}
}
- EnumResolver enumRes = constructEnumResolver(enumClass, config, beanDesc.findJsonValueMethod());
+ EnumResolver enumRes = constructEnumResolver(enumClass, config, beanDesc.findJsonValueAccessor());
// May have @JsonCreator for static factory method:
- final AnnotationIntrospector ai = config.getAnnotationIntrospector();
for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {
- if (ai.hasCreatorAnnotation(factory)) {
+ if (_hasCreatorAnnotation(ctxt, factory)) {
int argCount = factory.getParameterCount();
if (argCount == 1) {
Class<?> returnType = factory.getRawReturnType();
@@ -1567,7 +1797,7 @@
}
return new UntypedObjectDeserializer(lt, mt);
}
- if (rawType == CLASS_STRING || rawType == CLASS_CHAR_BUFFER) {
+ if (rawType == CLASS_STRING || rawType == CLASS_CHAR_SEQUENCE) {
return StringDeserializer.instance;
}
if (rawType == CLASS_ITERABLE) {
@@ -1581,14 +1811,8 @@
}
if (rawType == CLASS_MAP_ENTRY) {
// 28-Apr-2015, tatu: TypeFactory does it all for us already so
- JavaType kt = type.containedType(0);
- if (kt == null) {
- kt = TypeFactory.unknownType();
- }
- JavaType vt = type.containedType(1);
- if (vt == null) {
- vt = TypeFactory.unknownType();
- }
+ JavaType kt = type.containedTypeOrUnknown(0);
+ JavaType vt = type.containedTypeOrUnknown(1);
TypeDeserializer vts = (TypeDeserializer) vt.getTypeHandler();
if (vts == null) {
vts = findTypeDeserializer(ctxt.getConfig(), vt);
@@ -1810,6 +2034,23 @@
}
/**
+ * @since 2.9
+ */
+ protected JsonDeserializer<Object> findContentDeserializerFromAnnotation(DeserializationContext ctxt,
+ Annotated ann)
+ throws JsonMappingException
+ {
+ AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
+ if (intr != null) {
+ Object deserDef = intr.findContentDeserializer(ann);
+ if (deserDef != null) {
+ return ctxt.deserializerInstance(ann, deserDef);
+ }
+ }
+ return null;
+ }
+
+ /**
* Helper method used to resolve additional type-related annotation information
* like type overrides, or handler (serializer, deserializer) overrides,
* so that from declared field, property or constructor parameter type
@@ -1870,20 +2111,34 @@
}
protected EnumResolver constructEnumResolver(Class<?> enumClass,
- DeserializationConfig config, AnnotatedMethod jsonValueMethod)
+ DeserializationConfig config, AnnotatedMember jsonValueAccessor)
{
- if (jsonValueMethod != null) {
- Method accessor = jsonValueMethod.getAnnotated();
+ if (jsonValueAccessor != null) {
if (config.canOverrideAccessModifiers()) {
- ClassUtil.checkAndFixAccess(accessor, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
+ ClassUtil.checkAndFixAccess(jsonValueAccessor.getMember(),
+ config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
- return EnumResolver.constructUnsafeUsingMethod(enumClass, accessor, config.getAnnotationIntrospector());
+ return EnumResolver.constructUnsafeUsingMethod(enumClass,
+ jsonValueAccessor, config.getAnnotationIntrospector());
}
// 14-Mar-2016, tatu: We used to check `DeserializationFeature.READ_ENUMS_USING_TO_STRING`
// here, but that won't do: it must be dynamically changeable...
return EnumResolver.constructUnsafe(enumClass, config.getAnnotationIntrospector());
}
+ /**
+ * @since 2.9
+ */
+ protected boolean _hasCreatorAnnotation(DeserializationContext ctxt,
+ Annotated ann) {
+ AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
+ if (intr != null) {
+ JsonCreator.Mode mode = intr.findCreatorAnnotation(ctxt.getConfig(), ann);
+ return (mode != null) && (mode != JsonCreator.Mode.DISABLED);
+ }
+ return false;
+ }
+
/*
/**********************************************************
/* Deprecated helper methods
@@ -1905,36 +2160,6 @@
if (intr == null) {
return type;
}
-
- // First, deserializers for key/value types?
- /*
- if (type.isMapLikeType()) {
- JavaType keyType = type.getKeyType();
- // 21-Mar-2011, tatu: ... and associated deserializer too (unless already assigned)
- // (not 100% why or how, but this does seem to get called more than once, which
- // is not good: for now, let's just avoid errors)
- if (keyType != null && keyType.getValueHandler() == null) {
- Object kdDef = intr.findKeyDeserializer(a);
- KeyDeserializer kd = ctxt.keyDeserializerInstance(a, kdDef);
- if (kd != null) {
- type = (T) ((MapLikeType) type).withKeyValueHandler(kd);
- keyType = type.getKeyType(); // just in case it's used below
- }
- }
- }
- JavaType contentType = type.getContentType();
- if (contentType != null) {
- // ... as well as deserializer for contents:
- if (contentType.getValueHandler() == null) { // as with above, avoid resetting (which would trigger exception)
- Object cdDef = intr.findContentDeserializer(a);
- JsonDeserializer<?> cd = ctxt.deserializerInstance(a, cdDef);
- if (cd != null) {
- type = (T) type.withContentValueHandler(cd);
- }
- }
- }
- */
- // then: type refinement(s)?
return intr.refineDeserializationType(ctxt.getConfig(), a, type);
}
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 76603d5..88051e7 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
@@ -20,7 +20,7 @@
{
/* TODOs for future versions:
*
- * For 2.8?
+ * For 2.9?
*
* - New method in JsonDeserializer (deserializeNext()) to allow use of more
* efficient 'nextXxx()' method `JsonParser` provides.
@@ -35,10 +35,18 @@
* Lazily constructed exception used as root cause if reporting problem
* with creator method that returns <code>null</code> (which is not allowed)
*
- * @since 3.8
+ * @since 2.8
*/
protected transient Exception _nullFromCreator;
-
+
+ /**
+ * State marker we need in order to avoid infinite recursion for some cases
+ * (not very clean, alas, but has to do for now)
+ *
+ * @since 2.9
+ */
+ private volatile transient NameTransformer _currentlyTransforming;
+
/*
/**********************************************************
/* Life-cycle, construction, initialization
@@ -86,19 +94,22 @@
}
@Override
- public JsonDeserializer<Object> unwrappingDeserializer(NameTransformer unwrapper)
+ public JsonDeserializer<Object> unwrappingDeserializer(NameTransformer transformer)
{
- /* bit kludgy but we don't want to accidentally change type; sub-classes
- * MUST override this method to support unwrapped properties...
- */
+ // bit kludgy but we don't want to accidentally change type; sub-classes
+ // MUST override this method to support unwrapped properties...
if (getClass() != BeanDeserializer.class) {
return this;
}
- /* main thing really is to just enforce ignoring of unknown
- * properties; since there may be multiple unwrapped values
- * and properties for all may be interleaved...
- */
- return new BeanDeserializer(this, unwrapper);
+ // 25-Mar-2017, tatu: Not clean at all, but for [databind#383] we do need
+ // to keep track of accidental recursion...
+ if (_currentlyTransforming == transformer) {
+ return this;
+ }
+ _currentlyTransforming = transformer;
+ try {
+ return new BeanDeserializer(this, transformer);
+ } finally { _currentlyTransforming = null; }
}
@Override
@@ -154,34 +165,35 @@
JsonToken t) throws IOException
{
// and then others, generally requiring use of @JsonCreator
- switch (t) {
- case VALUE_STRING:
- return deserializeFromString(p, ctxt);
- case VALUE_NUMBER_INT:
- return deserializeFromNumber(p, ctxt);
- case VALUE_NUMBER_FLOAT:
- return deserializeFromDouble(p, ctxt);
- case VALUE_EMBEDDED_OBJECT:
- return deserializeFromEmbedded(p, ctxt);
- case VALUE_TRUE:
- case VALUE_FALSE:
- return deserializeFromBoolean(p, ctxt);
-
- case VALUE_NULL:
- return deserializeFromNull(p, ctxt);
- case START_ARRAY:
- // these only work if there's a (delegating) creator...
- return deserializeFromArray(p, ctxt);
- case FIELD_NAME:
- case END_OBJECT: // added to resolve [JACKSON-319], possible related issues
- if (_vanillaProcessing) {
- return vanillaDeserialize(p, ctxt, t);
+ if (t != null) {
+ switch (t) {
+ case VALUE_STRING:
+ return deserializeFromString(p, ctxt);
+ case VALUE_NUMBER_INT:
+ return deserializeFromNumber(p, ctxt);
+ case VALUE_NUMBER_FLOAT:
+ return deserializeFromDouble(p, ctxt);
+ case VALUE_EMBEDDED_OBJECT:
+ return deserializeFromEmbedded(p, ctxt);
+ case VALUE_TRUE:
+ case VALUE_FALSE:
+ return deserializeFromBoolean(p, ctxt);
+ case VALUE_NULL:
+ return deserializeFromNull(p, ctxt);
+ case START_ARRAY:
+ // these only work if there's a (delegating) creator...
+ return deserializeFromArray(p, ctxt);
+ case FIELD_NAME:
+ case END_OBJECT: // added to resolve [JACKSON-319], possible related issues
+ if (_vanillaProcessing) {
+ return vanillaDeserialize(p, ctxt, t);
+ }
+ if (_objectIdReader != null) {
+ return deserializeWithObjectId(p, ctxt);
+ }
+ return deserializeFromObject(p, ctxt);
+ default:
}
- if (_objectIdReader != null) {
- return deserializeWithObjectId(p, ctxt);
- }
- return deserializeFromObject(p, ctxt);
- default:
}
return ctxt.handleUnexpectedToken(handledType(), p);
}
@@ -291,14 +303,14 @@
@Override
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
- /* 09-Dec-2014, tatu: As per [#622], we need to allow Object Id references
+ /* 09-Dec-2014, tatu: As per [databind#622], we need to allow Object Id references
* to come in as JSON Objects as well; but for now assume they will
* be simple, single-property references, which means that we can
* recognize them without having to buffer anything.
* Once again, if we must, we can do more complex handling with buffering,
* but let's only do that if and when that becomes necessary.
*/
- if (_objectIdReader != null && _objectIdReader.maySerializeAsObject()) {
+ if ((_objectIdReader != null) && _objectIdReader.maySerializeAsObject()) {
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)
&& _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) {
return deserializeFromObjectId(p, ctxt);
@@ -381,8 +393,8 @@
{
final PropertyBasedCreator creator = _propertyBasedCreator;
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader);
-
TokenBuffer unknown = null;
+ final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
JsonToken t = p.getCurrentToken();
List<BeanReferring> referrings = null;
@@ -397,8 +409,13 @@
SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {
// Last creator property to set?
- if (buffer.assignParameter(creatorProp,
- _deserializeWithErrorWrapping(p, ctxt, creatorProp))) {
+ Object value;
+ if ((activeView != null) && !creatorProp.visibleInView(activeView)) {
+ p.skipChildren();
+ continue;
+ }
+ value = _deserializeWithErrorWrapping(p, ctxt, creatorProp);
+ if (buffer.assignParameter(creatorProp, value)) {
p.nextToken(); // to move to following FIELD_NAME/END_OBJECT
Object bean;
try {
@@ -616,7 +633,7 @@
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
- if (activeView != null && !prop.visibleInView(activeView)) {
+ if ((activeView != null) && !prop.visibleInView(activeView)) {
p.skipChildren();
continue;
}
@@ -640,21 +657,17 @@
// but... others should be passed to unwrapped property deserializers
tokens.writeFieldName(propName);
tokens.copyCurrentStructure(p);
- } else {
- // Need to copy to a separate buffer first
- TokenBuffer b2 = new TokenBuffer(p, ctxt);
- b2.copyCurrentStructure(p);
- tokens.writeFieldName(propName);
- tokens.append(b2);
- try {
- JsonParser p2 = b2.asParser(p);
- p2.nextToken();
- _anySetter.deserializeAndSet(p2, ctxt, bean, propName);
- } catch (Exception e) {
- wrapAndThrow(e, bean, propName, ctxt);
- }
continue;
}
+ // Need to copy to a separate buffer first
+ TokenBuffer b2 = TokenBuffer.asCopyOfValue(p);
+ tokens.writeFieldName(propName);
+ tokens.append(b2);
+ try {
+ _anySetter.deserializeAndSet(b2.asParserOnFirstToken(), ctxt, bean, propName);
+ } catch (Exception e) {
+ wrapAndThrow(e, bean, propName, ctxt);
+ }
}
tokens.writeEndObject();
_unwrappedPropertyHandler.processUnwrapped(p, ctxt, bean, tokens);
@@ -662,7 +675,8 @@
}
@SuppressWarnings("resource")
- protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext ctxt, Object bean)
+ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext ctxt,
+ Object bean)
throws IOException
{
JsonToken t = p.getCurrentToken();
@@ -702,14 +716,11 @@
tokens.copyCurrentStructure(p);
} else {
// Need to copy to a separate buffer first
- TokenBuffer b2 = new TokenBuffer(p, ctxt);
- b2.copyCurrentStructure(p);
+ TokenBuffer b2 = TokenBuffer.asCopyOfValue(p);
tokens.writeFieldName(propName);
tokens.append(b2);
try {
- JsonParser p2 = b2.asParser(p);
- p2.nextToken();
- _anySetter.deserializeAndSet(p2, ctxt, bean, propName);
+ _anySetter.deserializeAndSet(b2.asParserOnFirstToken(), ctxt, bean, propName);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
@@ -725,6 +736,10 @@
protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, DeserializationContext ctxt)
throws IOException
{
+ // 01-Dec-2016, tatu: Note: This IS legal to call, but only when unwrapped
+ // value itself is NOT passed via `CreatorProperty` (which isn't supported).
+ // Ok however to pass via setter or field.
+
final PropertyBasedCreator creator = _propertyBasedCreator;
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader);
@@ -739,7 +754,8 @@
SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {
// Last creator property to set?
- if (buffer.assignParameter(creatorProp, _deserializeWithErrorWrapping(p, ctxt, creatorProp))) {
+ if (buffer.assignParameter(creatorProp,
+ _deserializeWithErrorWrapping(p, ctxt, creatorProp))) {
t = p.nextToken(); // to move to following FIELD_NAME/END_OBJECT
Object bean;
try {
@@ -751,16 +767,23 @@
p.setCurrentValue(bean);
// if so, need to copy all remaining tokens into buffer
while (t == JsonToken.FIELD_NAME) {
- p.nextToken(); // to skip name
+ // NOTE: do NOT skip name as it needs to be copied; `copyCurrentStructure` does that
tokens.copyCurrentStructure(p);
t = p.nextToken();
}
+ // 28-Aug-2018, tatu: Let's add sanity check here, easier to catch off-by-some
+ // problems if we maintain invariants
+ if (t != JsonToken.END_OBJECT) {
+ ctxt.reportWrongTokenException(this, JsonToken.END_OBJECT,
+ "Attempted to unwrap '%s' value",
+ handledType().getName());
+ }
tokens.writeEndObject();
if (bean.getClass() != _beanType.getRawClass()) {
// !!! 08-Jul-2011, tatu: Could probably support; but for now
// it's too complicated, so bail out
- tokens.close();
- ctxt.reportMappingException("Can not create polymorphic instances with unwrapped values");
+ ctxt.reportInputMismatch(creatorProp,
+ "Cannot create polymorphic instances with unwrapped values");
return null;
}
return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, bean, tokens);
@@ -792,15 +815,12 @@
tokens.copyCurrentStructure(p);
} else {
// Need to copy to a separate buffer first
- TokenBuffer b2 = new TokenBuffer(p, ctxt);
- b2.copyCurrentStructure(p);
+ TokenBuffer b2 = TokenBuffer.asCopyOfValue(p);
tokens.writeFieldName(propName);
tokens.append(b2);
try {
- JsonParser p2 = b2.asParser(p);
- p2.nextToken();
buffer.bufferAnyProperty(_anySetter, propName,
- _anySetter.deserialize(p2, ctxt));
+ _anySetter.deserialize(b2.asParserOnFirstToken(), ctxt));
} catch (Exception e) {
wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt);
}
@@ -940,8 +960,9 @@
if (bean.getClass() != _beanType.getRawClass()) {
// !!! 08-Jul-2011, tatu: Could theoretically support; but for now
// it's too complicated, so bail out
- ctxt.reportMappingException("Can not create polymorphic instances with external type ids");
- return null;
+ return ctxt.reportBadDefinition(_beanType, String.format(
+ "Cannot create polymorphic instances with external type ids (%s -> %s)",
+ _beanType, bean.getClass()));
}
return ext.complete(p, ctxt, bean);
}
@@ -969,9 +990,11 @@
}
// "any property"?
if (_anySetter != null) {
- buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt));
+ buffer.bufferAnyProperty(_anySetter, propName,
+ _anySetter.deserialize(p, ctxt));
}
}
+ tokens.writeEndObject();
// We hit END_OBJECT; resolve the pieces:
try {
@@ -1019,8 +1042,8 @@
public void handleResolvedForwardReference(Object id, Object value) throws IOException
{
if (_bean == null) {
- _context.reportMappingException(
-"Can not resolve ObjectId forward reference using property '%s' (of type %s): Bean not yet resolved",
+ _context.reportInputMismatch(_prop,
+"Cannot resolve ObjectId forward reference using property '%s' (of type %s): Bean not yet resolved",
_prop.getName(), _prop.getDeclaringClass().getName());
}
_prop.set(_bean, 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 63eafbb..6ce41f7 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
@@ -26,6 +26,7 @@
public abstract class BeanDeserializerBase
extends StdDeserializer<Object>
implements ContextualDeserializer, ResolvableDeserializer,
+ ValueInstantiator.Gettable, // since 2.9
java.io.Serializable // since 2.1
{
private static final long serialVersionUID = 1;
@@ -39,16 +40,6 @@
*/
/**
- * Annotations from the bean class: used for accessing
- * annotations during resolution
- * (see {@link #resolve}) and
- * contextualization (see {@link #createContextual})
- *<p>
- * Transient since annotations only used during construction.
- */
- final private transient Annotations _classAnnotations;
-
- /**
* Declared type of the bean this deserializer handles.
*/
final protected JavaType _beanType;
@@ -57,7 +48,7 @@
* Requested shape from bean class annotations.
*/
final protected JsonFormat.Shape _serializationShape;
-
+
/*
/**********************************************************
/* Configuration for creating value instance
@@ -150,13 +141,13 @@
* on active view used (if any)
*/
final protected boolean _needViewProcesing;
-
+
/**
* We may also have one or more back reference fields (usually
* zero or one).
*/
final protected Map<String, SettableBeanProperty> _backRefs;
-
+
/*
/**********************************************************
/* Related handlers
@@ -208,9 +199,6 @@
boolean hasViews)
{
super(beanDesc.getType());
-
- AnnotatedClass ac = beanDesc.getClassInfo();
- _classAnnotations = ac.getAnnotations();
_beanType = beanDesc.getType();
_valueInstantiator = builder.getValueInstantiator();
@@ -252,7 +240,6 @@
{
super(src._beanType);
- _classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
@@ -274,12 +261,11 @@
_vanillaProcessing = src._vanillaProcessing;
}
-
+
protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapper)
{
super(src._beanType);
- _classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
@@ -317,8 +303,6 @@
public BeanDeserializerBase(BeanDeserializerBase src, ObjectIdReader oir)
{
super(src._beanType);
-
- _classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
@@ -356,19 +340,18 @@
public BeanDeserializerBase(BeanDeserializerBase src, Set<String> ignorableProps)
{
super(src._beanType);
- _classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
_propertyBasedCreator = src._propertyBasedCreator;
-
+
_backRefs = src._backRefs;
_ignorableProps = ignorableProps;
_ignoreAllUnknown = src._ignoreAllUnknown;
_anySetter = src._anySetter;
_injectables = src._injectables;
-
+
_nonStandardCreation = src._nonStandardCreation;
_unwrappedPropertyHandler = src._unwrappedPropertyHandler;
_needViewProcesing = src._needViewProcesing;
@@ -388,14 +371,12 @@
protected BeanDeserializerBase(BeanDeserializerBase src, BeanPropertyMap beanProps)
{
super(src._beanType);
-
- _classAnnotations = src._classAnnotations;
_beanType = src._beanType;
-
+
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
_propertyBasedCreator = src._propertyBasedCreator;
-
+
_beanProperties = beanProps;
_backRefs = src._backRefs;
_ignorableProps = src._ignorableProps;
@@ -434,7 +415,7 @@
* Fluent factory for creating a variant that can handle
* POJO output as a JSON Array. Implementations may ignore this request
* if no such input is possible.
- *
+ *
* @since 2.1
*/
protected abstract BeanDeserializerBase asArrayDeserializer();
@@ -451,8 +432,7 @@
* This is needed to handle recursive and transitive dependencies.
*/
@Override
- public void resolve(DeserializationContext ctxt)
- throws JsonMappingException
+ public void resolve(DeserializationContext ctxt) throws JsonMappingException
{
ExternalTypeHandler.Builder extTypes = null;
// if ValueInstantiator can use "creator" approach, need to resolve it here...
@@ -460,6 +440,18 @@
if (_valueInstantiator.canCreateFromObjectWith()) {
creatorProps = _valueInstantiator.getFromObjectArguments(ctxt.getConfig());
+
+ // 22-Jan-2018, tatu: May need to propagate "ignorable" status (from `Access.READ_ONLY`
+ // or perhaps class-ignorables) into Creator properties too. Can not just delete,
+ // at this point, but is needed for further processing down the line
+ if (_ignorableProps != null) {
+ for (int i = 0, end = creatorProps.length; i < end; ++i) {
+ SettableBeanProperty prop = creatorProps[i];
+ if (_ignorableProps.contains(prop.getName())) {
+ creatorProps[i].markAsIgnorable();
+ }
+ }
+ }
} else {
creatorProps = null;
}
@@ -468,9 +460,11 @@
// 24-Mar-2017, tatu: Looks like we may have to iterate over
// properties twice, to handle potential issues with recursive
// types (see [databind#1575] f.ex).
-
// First loop: find deserializer if not yet known, but do not yet
// contextualize (since that can lead to problems with self-references)
+ // 22-Jan-2018, tatu: NOTE! Need not check for `isIgnorable` as that can
+ // only happen for props in `creatorProps`
+
for (SettableBeanProperty prop : _beanProperties) {
if (!prop.hasValueDeserializer()) {
// [databind#125]: allow use of converters
@@ -497,19 +491,30 @@
prop = _resolvedObjectIdProperty(ctxt, prop);
}
// Support unwrapped values (via @JsonUnwrapped)
- SettableBeanProperty u = _resolveUnwrappedProperty(ctxt, prop);
- if (u != null) {
- prop = u;
- if (unwrapped == null) {
- unwrapped = new UnwrappedPropertyHandler();
+ NameTransformer xform = _findPropertyUnwrapper(ctxt, prop);
+ if (xform != null) {
+ JsonDeserializer<Object> orig = prop.getValueDeserializer();
+ JsonDeserializer<Object> unwrapping = orig.unwrappingDeserializer(xform);
+ if (unwrapping != orig && unwrapping != null) {
+ prop = prop.withValueDeserializer(unwrapping);
+ if (unwrapped == null) {
+ unwrapped = new UnwrappedPropertyHandler();
+ }
+ unwrapped.addProperty(prop);
+ // 12-Dec-2014, tatu: As per [databind#647], we will have problems if
+ // the original property is left in place. So let's remove it now.
+ // 25-Mar-2017, tatu: Wonder if this could be problematic wrt creators?
+ // (that is, should be remove it from creator too)
+ _beanProperties.remove(prop);
+ continue;
}
- unwrapped.addProperty(prop);
- // 12-Dec-2014, tatu: As per [databind#647], we will have problems if
- // the original property is left in place. So let's remove it now.
- // 25-Mar-2017, tatu: Wonder if this could be problematic wrt creators?
- _beanProperties.remove(prop);
- continue;
}
+
+ // 26-Oct-2016, tatu: Need to have access to value deserializer to know if
+ // merging needed, and now seems to be reasonable time to do that.
+ final PropertyMetadata md = prop.getMetadata();
+ prop = _resolveMergeAndNullSettings(ctxt, prop, md);
+
// non-static inner classes too:
prop = _resolveInnerClassValuedProperty(ctxt, prop);
if (prop != origProp) {
@@ -522,7 +527,7 @@
TypeDeserializer typeDeser = prop.getValueTypeDeserializer();
if (typeDeser.getTypeInclusion() == JsonTypeInfo.As.EXTERNAL_PROPERTY) {
if (extTypes == null) {
- extTypes = new ExternalTypeHandler.Builder();
+ extTypes = ExternalTypeHandler.builder(_beanType);
}
extTypes.addExternal(prop, typeDeser);
// In fact, remove from list of known properties to simplify later handling
@@ -532,7 +537,7 @@
}
}
// "any setter" may also need to be resolved now
- if (_anySetter != null && !_anySetter.hasValueDeserializer()) {
+ if ((_anySetter != null) && !_anySetter.hasValueDeserializer()) {
_anySetter = _anySetter.withValueDeserializer(findDeserializer(ctxt,
_anySetter.getType(), _anySetter.getProperty()));
}
@@ -540,9 +545,9 @@
if (_valueInstantiator.canCreateUsingDelegate()) {
JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
if (delegateType == null) {
- throw new IllegalArgumentException("Invalid delegate-creator definition for "+_beanType
- +": value instantiator ("+_valueInstantiator.getClass().getName()
- +") returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'");
+ ctxt.reportBadDefinition(_beanType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'",
+ _beanType, _valueInstantiator.getClass().getName()));
}
_delegateDeserializer = _findDelegateDeserializer(ctxt, delegateType,
_valueInstantiator.getDelegateCreator());
@@ -552,9 +557,9 @@
if (_valueInstantiator.canCreateUsingArrayDelegate()) {
JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
if (delegateType == null) {
- throw new IllegalArgumentException("Invalid array-delegate-creator definition for "+_beanType
- +": value instantiator ("+_valueInstantiator.getClass().getName()
- +") returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'");
+ ctxt.reportBadDefinition(_beanType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'",
+ _beanType, _valueInstantiator.getClass().getName()));
}
_arrayDelegateDeserializer = _findDelegateDeserializer(ctxt, delegateType,
_valueInstantiator.getArrayDelegateCreator());
@@ -562,7 +567,8 @@
// And now that we know CreatorProperty instances are also resolved can finally create the creator:
if (creatorProps != null) {
- _propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, creatorProps);
+ _propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator,
+ creatorProps, _beanProperties);
}
if (extTypes != null) {
@@ -587,7 +593,7 @@
protected void _replaceProperty(BeanPropertyMap props, SettableBeanProperty[] creatorProps,
SettableBeanProperty origProp, SettableBeanProperty newProp)
{
- props.replace(newProp);
+ props.replace(origProp, newProp);
// [databind#795]: Make sure PropertyBasedCreator's properties stay in sync
if (creatorProps != null) {
// 18-May-2015, tatu: _Should_ start with consistent set. But can we really
@@ -598,29 +604,39 @@
return;
}
}
+ /*
// ... as per above, it is possible we'd need to add this as fallback
// if (but only if) identity check fails?
- /*
- if (creatorProps[i].getName().equals(prop.getName())) {
- creatorProps[i] = prop;
- break;
+ for (int i = 0, len = creatorProps.length; i < len; ++i) {
+ if (creatorProps[i].getName().equals(origProp.getName())) {
+ creatorProps[i] = newProp;
+ return;
+ }
}
*/
}
}
- private JsonDeserializer<Object> _findDelegateDeserializer(DeserializationContext ctxt, JavaType delegateType,
- AnnotatedWithParams delegateCreator) throws JsonMappingException {
+ @SuppressWarnings("unchecked")
+ private JsonDeserializer<Object> _findDelegateDeserializer(DeserializationContext ctxt,
+ JavaType delegateType, AnnotatedWithParams delegateCreator) throws JsonMappingException
+ {
// Need to create a temporary property to allow contextual deserializers:
BeanProperty.Std property = new BeanProperty.Std(TEMP_PROPERTY_NAME,
- delegateType, null, _classAnnotations, delegateCreator,
+ delegateType, null, delegateCreator,
PropertyMetadata.STD_OPTIONAL);
-
TypeDeserializer td = delegateType.getTypeHandler();
if (td == null) {
td = ctxt.getConfig().findTypeDeserializer(delegateType);
}
- JsonDeserializer<Object> dd = findDeserializer(ctxt, delegateType, property);
+ // 04-May-2018, tatu: [databind#2021] check if there's custom deserializer attached
+ // to type (resolved from parameter)
+ JsonDeserializer<Object> dd = delegateType.getValueHandler();
+ if (dd == null) {
+ dd = findDeserializer(ctxt, delegateType, property);
+ } else {
+ dd = (JsonDeserializer<Object>) ctxt.handleSecondaryContextualization(dd, property, delegateType);
+ }
if (td != null) {
td = td.forProperty(property);
return new TypeWrappedDeserializer(td, dd);
@@ -628,7 +644,6 @@
return dd;
}
-
/**
* Helper method that can be used to see if specified property is annotated
* to indicate use of a converter for property value (in case of container types,
@@ -636,7 +651,7 @@
*<p>
* NOTE: returned deserializer is NOT yet contextualized, caller needs to take
* care to do that.
- *
+ *
* @since 2.2
*/
protected JsonDeserializer<Object> findConvertingDeserializer(DeserializationContext ctxt,
@@ -657,7 +672,7 @@
}
return null;
}
-
+
/**
* Although most of post-processing is done in resolve(), we only get
* access to referring property's annotations here; and this is needed
@@ -673,9 +688,8 @@
// First: may have an override for Object Id:
final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
- final AnnotatedMember accessor = (property == null || intr == null)
- ? null : property.getMember();
- if (accessor != null && intr != null) {
+ final AnnotatedMember accessor = _neitherNull(property, intr) ? property.getMember() : null;
+ if (accessor != null) {
ObjectIdInfo objectIdInfo = intr.findObjectIdInfo(accessor);
if (objectIdInfo != null) { // some code duplication here as well (from BeanDeserializerFactory)
// 2.1: allow modifications by "id ref" annotations as well:
@@ -691,12 +705,13 @@
PropertyName propName = objectIdInfo.getPropertyName();
idProp = findProperty(propName);
if (idProp == null) {
- throw new IllegalArgumentException("Invalid Object Id definition for "
- +handledType().getName()+": can not find property with name '"+propName+"'");
+ ctxt.reportBadDefinition(_beanType, String.format(
+ "Invalid Object Id definition for %s: cannot find property with name '%s'",
+ handledType().getName(), propName));
}
idType = idProp.getType();
idGen = new PropertyBasedObjectIdGenerator(objectIdInfo.getScope());
- } else { // other types need to be simpler
+ } else { // other types are to be simpler
JavaType type = ctxt.constructType(implClass);
idType = ctxt.getTypeFactory().findTypeParameters(type, ObjectIdGenerator.class)[0];
idProp = null;
@@ -738,7 +753,6 @@
// 16-May-2016, tatu: How about per-property case-insensitivity?
Boolean B = format.getFeature(JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
if (B != null) {
- // !!! TODO
BeanPropertyMap propsOrig = _beanProperties;
BeanPropertyMap props = propsOrig.withCaseInsensitivity(B.booleanValue());
if (props != propsOrig) {
@@ -762,6 +776,7 @@
*/
protected SettableBeanProperty _resolveManagedReferenceProperty(DeserializationContext ctxt,
SettableBeanProperty prop)
+ throws JsonMappingException
{
String refName = prop.getManagedReferenceName();
if (refName == null) {
@@ -770,20 +785,21 @@
JsonDeserializer<?> valueDeser = prop.getValueDeserializer();
SettableBeanProperty backProp = valueDeser.findBackReference(refName);
if (backProp == null) {
- throw new IllegalArgumentException("Can not handle managed/back reference '"+refName+"': no back reference property found from type "
- +prop.getType());
+ ctxt.reportBadDefinition(_beanType, String.format(
+"Cannot handle managed/back reference '%s': no back reference property found from type %s",
+ refName, prop.getType()));
}
// also: verify that type is compatible
JavaType referredType = _beanType;
JavaType backRefType = backProp.getType();
boolean isContainer = prop.getType().isContainerType();
if (!backRefType.getRawClass().isAssignableFrom(referredType.getRawClass())) {
- throw new IllegalArgumentException("Can not handle managed/back reference '"+refName+"': back reference type ("
- +backRefType.getRawClass().getName()+") not compatible with managed type ("
- +referredType.getRawClass().getName()+")");
+ ctxt.reportBadDefinition(_beanType, String.format(
+"Cannot handle managed/back reference '%s': back reference type (%s) not compatible with managed type (%s)",
+ refName, backRefType.getRawClass().getName(),
+ referredType.getRawClass().getName()));
}
- return new ManagedReferenceProperty(prop, refName, backProp,
- _classAnnotations, isContainer);
+ return new ManagedReferenceProperty(prop, refName, backProp, isContainer);
}
/**
@@ -806,24 +822,27 @@
* Helper method called to see if given property might be so-called unwrapped
* property: these require special handling.
*/
- protected SettableBeanProperty _resolveUnwrappedProperty(DeserializationContext ctxt,
+ protected NameTransformer _findPropertyUnwrapper(DeserializationContext ctxt,
SettableBeanProperty prop)
+ throws JsonMappingException
{
AnnotatedMember am = prop.getMember();
if (am != null) {
NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(am);
if (unwrapper != null) {
- JsonDeserializer<Object> orig = prop.getValueDeserializer();
- JsonDeserializer<Object> unwrapping = orig.unwrappingDeserializer(unwrapper);
- if (unwrapping != orig && unwrapping != null) {
- // might be cleaner to create new instance; but difficult to do reliably, so:
- return prop.withValueDeserializer(unwrapping);
+ // 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
+ // of unwrapped values through creator properties, so fail fast
+ if (prop instanceof CreatorProperty) {
+ ctxt.reportBadDefinition(getValueType(), String.format(
+ "Cannot define Creator property \"%s\" as `@JsonUnwrapped`: combination not yet supported",
+ prop.getName()));
}
+ return unwrapper;
}
}
return null;
}
-
+
/**
* Helper method that will handle gruesome details of dealing with properties
* that have non-static inner class as value...
@@ -862,20 +881,96 @@
return prop;
}
+ // @since 2.9
+ protected SettableBeanProperty _resolveMergeAndNullSettings(DeserializationContext ctxt,
+ SettableBeanProperty prop, PropertyMetadata propMetadata)
+ throws JsonMappingException
+ {
+ PropertyMetadata.MergeInfo merge = propMetadata.getMergeInfo();
+ // First mergeability
+ if (merge != null) {
+ JsonDeserializer<?> valueDeser = prop.getValueDeserializer();
+ Boolean mayMerge = valueDeser.supportsUpdate(ctxt.getConfig());
+
+ if (mayMerge == null) {
+ // we don't really know if it's ok; so only use if explicitly specified
+ if (merge.fromDefaults) {
+ return prop;
+ }
+ } else if (!mayMerge.booleanValue()) { // prevented
+ if (!merge.fromDefaults) {
+ // If attempts was made via explicit annotation/per-type config override,
+ // should be reported; may or may not result in exception
+ ctxt.reportBadMerge(valueDeser);
+ }
+ return prop;
+ }
+ // Anyway; if we get this far, do enable merging
+ AnnotatedMember accessor = merge.getter;
+ accessor.fixAccess(ctxt.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
+ if (!(prop instanceof SetterlessProperty)) {
+ prop = MergingSettableBeanProperty.construct(prop, accessor);
+ }
+ }
+
+ // And after this, see if we require non-standard null handling
+ NullValueProvider nuller = findValueNullProvider(ctxt, prop, propMetadata);
+ if (nuller != null) {
+ prop = prop.withNullProvider(nuller);
+ }
+ return prop;
+ }
+
/*
/**********************************************************
- /* Public accessors
+ /* Public accessors; null/empty value providers
+ /**********************************************************
+ */
+
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ // POJO types do not have custom `null` values
+ return AccessPattern.ALWAYS_NULL;
+ }
+
+ @Override
+ public AccessPattern getEmptyAccessPattern() {
+ // Empty values cannot be shared
+ return AccessPattern.DYNAMIC;
+ }
+
+ @Override // since 2.9
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ // alas, need to promote exception, if any:
+ try {
+ return _valueInstantiator.createUsingDefault(ctxt);
+ } catch (IOException e) {
+ return ClassUtil.throwAsMappingException(ctxt, e);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Public accessors; other
/**********************************************************
*/
@Override
public boolean isCachable() { return true; }
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ // although with possible caveats, yes, values can be updated
+ // 23-Oct-2016, tatu: Perhaps in future could and should verify from
+ // bean settings...
+ return Boolean.TRUE;
+ }
+
@Override
public Class<?> handledType() {
return _beanType.getRawClass();
}
-
+
/**
* Overridden to return true for those instances that are
* handling value for which Object Identity handling is enabled
@@ -938,7 +1033,7 @@
* Accessor for finding properties that represents values to pass
* through property-based creator method (constructor or
* factory method)
- *
+ *
* @since 2.0
*/
public Iterator<SettableBeanProperty> creatorProperties()
@@ -959,7 +1054,7 @@
* Accessor for finding the property with given name, if POJO
* has one. Name used is the external name, i.e. name used
* in external data representation (JSON).
- *
+ *
* @since 2.0
*/
public SettableBeanProperty findProperty(String propertyName)
@@ -979,7 +1074,7 @@
* since properties are not directly indexable; however, for most
* instances difference is not significant as number of properties
* is low.
- *
+ *
* @since 2.3
*/
public SettableBeanProperty findProperty(int propertyIndex)
@@ -1005,6 +1100,7 @@
return _backRefs.get(logicalName);
}
+ @Override // ValueInstantiator.Gettable
public ValueInstantiator getValueInstantiator() {
return _valueInstantiator;
}
@@ -1024,13 +1120,13 @@
*
* @param original Property to replace
* @param replacement Property to replace it with
- *
+ *
* @since 2.1
*/
public void replaceProperty(SettableBeanProperty original,
SettableBeanProperty replacement)
{
- _beanProperties.replace(replacement);
+ _beanProperties.replace(original, replacement);
}
/*
@@ -1190,28 +1286,21 @@
if (_propertyBasedCreator != null) {
return _deserializeUsingPropertyBased(p, ctxt);
}
- // should only occur for abstract types...
- if (_beanType.isAbstract()) {
- return ctxt.handleMissingInstantiator(handledType(), p,
- "abstract type (need to add/enable type information?)");
- }
// 25-Jan-2017, tatu: We do not actually support use of Creators for non-static
// inner classes -- with one and only one exception; that of default constructor!
// -- so let's indicate it
Class<?> raw = _beanType.getRawClass();
if (ClassUtil.isNonStaticInnerClass(raw)) {
- return ctxt.handleMissingInstantiator(raw, p,
+ return ctxt.handleMissingInstantiator(raw, null, p,
"can only instantiate non-static inner class by using default, no-argument constructor");
}
- return ctxt.handleMissingInstantiator(raw, p,
-"no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)");
+ return ctxt.handleMissingInstantiator(raw, getValueInstantiator(), p,
+ "cannot deserialize from Object value (no delegate- or property-based Creator)");
}
protected abstract Object _deserializeUsingPropertyBased(final JsonParser p,
- final DeserializationContext ctxt)
- throws IOException, JsonProcessingException;
+ final DeserializationContext ctxt) throws IOException;
- @SuppressWarnings("incomplete-switch")
public Object deserializeFromNumber(JsonParser p, DeserializationContext ctxt)
throws IOException
{
@@ -1220,8 +1309,8 @@
return deserializeFromObjectId(p, ctxt);
}
final JsonDeserializer<Object> delegateDeser = _delegateDeserializer();
- switch (p.getNumberType()) {
- case INT:
+ NumberType nt = p.getNumberType();
+ if (nt == NumberType.INT) {
if (delegateDeser != null) {
if (!_valueInstantiator.canCreateFromInt()) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt,
@@ -1233,7 +1322,8 @@
}
}
return _valueInstantiator.createFromInt(ctxt, p.getIntValue());
- case LONG:
+ }
+ if (nt == NumberType.LONG) {
if (delegateDeser != null) {
if (!_valueInstantiator.canCreateFromInt()) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt,
@@ -1255,12 +1345,13 @@
}
return bean;
}
- return ctxt.handleMissingInstantiator(handledType(), p,
+ return ctxt.handleMissingInstantiator(handledType(), getValueInstantiator(), p,
"no suitable creator method found to deserialize from Number value (%s)",
p.getNumberValue());
}
- public Object deserializeFromString(JsonParser p, DeserializationContext ctxt) throws IOException
+ public Object deserializeFromString(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
// First things first: id Object Id is used, most likely that's it
if (_objectIdReader != null) {
@@ -1310,7 +1401,7 @@
return _valueInstantiator.createUsingDelegate(ctxt,
delegateDeser.deserialize(p, ctxt));
}
- return ctxt.handleMissingInstantiator(handledType(), p,
+ return ctxt.handleMissingInstantiator(handledType(), getValueInstantiator(), p,
"no suitable creator method found to deserialize from Number value (%s)",
p.getNumberValue());
}
@@ -1337,7 +1428,7 @@
public Object deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
{
- // note: can not call `_delegateDeserializer()` since order reversed here:
+ // note: cannot call `_delegateDeserializer()` since order reversed here:
JsonDeserializer<Object> delegateDeser = _arrayDelegateDeserializer;
// fallback to non-array delegate
if ((delegateDeser != null) || ((delegateDeser = _delegateDeserializer) != null)) {
@@ -1390,10 +1481,19 @@
return bean;
}
}
-
// TODO: maybe add support for ValueInstantiator, embedded?
-
- return p.getEmbeddedObject();
+
+ // 26-Jul-2017, tatu: related to [databind#1711], let's actually verify assignment
+ // compatibility before returning. Bound to catch misconfigured cases and produce
+ // more meaningful exceptions.
+ Object value = p.getEmbeddedObject();
+ if (value != null) {
+ if (!_beanType.isTypeOrSuperTypeOf(value.getClass())) {
+ // allow this to be handled...
+ value = ctxt.handleWeirdNativeValue(_beanType, value, p);
+ }
+ }
+ return value;
}
/**
@@ -1420,7 +1520,7 @@
injector.inject(ctxt, bean);
}
}
-
+
/**
* Method called to handle set of one or more unknown properties,
* stored in their entirety in given {@link TokenBuffer}
@@ -1470,7 +1570,7 @@
/**
* Method called when a JSON property is encountered that has not matching
- * setter, any-setter or field, and thus can not be assigned.
+ * setter, any-setter or field, and thus cannot be assigned.
*/
@Override
protected void handleUnknownProperty(JsonParser p, DeserializationContext ctxt,
@@ -1492,7 +1592,7 @@
/**
* Method called when an explicitly ignored property (one specified with a
* name to match, either by property annotation or class annotation) is encountered.
- *
+ *
* @since 2.3
*/
protected void handleIgnoredProperty(JsonParser p, DeserializationContext ctxt,
@@ -1504,7 +1604,7 @@
}
p.skipChildren();
}
-
+
/**
* Method called in cases where we may have polymorphic deserialization
* case: that is, type of Creator-constructed bean is not the type
@@ -1547,7 +1647,7 @@
}
return bean;
}
-
+
/**
* Helper method called to (try to) locate deserializer for given sub-type of
* type that this deserializer handles.
@@ -1585,7 +1685,7 @@
}
return subDeser;
}
-
+
/*
/**********************************************************
/* Helper methods for error reporting
@@ -1607,16 +1707,10 @@
public void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
throws IOException
{
- // [JACKSON-55] Need to add reference information
+ // Need to add reference information
throw JsonMappingException.wrapWithPath(throwOrReturnThrowable(t, ctxt), bean, fieldName);
}
- @Deprecated // since 2.4, not used by core Jackson; only relevant for arrays/Collections
- public void wrapAndThrow(Throwable t, Object bean, int index, DeserializationContext ctxt) throws IOException {
- // [JACKSON-55] Need to add reference information
- throw JsonMappingException.wrapWithPath(throwOrReturnThrowable(t, ctxt), bean, index);
- }
-
private Throwable throwOrReturnThrowable(Throwable t, DeserializationContext ctxt)
throws IOException
{
@@ -1628,9 +1722,7 @@
t = t.getCause();
}
// Errors to be passed as is
- if (t instanceof Error) {
- throw (Error) t;
- }
+ ClassUtil.throwIfError(t);
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
// Ditto for IOExceptions; except we may want to wrap JSON exceptions
if (t instanceof IOException) {
@@ -1638,9 +1730,7 @@
throw (IOException) t;
}
} else if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for unchecked exceptions
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
+ ClassUtil.throwIfRTE(t);
}
return t;
}
@@ -1652,17 +1742,14 @@
t = t.getCause();
}
// Errors and "plain" IOExceptions to be passed as is
- if (t instanceof Error) {
- throw (Error) t;
- }
- boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
+ ClassUtil.throwIfError(t);
if (t instanceof IOException) {
// Since we have no more information to add, let's not actually wrap..
throw (IOException) t;
- } else if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for unchecked exceptions
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
+ }
+ boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
+ if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for unchecked exceptions
+ ClassUtil.throwIfRTE(t);
}
return ctxt.handleInstantiationProblem(_beanType.getRawClass(), null, t);
}
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 5ca54d5..c105307 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBuilder.java
@@ -26,6 +26,11 @@
final protected DeserializationConfig _config;
+ /**
+ * @since 2.9
+ */
+ final protected DeserializationContext _context;
+
/*
/**********************************************************
/* General information about POJO
@@ -42,18 +47,18 @@
/* Accumulated information about properties
/**********************************************************
*/
-
+
/**
* Properties to deserialize collected so far.
*/
final protected Map<String, SettableBeanProperty> _properties
= new LinkedHashMap<String, SettableBeanProperty>();
-
+
/**
* Value injectors for deserialization
*/
protected List<ValueInjector> _injectables;
-
+
/**
* Back-reference properties this bean contains (if any)
*/
@@ -64,7 +69,7 @@
* purposes (meaning no exception is thrown, value is just skipped).
*/
protected HashSet<String> _ignorableProps;
-
+
/**
* Object that will handle value instantiation for the bean type.
*/
@@ -75,7 +80,7 @@
* bean type.
*/
protected ObjectIdReader _objectIdReader;
-
+
/**
* Fallback setter used for handling any properties that are not
* mapped to regular setters. If setter is not null, it will be
@@ -105,12 +110,13 @@
/* Life-cycle: construction
/**********************************************************
*/
-
+
public BeanDeserializerBuilder(BeanDescription beanDesc,
- DeserializationConfig config)
+ DeserializationContext ctxt)
{
_beanDesc = beanDesc;
- _config = config;
+ _context = ctxt;
+ _config = ctxt.getConfig();
}
/**
@@ -120,6 +126,7 @@
protected BeanDeserializerBuilder(BeanDeserializerBuilder src)
{
_beanDesc = src._beanDesc;
+ _context = src._context;
_config = src._config;
// let's make copy of properties
@@ -130,10 +137,10 @@
_ignorableProps = src._ignorableProps;
_valueInstantiator = src._valueInstantiator;
_objectIdReader = src._objectIdReader;
-
+
_anySetter = src._anySetter;
_ignoreAllUnknown = src._ignoreAllUnknown;
-
+
_buildMethod = src._buildMethod;
_builderConfig = src._builderConfig;
}
@@ -146,7 +153,7 @@
private static <T> List<T> _copy(List<T> src) {
return (src == null) ? null : new ArrayList<T>(src);
}
-
+
/*
/**********************************************************
/* Life-cycle: state modification (adders, setters)
@@ -188,12 +195,15 @@
// access set early; unfortunate, but since it works....
prop.fixAccess(_config);
_backRefProperties.put(referenceName, prop);
- // also: if we had property with same name, actually remove it
+ // 16-Jan-2018, tatu: As per [databind#1878] we may want to leave it as is, to allow
+ // population for cases of "wrong direction", traversing parent first
+ // If this causes problems should probably instead include in "ignored properties" list
+ // Alternatively could also extend annotation to allow/disallow explicit value from input
+ /*
if (_properties != null) {
_properties.remove(prop.getName());
}
- // ??? 23-Jul-2012, tatu: Should it be included in list of all properties?
- // For now, won't add, since it is inferred, not explicit...
+ */
}
public void addInjectable(PropertyName propName, JavaType propType,
@@ -208,8 +218,7 @@
if (fixAccess) {
member.fixAccess(forceAccess);
}
- _injectables.add(new ValueInjector(propName, propType,
- contextAnnotations, member, valueId));
+ _injectables.add(new ValueInjector(propName, propType, member, valueId));
}
/**
@@ -318,6 +327,13 @@
return _builderConfig;
}
+ /**
+ * @since 2.9.4
+ */
+ public boolean hasIgnorable(String name) {
+ return (_ignorableProps != null) && _ignorableProps.contains(name);
+ }
+
/*
/**********************************************************
/* Build method(s)
@@ -332,9 +348,9 @@
{
Collection<SettableBeanProperty> props = _properties.values();
_fixAccess(props);
-
BeanPropertyMap propertyMap = BeanPropertyMap.construct(props,
- _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES),
+ _collectAliases(props));
propertyMap.assignIndexes();
// view processing must be enabled if:
@@ -373,22 +389,22 @@
* @since 2.0
*/
public AbstractDeserializer buildAbstract() {
- return new AbstractDeserializer(this, _beanDesc, _backRefProperties);
+ return new AbstractDeserializer(this, _beanDesc, _backRefProperties, _properties);
}
/**
* Method for constructing a specialized deserializer that uses
* additional external Builder object during data binding.
*/
- public JsonDeserializer<?> buildBuilderBased(JavaType valueType,
- String expBuildMethodName)
+ public JsonDeserializer<?> buildBuilderBased(JavaType valueType, String expBuildMethodName)
+ throws JsonMappingException
{
// First: validation; must have build method that returns compatible type
if (_buildMethod == null) {
// as per [databind#777], allow empty name
if (!expBuildMethodName.isEmpty()) {
- throw new IllegalArgumentException(String.format(
- "Builder class %s does not have build method (name: '%s')",
+ _context.reportBadDefinition(_beanDesc.getType(),
+ String.format("Builder class %s does not have build method (name: '%s')",
_beanDesc.getBeanClass().getName(),
expBuildMethodName));
}
@@ -399,16 +415,19 @@
if ((rawBuildType != rawValueType)
&& !rawBuildType.isAssignableFrom(rawValueType)
&& !rawValueType.isAssignableFrom(rawBuildType)) {
- throw new IllegalArgumentException("Build method '"+_buildMethod.getFullName()
- +" has bad return type ("+rawBuildType.getName()
- +"), not compatible with POJO type ("+valueType.getRawClass().getName()+")");
+ _context.reportBadDefinition(_beanDesc.getType(),
+ String.format("Build method '%s' has wrong return type (%s), not compatible with POJO type (%s)",
+ _buildMethod.getFullName(),
+ rawBuildType.getName(),
+ valueType.getRawClass().getName()));
}
}
// And if so, we can try building the deserializer
Collection<SettableBeanProperty> props = _properties.values();
_fixAccess(props);
BeanPropertyMap propertyMap = BeanPropertyMap.construct(props,
- _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES),
+ _collectAliases(props));
propertyMap.assignIndexes();
boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
@@ -423,17 +442,15 @@
}
if (_objectIdReader != null) {
- /* 18-Nov-2012, tatu: May or may not have annotations for id property;
- * but no easy access. But hard to see id property being optional,
- * so let's consider required at this point.
- */
+ // May or may not have annotations for id property; but no easy access.
+ // But hard to see id property being optional, so let's consider required at this point.
ObjectIdValueProperty prop = new ObjectIdValueProperty(_objectIdReader,
PropertyMetadata.STD_REQUIRED);
propertyMap = propertyMap.withProperty(prop);
}
return new BuilderBasedDeserializer(this,
- _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown,
+ _beanDesc, valueType, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown,
anyViews);
}
@@ -443,7 +460,7 @@
/**********************************************************
*/
- private void _fixAccess(Collection<SettableBeanProperty> mainProps)
+ protected void _fixAccess(Collection<SettableBeanProperty> mainProps)
{
/* 07-Sep-2016, tatu: Ideally we should be able to avoid forcing
* access to properties that are likely ignored, but due to
@@ -482,4 +499,26 @@
_buildMethod.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
}
+
+ protected Map<String,List<PropertyName>> _collectAliases(Collection<SettableBeanProperty> props)
+ {
+ Map<String,List<PropertyName>> mapping = null;
+ AnnotationIntrospector intr = _config.getAnnotationIntrospector();
+ if (intr != null) {
+ for (SettableBeanProperty prop : props) {
+ List<PropertyName> aliases = intr.findPropertyAliases(prop.getMember());
+ if ((aliases == null) || aliases.isEmpty()) {
+ continue;
+ }
+ if (mapping == null) {
+ mapping = new HashMap<>();
+ }
+ mapping.put(prop.getName(), aliases);
+ }
+ }
+ if (mapping == null) {
+ return Collections.emptyMap();
+ }
+ return mapping;
+ }
}
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 d93745c..1f02d70 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
@@ -3,13 +3,12 @@
import java.util.*;
import com.fasterxml.jackson.annotation.*;
-
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
-import com.fasterxml.jackson.databind.cfg.ConfigOverride;
import com.fasterxml.jackson.databind.deser.impl.*;
import com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer;
+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;
@@ -38,8 +37,6 @@
*/
private final static Class<?>[] INIT_CAUSE_PARAMS = new Class<?>[] { Throwable.class };
- private final static Class<?>[] NO_VIEWS = new Class<?>[0];
-
/*
/**********************************************************
/* Life-cycle
@@ -74,11 +71,7 @@
* Instead, let's actually just throw an error if this method is called when subtype
* has not properly overridden this method; this to indicate problem as soon as possible.
*/
- if (getClass() != BeanDeserializerFactory.class) {
- throw new IllegalStateException("Subtype of BeanDeserializerFactory ("+getClass().getName()
- +") has not properly overridden method 'withAdditionalDeserializers': can not instantiate subtype with "
- +"additional deserializer definitions");
- }
+ ClassUtil.verifyMustOverride(BeanDeserializerFactory.class, this, "withConfig");
return new BeanDeserializerFactory(config);
}
@@ -111,9 +104,9 @@
if (type.isThrowable()) {
return buildThrowableDeserializer(ctxt, type, beanDesc);
}
- /* Or, for abstract types, may have alternate means for resolution
- * (defaulting, materialization)
- */
+ // Or, for abstract types, may have alternate means for resolution
+ // (defaulting, materialization)
+
// 29-Nov-2015, tatu: Also, filter out calls to primitive types, they are
// not something we could materialize anything for
if (type.isAbstract() && !type.isPrimitive() && !type.isEnumType()) {
@@ -145,17 +138,16 @@
}
@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);
BeanDescription builderDesc = ctxt.getConfig().introspectForBuilder(builderType);
return buildBuilderBasedDeserializer(ctxt, valueType, builderDesc);
}
-
+
/**
* Method called by {@link BeanDeserializerFactory} to see if there might be a standard
* deserializer registered for given type.
@@ -222,6 +214,13 @@
valueInstantiator = findValueInstantiator(ctxt, beanDesc);
} catch (NoClassDefFoundError error) {
return new ErrorThrowingDeserializer(error);
+ } catch (IllegalArgumentException e) {
+ // 05-Apr-2017, tatu: Although it might appear cleaner to require collector
+ // to throw proper exception, it doesn't actually have reference to this
+ // instance so...
+ throw InvalidDefinitionException.from(ctxt.getParser(),
+ ClassUtil.exceptionMessage(e),
+ beanDesc, null);
}
BeanDeserializerBuilder builder = constructBeanDeserializerBuilder(ctxt, beanDesc);
builder.setValueInstantiator(valueInstantiator);
@@ -230,7 +229,7 @@
addObjectIdReader(ctxt, beanDesc, builder);
// managed/back reference fields/setters need special handling... first part
- addReferenceProperties(ctxt, beanDesc, builder);
+ addBackReferenceProperties(ctxt, beanDesc, builder);
addInjectables(ctxt, beanDesc, builder);
final DeserializationConfig config = ctxt.getConfig();
@@ -246,8 +245,8 @@
} else {
deserializer = builder.build();
}
-
- // [JACKSON-440]: may have modifier(s) that wants to modify or replace serializer we just built:
+ // may have modifier(s) that wants to modify or replace serializer we just built
+ // (note that `resolve()` and `createContextual()` called later on)
if (_factoryConfig.hasDeserializerModifiers()) {
for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) {
deserializer = mod.modifyDeserializer(config, beanDesc, deserializer);
@@ -268,8 +267,20 @@
DeserializationContext ctxt, JavaType valueType, BeanDescription builderDesc)
throws JsonMappingException
{
- // Creators, anyone? (to create builder itself)
- ValueInstantiator valueInstantiator = findValueInstantiator(ctxt, builderDesc);
+ // Creators, anyone? (to create builder itself)
+ ValueInstantiator valueInstantiator;
+ try {
+ valueInstantiator = findValueInstantiator(ctxt, builderDesc);
+ } catch (NoClassDefFoundError error) {
+ return new ErrorThrowingDeserializer(error);
+ } catch (IllegalArgumentException e) {
+ // 05-Apr-2017, tatu: Although it might appear cleaner to require collector
+ // to throw proper exception, it doesn't actually have reference to this
+ // instance so...
+ throw InvalidDefinitionException.from(ctxt.getParser(),
+ ClassUtil.exceptionMessage(e),
+ builderDesc, null);
+ }
final DeserializationConfig config = ctxt.getConfig();
BeanDeserializerBuilder builder = constructBeanDeserializerBuilder(ctxt, builderDesc);
builder.setValueInstantiator(valueInstantiator);
@@ -278,12 +289,12 @@
addObjectIdReader(ctxt, builderDesc, builder);
// managed/back reference fields/setters need special handling... first part
- addReferenceProperties(ctxt, builderDesc, builder);
+ addBackReferenceProperties(ctxt, builderDesc, builder);
addInjectables(ctxt, builderDesc, builder);
JsonPOJOBuilder.Value builderConfig = builderDesc.findPOJOBuilderConfig();
final String buildMethodName = (builderConfig == null) ?
- "build" : builderConfig.buildMethodName;
+ JsonPOJOBuilder.DEFAULT_BUILD_METHOD : builderConfig.buildMethodName;
// and lastly, find build method to use:
AnnotatedMethod buildMethod = builderDesc.findMethod(buildMethodName, null);
@@ -332,7 +343,7 @@
idProp = builder.findProperty(propName);
if (idProp == null) {
throw new IllegalArgumentException("Invalid Object Id definition for "
- +beanDesc.getBeanClass().getName()+": can not find property with name '"+propName+"'");
+ +beanDesc.getBeanClass().getName()+": cannot find property with name '"+propName+"'");
}
idType = idProp.getType();
gen = new PropertyBasedObjectIdGenerator(objectIdInfo.getScope());
@@ -362,9 +373,7 @@
// (and assume there won't be any back references)
// But then let's decorate things a bit
- /* To resolve [JACKSON-95], need to add "initCause" as setter
- * for exceptions (sub-classes of Throwable).
- */
+ // Need to add "initCause" as setter for exceptions (sub-classes of Throwable).
AnnotatedMethod am = beanDesc.findMethod("initCause", INIT_CAUSE_PARAMS);
if (am != null) { // should never be null
SimpleBeanPropertyDefinition propDef = SimpleBeanPropertyDefinition.construct(ctxt.getConfig(), am,
@@ -372,10 +381,8 @@
SettableBeanProperty prop = constructSettableProperty(ctxt, beanDesc, propDef,
am.getParameterType(0));
if (prop != null) {
- /* 21-Aug-2011, tatus: We may actually have found 'cause' property
- * to set... but let's replace it just in case,
- * otherwise can end up with odd errors.
- */
+ // 21-Aug-2011, tatus: We may actually have found 'cause' property
+ // to set... but let's replace it just in case, otherwise can end up with odd errors.
builder.addOrReplaceProperty(prop, true);
}
}
@@ -384,10 +391,10 @@
builder.addIgnorable("localizedMessage");
// Java 7 also added "getSuppressed", skip if we have such data:
builder.addIgnorable("suppressed");
- /* As well as "message": it will be passed via constructor,
- * as there's no 'setMessage()' method
- */
- builder.addIgnorable("message");
+ // As well as "message": it will be passed via constructor,
+ // as there's no 'setMessage()' method
+ // 23-Jan-2018, tatu: ... although there MAY be Creator Property... which is problematic
+// builder.addIgnorable("message");
// update builder now that all information is in?
if (_factoryConfig.hasDeserializerModifiers()) {
@@ -427,7 +434,7 @@
*/
protected BeanDeserializerBuilder constructBeanDeserializerBuilder(DeserializationContext ctxt,
BeanDescription beanDesc) {
- return new BeanDeserializerBuilder(beanDesc, ctxt.getConfig());
+ return new BeanDeserializerBuilder(beanDesc, ctxt);
}
/**
@@ -446,7 +453,7 @@
? builder.getValueInstantiator().getFromObjectArguments(ctxt.getConfig())
: null;
final boolean hasCreatorProps = (creatorProps != null);
-
+
// 01-May-2016, tatu: Which base type to use here gets tricky, since
// it may often make most sense to use general type for overrides,
// but what we have here may be more specific impl type. But for now
@@ -455,7 +462,6 @@
.getDefaultPropertyIgnorals(beanDesc.getBeanClass(),
beanDesc.getClassInfo());
Set<String> ignored;
-
if (ignorals != null) {
boolean ignoreAny = ignorals.getIgnoreUnknown();
builder.setIgnoreUnknownProperties(ignoreAny);
@@ -469,20 +475,12 @@
}
// Also, do we have a fallback "any" setter?
- AnnotatedMethod anySetterMethod = beanDesc.findAnySetter();
- AnnotatedMember anySetterField = null;
- if (anySetterMethod != null) {
- builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetterMethod));
- }
- else {
- anySetterField = beanDesc.findAnySetterField();
- if(anySetterField != null) {
- builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetterField));
- }
- }
- // NOTE: we do NOT add @JsonIgnore'd properties into blocked ones if there's any-setter
- // Implicit ones via @JsonIgnore and equivalent?
- if (anySetterMethod == null && anySetterField == null) {
+ AnnotatedMember anySetter = beanDesc.findAnySetterAccessor();
+ if (anySetter != null) {
+ builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter));
+ } else {
+ // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block
+ // properties regardless, for now only consider unless there's any setter...
Collection<String> ignored2 = beanDesc.getIgnoredPropertyNames();
if (ignored2 != null) {
for (String propName : ignored2) {
@@ -498,39 +496,53 @@
// Ok: let's then filter out property definitions
List<BeanPropertyDefinition> propDefs = filterBeanProps(ctxt,
beanDesc, builder, beanDesc.findProperties(), ignored);
-
// After which we can let custom code change the set
if (_factoryConfig.hasDeserializerModifiers()) {
for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) {
propDefs = mod.updateProperties(ctxt.getConfig(), beanDesc, propDefs);
}
}
-
+
// At which point we still have all kinds of properties; not all with mutators:
for (BeanPropertyDefinition propDef : propDefs) {
SettableBeanProperty prop = null;
- /* 18-Oct-2013, tatu: Although constructor parameters have highest precedence,
- * we need to do linkage (as per [databind#318]), and so need to start with
- * other types, and only then create constructor parameter, if any.
- */
+
+ // 18-Oct-2013, tatu: Although constructor parameters have highest precedence,
+ // we need to do linkage (as per [databind#318]), and so need to start with
+ // other types, and only then create constructor parameter, if any.
if (propDef.hasSetter()) {
- JavaType propertyType = propDef.getSetter().getParameterType(0);
+ AnnotatedMethod setter = propDef.getSetter();
+ JavaType propertyType = setter.getParameterType(0);
prop = constructSettableProperty(ctxt, beanDesc, propDef, propertyType);
} else if (propDef.hasField()) {
- JavaType propertyType = propDef.getField().getType();
+ AnnotatedField field = propDef.getField();
+ JavaType propertyType = field.getType();
prop = constructSettableProperty(ctxt, beanDesc, propDef, propertyType);
- } else if (useGettersAsSetters && propDef.hasGetter()) {
- /* May also need to consider getters
- * for Map/Collection properties; but with lowest precedence
- */
+ } else {
+ // NOTE: specifically getter, since field was already checked above
AnnotatedMethod getter = propDef.getGetter();
- // should only consider Collections and Maps, for now?
- Class<?> rawPropertyType = getter.getRawType();
- if (Collection.class.isAssignableFrom(rawPropertyType)
- || Map.class.isAssignableFrom(rawPropertyType)) {
- prop = constructSetterlessProperty(ctxt, beanDesc, propDef);
+ if (getter != null) {
+ if (useGettersAsSetters && _isSetterlessType(getter.getRawType())) {
+ // 23-Jan-2018, tatu: As per [databind#1805], need to ensure we don't
+ // accidentally sneak in getter-as-setter for `READ_ONLY` properties
+ if (builder.hasIgnorable(propDef.getName())) {
+ ;
+ } else {
+ prop = constructSetterlessProperty(ctxt, beanDesc, propDef);
+ }
+ } else if (!propDef.hasConstructorParameter()) {
+ PropertyMetadata md = propDef.getMetadata();
+ // 25-Oct-2016, tatu: If merging enabled, might not need setter.
+ // We cannot quite support this with creator parameters; in theory
+ // possibly, but right not not due to complexities of routing, so
+ // just prevent
+ if (md.getMergeInfo() != null) {
+ prop = constructSetterlessProperty(ctxt, beanDesc, propDef);
+ }
+ }
}
}
+
// 25-Sep-2014, tatu: No point in finding constructor parameters for abstract types
// (since they are never used anyway)
if (hasCreatorProps && propDef.hasConstructorParameter()) {
@@ -562,26 +574,34 @@
if (prop != null) {
cprop.setFallbackSetter(prop);
}
- prop = cprop;
+ Class<?>[] views = propDef.findViews();
+ if (views == null) {
+ views = beanDesc.findDefaultViews();
+ }
+ cprop.setViews(views);
builder.addCreatorProperty(cprop);
continue;
}
-
if (prop != null) {
+ // one more thing before adding to builder: copy any metadata
Class<?>[] views = propDef.findViews();
if (views == null) {
- // one more twist: if default inclusion disabled, need to force empty set of views
- if (!ctxt.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
- views = NO_VIEWS;
- }
+ views = beanDesc.findDefaultViews();
}
- // one more thing before adding to builder: copy any metadata
prop.setViews(views);
builder.addProperty(prop);
}
}
}
-
+
+ private boolean _isSetterlessType(Class<?> rawType) {
+ // May also need to consider getters
+ // for Map/Collection properties; but with lowest precedence
+ // should only consider Collections and Maps, for now?
+ return Collection.class.isAssignableFrom(rawType)
+ || Map.class.isAssignableFrom(rawType);
+ }
+
/**
* Helper method called to filter out explicit ignored properties,
* as well as properties that have "ignorable types".
@@ -604,16 +624,10 @@
continue;
}
if (!property.hasConstructorParameter()) { // never skip constructor params
- Class<?> rawPropertyType = null;
- if (property.hasSetter()) {
- rawPropertyType = property.getSetter().getRawParameterType(0);
- } else if (property.hasField()) {
- rawPropertyType = property.getField().getRawType();
- }
-
+ Class<?> rawPropertyType = property.getRawPrimaryType();
// Some types are declared as ignorable as well
if ((rawPropertyType != null)
- && isIgnorableType(ctxt.getConfig(), beanDesc, rawPropertyType, ignoredTypes)) {
+ && isIgnorableType(ctxt.getConfig(), property, rawPropertyType, ignoredTypes)) {
// important: make ignorable, to avoid errors if value is actually seen
builder.addIgnorable(name);
continue;
@@ -627,17 +641,19 @@
/**
* Method that will find if bean has any managed- or back-reference properties,
* and if so add them to bean, to be linked during resolution phase.
+ *
+ * @since 2.9
*/
- protected void addReferenceProperties(DeserializationContext ctxt,
+ protected void addBackReferenceProperties(DeserializationContext ctxt,
BeanDescription beanDesc, BeanDeserializerBuilder builder)
throws JsonMappingException
{
// and then back references, not necessarily found as regular properties
- Map<String,AnnotatedMember> refs = beanDesc.findBackReferenceProperties();
- if (refs != null) {
- for (Map.Entry<String, AnnotatedMember> en : refs.entrySet()) {
- String name = en.getKey();
- AnnotatedMember m = en.getValue();
+ List<BeanPropertyDefinition> refProps = beanDesc.findBackReferences();
+ if (refProps != null) {
+ for (BeanPropertyDefinition refProp : refProps) {
+ /*
+ AnnotatedMember m = refProp.getMutator();
JavaType type;
if (m instanceof AnnotatedMethod) {
type = ((AnnotatedMethod) m).getParameterType(0);
@@ -647,18 +663,26 @@
// work through constructors; but let's at least indicate the issue for now
if (m instanceof AnnotatedParameter) {
ctxt.reportBadTypeDefinition(beanDesc,
-"Can not bind back references as Creator parameters: type %s (reference '%s', parameter index #%d)",
-beanDesc.getBeanClass().getName(), name, ((AnnotatedParameter) m).getIndex());
+"Cannot bind back reference using Creator parameter (reference '%s', parameter index #%d)",
+name, ((AnnotatedParameter) m).getIndex());
}
}
- SimpleBeanPropertyDefinition propDef = SimpleBeanPropertyDefinition.construct(
- ctxt.getConfig(), m, PropertyName.construct(name));
- builder.addBackReferenceProperty(name, constructSettableProperty(ctxt,
- beanDesc, propDef, type));
+ */
+ String refName = refProp.findReferenceName();
+ builder.addBackReferenceProperty(refName, constructSettableProperty(ctxt,
+ beanDesc, refProp, refProp.getPrimaryType()));
}
}
}
+ @Deprecated // since 2.9 (rename)
+ protected void addReferenceProperties(DeserializationContext ctxt,
+ BeanDescription beanDesc, BeanDeserializerBuilder builder)
+ throws JsonMappingException
+ {
+ addBackReferenceProperties(ctxt, beanDesc, builder);
+ }
+
/**
* Method called locate all members used for value injection (if any),
* constructor {@link com.fasterxml.jackson.databind.deser.impl.ValueInjector} instances, and add them to builder.
@@ -692,31 +716,58 @@
throws JsonMappingException
{
//find the java type based on the annotated setter method or setter field
- JavaType type = null;
+ BeanProperty prop;
+ JavaType keyType;
+ JavaType valueType;
+
if (mutator instanceof AnnotatedMethod) {
// we know it's a 2-arg method, second arg is the value
- type = ((AnnotatedMethod) mutator).getParameterType(1);
+ AnnotatedMethod am = (AnnotatedMethod) mutator;
+ keyType = am.getParameterType(0);
+ valueType = am.getParameterType(1);
+ valueType = resolveMemberAndTypeAnnotations(ctxt, mutator, valueType);
+ prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()),
+ valueType, null, mutator,
+ PropertyMetadata.STD_OPTIONAL);
+
} else if (mutator instanceof AnnotatedField) {
+ AnnotatedField af = (AnnotatedField) mutator;
// get the type from the content type of the map object
- type = ((AnnotatedField) mutator).getType().getContentType();
+ JavaType mapType = af.getType();
+ mapType = resolveMemberAndTypeAnnotations(ctxt, mutator, mapType);
+ keyType = mapType.getKeyType();
+ valueType = mapType.getContentType();
+ prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()),
+ mapType, null, mutator, PropertyMetadata.STD_OPTIONAL);
+ } else {
+ return ctxt.reportBadDefinition(beanDesc.getType(), String.format(
+ "Unrecognized mutator type for any setter: %s", mutator.getClass()));
}
- // First: various annotations on type itself, as well as type-overrides
- // on accessor need to be resolved
- type = resolveMemberAndTypeAnnotations(ctxt, mutator, type);
- BeanProperty.Std prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()),
- type, null, beanDesc.getClassAnnotations(), mutator,
- PropertyMetadata.STD_OPTIONAL);
+ // First: see if there are explicitly specified
// and then possible direct deserializer override on accessor
- JsonDeserializer<Object> deser = findDeserializerFromAnnotation(ctxt, mutator);
+ KeyDeserializer keyDeser = findKeyDeserializerFromAnnotation(ctxt, mutator);
+ if (keyDeser == null) {
+ keyDeser = keyType.getValueHandler();
+ }
+ if (keyDeser == null) {
+ keyDeser = ctxt.findKeyDeserializer(keyType, prop);
+ } else {
+ if (keyDeser instanceof ContextualKeyDeserializer) {
+ keyDeser = ((ContextualKeyDeserializer) keyDeser)
+ .createContextual(ctxt, prop);
+ }
+ }
+ JsonDeserializer<Object> deser = findContentDeserializerFromAnnotation(ctxt, mutator);
if (deser == null) {
- deser = type.getValueHandler();
+ deser = valueType.getValueHandler();
}
if (deser != null) {
// As per [databind#462] need to ensure we contextualize deserializer before passing it on
- deser = (JsonDeserializer<Object>) ctxt.handlePrimaryContextualization(deser, prop, type);
+ deser = (JsonDeserializer<Object>) ctxt.handlePrimaryContextualization(deser, prop, valueType);
}
- TypeDeserializer typeDeser = type.getTypeHandler();
- return new SettableAnyProperty(prop, mutator, type, deser, typeDeser);
+ TypeDeserializer typeDeser = valueType.getTypeHandler();
+ return new SettableAnyProperty(prop, mutator, valueType,
+ keyDeser, deser, typeDeser);
}
/**
@@ -803,7 +854,7 @@
/**
* Helper method used to skip processing for types that we know
- * can not be (i.e. are never consider to be) beans:
+ * cannot be (i.e. are never consider to be) beans:
* things like primitives, Arrays, Enums, and proxy types.
*<p>
* Note that usually we shouldn't really be getting these sort of
@@ -813,17 +864,17 @@
{
String typeStr = ClassUtil.canBeABeanType(type);
if (typeStr != null) {
- throw new IllegalArgumentException("Can not deserialize Class "+type.getName()+" (of type "+typeStr+") as a Bean");
+ throw new IllegalArgumentException("Cannot deserialize Class "+type.getName()+" (of type "+typeStr+") as a Bean");
}
if (ClassUtil.isProxyType(type)) {
- throw new IllegalArgumentException("Can not deserialize Proxy class "+type.getName()+" as a Bean");
+ throw new IllegalArgumentException("Cannot deserialize Proxy class "+type.getName()+" as a Bean");
}
/* also: can't deserialize some local classes: static are ok; in-method not;
* other non-static inner classes are ok
*/
typeStr = ClassUtil.isLocalType(type, true);
if (typeStr != null) {
- throw new IllegalArgumentException("Can not deserialize Class "+type.getName()+" (of type "+typeStr+") as a Bean");
+ throw new IllegalArgumentException("Cannot deserialize Class "+type.getName()+" (of type "+typeStr+") as a Bean");
}
return true;
}
@@ -832,24 +883,26 @@
* Helper method that will check whether given raw type is marked as always ignorable
* (for purpose of ignoring properties with type)
*/
- protected boolean isIgnorableType(DeserializationConfig config, BeanDescription beanDesc,
+ protected boolean isIgnorableType(DeserializationConfig config, BeanPropertyDefinition propDef,
Class<?> type, Map<Class<?>,Boolean> ignoredTypes)
{
Boolean status = ignoredTypes.get(type);
if (status != null) {
return status.booleanValue();
}
- // 21-Apr-2016, tatu: For 2.8, can specify config overrides
- ConfigOverride override = config.findConfigOverride(type);
- if (override != null) {
- status = override.getIsIgnoredType();
- }
- if (status == null) {
- BeanDescription desc = config.introspectClassAnnotations(type);
- status = config.getAnnotationIntrospector().isIgnorableType(desc.getClassInfo());
- // We default to 'false', i.e. not ignorable
+ // 22-Oct-2016, tatu: Slight check to skip primitives, String
+ if ((type == String.class) || type.isPrimitive()) {
+ status = Boolean.FALSE;
+ } else {
+ // 21-Apr-2016, tatu: For 2.8, can specify config overrides
+ status = config.getConfigOverride(type).getIsIgnoredType();
if (status == null) {
- status = Boolean.FALSE;
+ BeanDescription desc = config.introspectClassAnnotations(type);
+ status = config.getAnnotationIntrospector().isIgnorableType(desc.getClassInfo());
+ // We default to 'false', i.e. not ignorable
+ if (status == null) {
+ status = Boolean.FALSE;
+ }
}
}
ignoredTypes.put(type, status);
@@ -863,6 +916,6 @@
BeanDescription beanDesc)
throws JsonMappingException
{
- SubTypeValidator.instance().validateSubType(ctxt, type);
+ SubTypeValidator.instance().validateSubType(ctxt, type, beanDesc);
}
}
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 817e29e..4f7b345 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java
@@ -27,6 +27,14 @@
protected final AnnotatedMethod _buildMethod;
+ /**
+ * Type that the builder will produce, target type; as opposed to
+ * `handledType()` which refers to Builder class.
+ *
+ * @since 2.9
+ */
+ protected final JavaType _targetType;
+
/*
/**********************************************************
/* Life-cycle, construction, initialization
@@ -37,22 +45,38 @@
* Constructor used by {@link BeanDeserializerBuilder}.
*/
public BuilderBasedDeserializer(BeanDeserializerBuilder builder,
- BeanDescription beanDesc,
+ BeanDescription beanDesc, JavaType targetType,
BeanPropertyMap properties, Map<String, SettableBeanProperty> backRefs,
Set<String> ignorableProps, boolean ignoreAllUnknown,
boolean hasViews)
{
super(builder, beanDesc, properties, backRefs,
ignorableProps, ignoreAllUnknown, hasViews);
+ _targetType = targetType;
_buildMethod = builder.getBuildMethod();
- // 05-Mar-2012, tatu: Can not really make Object Ids work with builders, not yet anyway
+ // 05-Mar-2012, tatu: Cannot really make Object Ids work with builders, not yet anyway
if (_objectIdReader != null) {
- throw new IllegalArgumentException("Can not use Object Id with Builder-based deserialization (type "
+ throw new IllegalArgumentException("Cannot use Object Id with Builder-based deserialization (type "
+beanDesc.getType()+")");
}
}
/**
+ * @deprecated Since 2.9
+ */
+ @Deprecated
+ public BuilderBasedDeserializer(BeanDeserializerBuilder builder,
+ BeanDescription beanDesc,
+ BeanPropertyMap properties, Map<String, SettableBeanProperty> backRefs,
+ Set<String> ignorableProps, boolean ignoreAllUnknown,
+ boolean hasViews)
+ {
+ this(builder, beanDesc,
+ beanDesc.getType(), // Wrong! But got no access via `BeanDeserializerBuilder`
+ properties, backRefs, ignorableProps, ignoreAllUnknown, hasViews);
+ }
+
+ /**
* Copy-constructor that can be used by sub-classes to allow
* copy-on-write styling copying of settings of an existing instance.
*/
@@ -65,26 +89,31 @@
{
super(src, ignoreAllUnknown);
_buildMethod = src._buildMethod;
+ _targetType = src._targetType;
}
protected BuilderBasedDeserializer(BuilderBasedDeserializer src, NameTransformer unwrapper) {
super(src, unwrapper);
_buildMethod = src._buildMethod;
+ _targetType = src._targetType;
}
public BuilderBasedDeserializer(BuilderBasedDeserializer src, ObjectIdReader oir) {
super(src, oir);
_buildMethod = src._buildMethod;
+ _targetType = src._targetType;
}
public BuilderBasedDeserializer(BuilderBasedDeserializer src, Set<String> ignorableProps) {
super(src, ignorableProps);
_buildMethod = src._buildMethod;
+ _targetType = src._targetType;
}
public BuilderBasedDeserializer(BuilderBasedDeserializer src, BeanPropertyMap props) {
super(src, props);
_buildMethod = src._buildMethod;
+ _targetType = src._targetType;
}
@Override
@@ -115,7 +144,7 @@
@Override
protected BeanDeserializerBase asArrayDeserializer() {
SettableBeanProperty[] props = _beanProperties.getPropertiesInInsertionOrder();
- return new BeanAsArrayBuilderDeserializer(this, props, _buildMethod);
+ return new BeanAsArrayBuilderDeserializer(this, _targetType, props, _buildMethod);
}
/*
@@ -124,7 +153,19 @@
/**********************************************************
*/
- protected final Object finishBuild(DeserializationContext ctxt, Object builder)
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ // 26-Oct-2016, tatu: No, we can't merge Builder-based POJOs as of now
+ return Boolean.FALSE;
+ }
+
+ /*
+ /**********************************************************
+ /* JsonDeserializer implementation
+ /**********************************************************
+ */
+
+ protected Object finishBuild(DeserializationContext ctxt, Object builder)
throws IOException
{
// As per [databind#777], allow returning builder itself
@@ -132,7 +173,7 @@
return builder;
}
try {
- return _buildMethod.getMember().invoke(builder);
+ return _buildMethod.getMember().invoke(builder, (Object[]) null);
} catch (Exception e) {
return wrapInstantiationProblem(e, ctxt);
}
@@ -142,42 +183,38 @@
* Main deserialization method for bean-based objects (POJOs).
*/
@Override
- public final Object deserialize(JsonParser p, DeserializationContext ctxt)
+ public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- JsonToken t = p.getCurrentToken();
-
// common case first:
- if (t == JsonToken.START_OBJECT) {
- t = p.nextToken();
+ if (p.isExpectedStartObjectToken()) {
+ JsonToken t = p.nextToken();
if (_vanillaProcessing) {
- return finishBuild(ctxt, vanillaDeserialize(p, ctxt, t));
+ return finishBuild(ctxt, vanillaDeserialize(p, ctxt, t));
}
Object builder = deserializeFromObject(p, ctxt);
return finishBuild(ctxt, builder);
}
// and then others, generally requiring use of @JsonCreator
- if (t != null) {
- switch (t) {
- case VALUE_STRING:
- return finishBuild(ctxt, deserializeFromString(p, ctxt));
- case VALUE_NUMBER_INT:
- return finishBuild(ctxt, deserializeFromNumber(p, ctxt));
- case VALUE_NUMBER_FLOAT:
- return finishBuild(ctxt, deserializeFromDouble(p, ctxt));
- case VALUE_EMBEDDED_OBJECT:
- return p.getEmbeddedObject();
- case VALUE_TRUE:
- case VALUE_FALSE:
- return finishBuild(ctxt, deserializeFromBoolean(p, ctxt));
- case START_ARRAY:
- // these only work if there's a (delegating) creator...
- return finishBuild(ctxt, deserializeFromArray(p, ctxt));
- case FIELD_NAME:
- case END_OBJECT:
- return finishBuild(ctxt, deserializeFromObject(p, ctxt));
- default:
- }
+ switch (p.getCurrentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ return finishBuild(ctxt, deserializeFromString(p, ctxt));
+ case JsonTokenId.ID_NUMBER_INT:
+ return finishBuild(ctxt, deserializeFromNumber(p, ctxt));
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ return finishBuild(ctxt, deserializeFromDouble(p, ctxt));
+ case JsonTokenId.ID_EMBEDDED_OBJECT:
+ return p.getEmbeddedObject();
+ case JsonTokenId.ID_TRUE:
+ case JsonTokenId.ID_FALSE:
+ return finishBuild(ctxt, deserializeFromBoolean(p, ctxt));
+ case JsonTokenId.ID_START_ARRAY:
+ // these only work if there's a (delegating) creator...
+ return finishBuild(ctxt, deserializeFromArray(p, ctxt));
+ case JsonTokenId.ID_FIELD_NAME:
+ case JsonTokenId.ID_END_OBJECT:
+ return finishBuild(ctxt, deserializeFromObject(p, ctxt));
+ default:
}
return ctxt.handleUnexpectedToken(handledType(), p);
}
@@ -189,13 +226,22 @@
*/
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt,
- Object builder)
- throws IOException
+ Object value) throws IOException
{
- /* Important: we call separate method which does NOT call
- * 'finishBuild()', to avoid problems with recursion
- */
- return finishBuild(ctxt, _deserialize(p, ctxt, builder));
+ // 26-Oct-2016, tatu: I cannot see any of making this actually
+ // work correctly, so let's indicate problem right away
+ JavaType valueType = _targetType;
+ // Did they try to give us builder?
+ Class<?> builderRawType = handledType();
+ Class<?> instRawType = value.getClass();
+ if (builderRawType.isAssignableFrom(instRawType)) {
+ return ctxt.reportBadDefinition(valueType, String.format(
+ "Deserialization of %s by passing existing Builder (%s) instance not supported",
+ valueType, builderRawType.getName()));
+ }
+ return ctxt.reportBadDefinition(valueType, String.format(
+ "Deserialization of %s by passing existing instance (of %s) not supported",
+ valueType, instRawType.getName()));
}
/*
@@ -204,59 +250,16 @@
/**********************************************************
*/
- protected final Object _deserialize(JsonParser p,
- DeserializationContext ctxt, Object builder)
- throws IOException, JsonProcessingException
- {
- if (_injectables != null) {
- injectValues(ctxt, builder);
- }
- if (_unwrappedPropertyHandler != null) {
- return deserializeWithUnwrapped(p, ctxt, builder);
- }
- if (_externalTypeIdHandler != null) {
- return deserializeWithExternalTypeId(p, ctxt, builder);
- }
- if (_needViewProcesing) {
- Class<?> view = ctxt.getActiveView();
- if (view != null) {
- return deserializeWithView(p, ctxt, builder, view);
- }
- }
- JsonToken t = p.getCurrentToken();
- // 23-Mar-2010, tatu: In some cases, we start with full JSON object too...
- if (t == JsonToken.START_OBJECT) {
- t = p.nextToken();
- }
- for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
- String propName = p.getCurrentName();
- // Skip field name:
- p.nextToken();
- SettableBeanProperty prop = _beanProperties.find(propName);
-
- if (prop != null) { // normal case
- try {
- builder = prop.deserializeSetAndReturn(p, ctxt, builder);
- } catch (Exception e) {
- wrapAndThrow(e, builder, propName, ctxt);
- }
- continue;
- }
- handleUnknownVanilla(p, ctxt, handledType(), propName);
- }
- return builder;
- }
-
/**
* Streamlined version that is only used when no "special"
* features are enabled.
*/
private final Object vanillaDeserialize(JsonParser p,
DeserializationContext ctxt, JsonToken t)
- throws IOException, JsonProcessingException
+ throws IOException
{
Object bean = _valueInstantiator.createUsingDefault(ctxt);
- for (; p.getCurrentToken() != JsonToken.END_OBJECT; p.nextToken()) {
+ for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
@@ -280,7 +283,7 @@
*/
@Override
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_nonStandardCreation) {
if (_unwrappedPropertyHandler != null) {
@@ -301,7 +304,7 @@
return deserializeWithView(p, ctxt, bean, view);
}
}
- for (; p.getCurrentToken() != JsonToken.END_OBJECT; p.nextToken()) {
+ for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
@@ -326,15 +329,18 @@
* values for creator method need to be buffered, first; and
* due to non-guaranteed ordering possibly some other properties
* as well.
+ *
+ * @return Builder instance constructed
*/
@Override
@SuppressWarnings("resource")
- protected final Object _deserializeUsingPropertyBased(final JsonParser p,
+ protected Object _deserializeUsingPropertyBased(final JsonParser p,
final DeserializationContext ctxt)
- throws IOException, JsonProcessingException
- {
+ throws IOException
+ {
final PropertyBasedCreator creator = _propertyBasedCreator;
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader);
+ final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
// 04-Jan-2010, tatu: May need to collect unknown properties for polymorphic cases
TokenBuffer unknown = null;
@@ -346,25 +352,29 @@
// creator property?
SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {
+ if ((activeView != null) && !creatorProp.visibleInView(activeView)) {
+ p.skipChildren();
+ continue;
+ }
// Last creator property to set?
if (buffer.assignParameter(creatorProp, creatorProp.deserialize(p, ctxt))) {
p.nextToken(); // to move to following FIELD_NAME/END_OBJECT
- Object bean;
+ Object builder;
try {
- bean = creator.build(ctxt, buffer);
+ builder = creator.build(ctxt, buffer);
} catch (Exception e) {
wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt);
continue; // never gets here
}
// polymorphic?
- if (bean.getClass() != _beanType.getRawClass()) {
- return handlePolymorphic(p, ctxt, bean, unknown);
+ if (builder.getClass() != _beanType.getRawClass()) {
+ return handlePolymorphic(p, ctxt, builder, unknown);
}
if (unknown != null) { // nope, just extra unknown stuff...
- bean = handleUnknownProperties(ctxt, bean, unknown);
+ builder = handleUnknownProperties(ctxt, builder, unknown);
}
// or just clean?
- return _deserialize(p, ctxt, bean);
+ return _deserialize(p, ctxt, builder);
}
continue;
}
@@ -398,21 +408,69 @@
}
// We hit END_OBJECT, so:
- Object bean;
+ Object builder;
try {
- bean = creator.build(ctxt, buffer);
+ builder = creator.build(ctxt, buffer);
} catch (Exception e) {
- bean = wrapInstantiationProblem(e, ctxt);
+ builder = wrapInstantiationProblem(e, ctxt);
}
if (unknown != null) {
// polymorphic?
- if (bean.getClass() != _beanType.getRawClass()) {
- return handlePolymorphic(null, ctxt, bean, unknown);
+ if (builder.getClass() != _beanType.getRawClass()) {
+ return handlePolymorphic(null, ctxt, builder, unknown);
}
// no, just some extra unknown properties
- return handleUnknownProperties(ctxt, bean, unknown);
+ return handleUnknownProperties(ctxt, builder, unknown);
}
- return bean;
+ return builder;
+ }
+
+ @SuppressWarnings("resource")
+ protected final Object _deserialize(JsonParser p,
+ DeserializationContext ctxt, Object builder) throws IOException
+ {
+ if (_injectables != null) {
+ injectValues(ctxt, builder);
+ }
+ if (_unwrappedPropertyHandler != null) {
+ if (p.hasToken(JsonToken.START_OBJECT)) {
+ p.nextToken();
+ }
+ TokenBuffer tokens = new TokenBuffer(p, ctxt);
+ tokens.writeStartObject();
+ return deserializeWithUnwrapped(p, ctxt, builder, tokens);
+ }
+ if (_externalTypeIdHandler != null) {
+ return deserializeWithExternalTypeId(p, ctxt, builder);
+ }
+ if (_needViewProcesing) {
+ Class<?> view = ctxt.getActiveView();
+ if (view != null) {
+ return deserializeWithView(p, ctxt, builder, view);
+ }
+ }
+ JsonToken t = p.getCurrentToken();
+ // 23-Mar-2010, tatu: In some cases, we start with full JSON object too...
+ if (t == JsonToken.START_OBJECT) {
+ t = p.nextToken();
+ }
+ for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
+ String propName = p.getCurrentName();
+ // Skip field name:
+ p.nextToken();
+ SettableBeanProperty prop = _beanProperties.find(propName);
+
+ if (prop != null) { // normal case
+ try {
+ builder = prop.deserializeSetAndReturn(p, ctxt, builder);
+ } catch (Exception e) {
+ wrapAndThrow(e, builder, propName, ctxt);
+ }
+ continue;
+ }
+ handleUnknownVanilla(p, ctxt, handledType(), propName);
+ }
+ return builder;
}
/*
@@ -423,7 +481,7 @@
protected final Object deserializeWithView(JsonParser p, DeserializationContext ctxt,
Object bean, Class<?> activeView)
- throws IOException, JsonProcessingException
+ throws IOException
{
JsonToken t = p.getCurrentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
@@ -460,7 +518,7 @@
*/
@SuppressWarnings("resource")
protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_delegateDeserializer != null) {
return _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt));
@@ -477,8 +535,7 @@
}
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
-
- for (; p.getCurrentToken() != JsonToken.END_OBJECT; p.nextToken()) {
+ for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
@@ -513,65 +570,20 @@
}
}
tokens.writeEndObject();
- _unwrappedPropertyHandler.processUnwrapped(p, ctxt, bean, tokens);
- return bean;
- }
-
- @SuppressWarnings("resource")
- protected Object deserializeWithUnwrapped(JsonParser p,
- DeserializationContext ctxt, Object bean)
- throws IOException, JsonProcessingException
- {
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.START_OBJECT) {
- t = p.nextToken();
- }
- TokenBuffer tokens = new TokenBuffer(p, ctxt);
- tokens.writeStartObject();
- final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
- for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
- String propName = p.getCurrentName();
- SettableBeanProperty prop = _beanProperties.find(propName);
- p.nextToken();
- if (prop != null) { // normal case
- if (activeView != null && !prop.visibleInView(activeView)) {
- p.skipChildren();
- continue;
- }
- try {
- bean = prop.deserializeSetAndReturn(p, ctxt, bean);
- } catch (Exception e) {
- wrapAndThrow(e, bean, propName, ctxt);
- }
- continue;
- }
- if (_ignorableProps != null && _ignorableProps.contains(propName)) {
- handleIgnoredProperty(p, ctxt, bean, propName);
- continue;
- }
- // but... others should be passed to unwrapped property deserializers
- tokens.writeFieldName(propName);
- tokens.copyCurrentStructure(p);
- // how about any setter? We'll get copies but...
- if (_anySetter != null) {
- _anySetter.deserializeAndSet(p, ctxt, bean, propName);
- }
- }
- tokens.writeEndObject();
- _unwrappedPropertyHandler.processUnwrapped(p, ctxt, bean, tokens);
- return bean;
+ return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, bean, tokens);
}
@SuppressWarnings("resource")
protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p,
DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ throws IOException
{
final PropertyBasedCreator creator = _propertyBasedCreator;
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader);
TokenBuffer tokens = new TokenBuffer(p, ctxt);
tokens.writeStartObject();
+ Object builder = null;
JsonToken t = p.getCurrentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
@@ -580,7 +592,20 @@
// creator property?
SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {
- buffer.assignParameter(creatorProp, creatorProp.deserialize(p, ctxt));
+ // Last creator property to set?
+ if (buffer.assignParameter(creatorProp, creatorProp.deserialize(p, ctxt))) {
+ t = p.nextToken(); // to move to following FIELD_NAME/END_OBJECT
+ try {
+ builder = creator.build(ctxt, buffer);
+ } catch (Exception e) {
+ wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt);
+ continue; // never gets here
+ }
+ if (builder.getClass() != _beanType.getRawClass()) {
+ return handlePolymorphic(p, ctxt, builder, tokens);
+ }
+ return deserializeWithUnwrapped(p, ctxt, builder, tokens);
+ }
continue;
}
// Object Id property?
@@ -604,16 +629,54 @@
buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt));
}
}
+ tokens.writeEndObject();
// We hit END_OBJECT, so:
- Object bean;
- // !!! 15-Feb-2012, tatu: Need to modify creator to use Builder!
- try {
- bean = creator.build(ctxt, buffer);
- } catch (Exception e) {
- return wrapInstantiationProblem(e, ctxt);
+ if (builder == null) {
+ try {
+ builder = creator.build(ctxt, buffer);
+ } catch (Exception e) {
+ return wrapInstantiationProblem(e, ctxt);
+ }
}
- return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, bean, tokens);
+ return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, builder, tokens);
+ }
+
+ protected Object deserializeWithUnwrapped(JsonParser p,
+ DeserializationContext ctxt, Object builder, TokenBuffer tokens)
+ throws IOException
+ {
+ final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
+ for (JsonToken t = p.getCurrentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
+ String propName = p.getCurrentName();
+ SettableBeanProperty prop = _beanProperties.find(propName);
+ p.nextToken();
+ if (prop != null) { // normal case
+ if (activeView != null && !prop.visibleInView(activeView)) {
+ p.skipChildren();
+ continue;
+ }
+ try {
+ builder = prop.deserializeSetAndReturn(p, ctxt, builder);
+ } catch (Exception e) {
+ wrapAndThrow(e, builder, propName, ctxt);
+ }
+ continue;
+ }
+ if (_ignorableProps != null && _ignorableProps.contains(propName)) {
+ handleIgnoredProperty(p, ctxt, builder, propName);
+ continue;
+ }
+ // but... others should be passed to unwrapped property deserializers
+ tokens.writeFieldName(propName);
+ tokens.copyCurrentStructure(p);
+ // how about any setter? We'll get copies but...
+ if (_anySetter != null) {
+ _anySetter.deserializeAndSet(p, ctxt, builder, propName);
+ }
+ }
+ tokens.writeEndObject();
+ return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, builder, tokens);
}
/*
@@ -624,7 +687,7 @@
*/
protected Object deserializeWithExternalTypeId(JsonParser p, DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (_propertyBasedCreator != null) {
return deserializeUsingPropertyBasedWithExternalTypeId(p, ctxt);
@@ -634,7 +697,7 @@
protected Object deserializeWithExternalTypeId(JsonParser p,
DeserializationContext ctxt, Object bean)
- throws IOException, JsonProcessingException
+ throws IOException
{
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
final ExternalTypeHandler ext = _externalTypeIdHandler.start();
@@ -644,7 +707,7 @@
t = p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
- // [JACKSON-831]: may have property AND be used as external type id:
+ // May have property AND be used as external type id:
if (t.isScalarValue()) {
ext.handleTypePropertyValue(p, ctxt, propName, bean);
}
@@ -675,6 +738,7 @@
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
+ continue;
} else {
// Unknown: let's call handler method
handleUnknownProperty(p, ctxt, bean, propName);
@@ -686,9 +750,12 @@
protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p,
DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ throws IOException
{
// !!! 04-Mar-2012, TODO: Need to fix -- will not work as is...
- throw new IllegalStateException("Deserialization with Builder, External type id, @JsonCreator not yet implemented");
+ JavaType t = _targetType;
+ return ctxt.reportBadDefinition(t, String.format(
+ "Deserialization (of %s) with Builder, External type id, @JsonCreator not yet implemented",
+ t));
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ContextualDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/ContextualDeserializer.java
index 11c972b..9db1f75 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/ContextualDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/ContextualDeserializer.java
@@ -29,7 +29,7 @@
* deserializers that may be needed by this deserializer
* @param property Method, field or constructor parameter that represents the property
* (and is used to assign deserialized value).
- * Should be available; but there may be cases where caller can not provide it and
+ * Should be available; but there may be cases where caller cannot provide it and
* null is passed instead (in which case impls usually pass 'this' deserializer as is)
*
* @return Deserializer to use for deserializing values of specified property;
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java
index d8e8602..f9badf8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java
@@ -6,16 +6,19 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.util.Annotations;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* This concrete sub-class implements property that is passed
* via Creator (constructor or static factory method).
* It is not a full-featured implementation in that its set method
- * should never be called -- instead, value must separately passed.
+ * should usually not be called for primary mutation -- instead, value must separately passed --
+ * but some aspects are still needed (specifically, injection).
*<p>
* Note on injectable values: unlike with other mutators, where
* deserializer and injecting are separate, here we treat the two as related
@@ -41,12 +44,7 @@
protected final Object _injectableValueId;
/**
- * @since 2.1
- */
- protected final int _creatorIndex;
-
- /**
- * In special cases, when implementing "updateValue", we can not use
+ * In special cases, when implementing "updateValue", we cannot use
* constructors or factory methods, but have to fall back on using a
* setter (or mutable field property). If so, this refers to that fallback
* accessor.
@@ -59,6 +57,22 @@
protected SettableBeanProperty _fallbackSetter;
/**
+ * @since 2.1
+ */
+ protected final int _creatorIndex;
+
+ /**
+ * Marker flag that may have to be set during construction, to indicate that
+ * although property may have been constructed and added as a placeholder,
+ * it represents something that should be ignored during deserialization.
+ * This mostly concerns Creator properties which may not be easily deleted
+ * during processing.
+ *
+ * @since 2.9.4
+ */
+ protected boolean _ignorable;
+
+ /**
* @param name Name of the logical property
* @param type Type of the property, used to find deserializer
* @param typeDeser Type deserializer to use for handling polymorphic type
@@ -91,33 +105,43 @@
protected CreatorProperty(CreatorProperty src, PropertyName newName) {
super(src, newName);
_annotated = src._annotated;
- _creatorIndex = src._creatorIndex;
_injectableValueId = src._injectableValueId;
_fallbackSetter = src._fallbackSetter;
+ _creatorIndex = src._creatorIndex;
+ _ignorable = src._ignorable;
}
- protected CreatorProperty(CreatorProperty src, JsonDeserializer<?> deser) {
- super(src, deser);
+ protected CreatorProperty(CreatorProperty src, JsonDeserializer<?> deser,
+ NullValueProvider nva) {
+ super(src, deser, nva);
_annotated = src._annotated;
- _creatorIndex = src._creatorIndex;
_injectableValueId = src._injectableValueId;
_fallbackSetter = src._fallbackSetter;
+ _creatorIndex = src._creatorIndex;
+ _ignorable = src._ignorable;
}
@Override
- public CreatorProperty withName(PropertyName newName) {
+ public SettableBeanProperty withName(PropertyName newName) {
return new CreatorProperty(this, newName);
}
@Override
- public CreatorProperty withValueDeserializer(JsonDeserializer<?> deser) {
+ public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
if (_valueDeserializer == deser) {
return this;
}
- return new CreatorProperty(this, deser);
+ // 07-May-2019, tatu: As per [databind#2303], must keep VD/NVP in-sync if they were
+ NullValueProvider nvp = (_valueDeserializer == _nullProvider) ? deser : _nullProvider;
+ return new CreatorProperty(this, deser, nvp);
}
@Override
+ public SettableBeanProperty withNullProvider(NullValueProvider nva) {
+ return new CreatorProperty(this, _valueDeserializer, nva);
+ }
+
+ @Override
public void fixAccess(DeserializationConfig config) {
if (_fallbackSetter != null) {
_fallbackSetter.fixAccess(config);
@@ -134,19 +158,37 @@
_fallbackSetter = fallbackSetter;
}
+ @Override
+ public void markAsIgnorable() {
+ _ignorable = true;
+ }
+
+ @Override
+ public boolean isIgnorable() {
+ return _ignorable;
+ }
+
+ /*
+ /**********************************************************
+ /* Injection support
+ /**********************************************************
+ */
+
/**
* Method that can be called to locate value to be injected for this
* property, if it is configured for this.
*/
public Object findInjectableValue(DeserializationContext context, Object beanInstance)
+ throws JsonMappingException
{
if (_injectableValueId == null) {
- throw new IllegalStateException("Property '"+getName()
- +"' (type "+getClass().getName()+") has no injectable value id configured");
+ context.reportBadDefinition(ClassUtil.classOf(beanInstance),
+ String.format("Property '%s' (type %s) has no injectable value id configured",
+ getName(), getClass().getName()));
}
return context.findInjectableValue(_injectableValueId, this, beanInstance);
}
-
+
/**
* Method to find value to inject, and inject it to this property.
*/
@@ -155,7 +197,7 @@
{
set(beanInstance, findInjectableValue(context, beanInstance));
}
-
+
/*
/**********************************************************
/* BeanProperty impl
@@ -186,36 +228,29 @@
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object instance) throws IOException
{
- set(instance, deserialize(p, ctxt));
+ _verifySetter();
+ _fallbackSetter.set(instance, deserialize(p, ctxt));
}
@Override
public Object deserializeSetAndReturn(JsonParser p,
DeserializationContext ctxt, Object instance) throws IOException
{
- return setAndReturn(instance, deserialize(p, ctxt));
+ _verifySetter();
+ return _fallbackSetter.setAndReturn(instance, deserialize(p, ctxt));
}
@Override
public void set(Object instance, Object value) throws IOException
{
- /* Hmmmh. Should we return quietly (NOP), or error?
- * Perhaps better to throw an exception, since it's generally an error.
- */
- if (_fallbackSetter == null) {
- throw new IllegalStateException("No fallback setter/field defined: can not use creator property for "
- +getClass().getName());
- }
+ _verifySetter();
_fallbackSetter.set(instance, value);
}
@Override
public Object setAndReturn(Object instance, Object value) throws IOException
{
- if (_fallbackSetter == null) {
- throw new IllegalStateException("No fallback setter/field defined: can not use creator property for "
- +getClass().getName());
- }
+ _verifySetter();
return _fallbackSetter.setAndReturn(instance, value);
}
@@ -226,4 +261,24 @@
@Override
public String toString() { return "[creator property, name '"+getName()+"'; inject id '"+_injectableValueId+"']"; }
+
+ // since 2.9
+ private final void _verifySetter() throws IOException {
+ if (_fallbackSetter == null) {
+ _reportMissingSetter(null, null);
+ }
+ }
+
+ // since 2.9
+ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) throws IOException
+ {
+ final String msg = "No fallback setter/field defined for creator property '"+getName()+"'";
+ // Hmmmh. Should we return quietly (NOP), or error?
+ // Perhaps better to throw an exception, since it's generally an error.
+ if (ctxt != null ) {
+ ctxt.reportBadDefinition(getType(), msg);
+ } else {
+ throw InvalidDefinitionException.from(p, msg, getType());
+ }
+ }
}
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 d759ddd..cdc90ed 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/DefaultDeserializationContext.java
@@ -38,7 +38,7 @@
/**
* Constructor that will pass specified deserializer factory and
* cache: cache may be null (in which case default implementation
- * will be used), factory can not be null
+ * will be used), factory cannot be null
*/
protected DefaultDeserializationContext(DeserializerFactory df, DeserializerCache cache) {
super(df, cache);
@@ -331,16 +331,14 @@
@Override
public DefaultDeserializationContext copy() {
- if (getClass() != Impl.class) {
- return super.copy();
- }
+ ClassUtil.verifyMustOverride(Impl.class, this, "copy");
return new Impl(this);
}
@Override
public DefaultDeserializationContext createInstance(DeserializationConfig config,
- JsonParser jp, InjectableValues values) {
- return new Impl(this, config, jp, values);
+ JsonParser p, InjectableValues values) {
+ return new Impl(this, config, p, values);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java
index e374867..38b8705 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java
@@ -75,7 +75,7 @@
}
/**
- * Method called when a property name from input can not be converted to a
+ * Method called when a property name from input cannot be converted to a
* non-Java-String key type (passed as <code>rawKeyType</code>) due to format problem.
* Handler may choose to do one of 3 things:
*<ul>
@@ -108,7 +108,7 @@
/**
* Method called when a String value
- * can not be converted to a non-String value type due to specific problem
+ * cannot be converted to a non-String value type due to specific problem
* (as opposed to String values never being usable).
* Handler may choose to do one of 3 things:
*<ul>
@@ -127,8 +127,8 @@
* to indicate type of failure unless handler produces key to use
*
* @return Either {@link #NOT_HANDLED} to indicate that handler does not know
- * what to do (and exception may be thrown), or value to use as key (possibly
- * <code>null</code>
+ * what to do (and exception may be thrown), or value to use as (possibly
+ * <code>null</code>)
*
* @since 2.8
*/
@@ -142,7 +142,7 @@
/**
* Method called when a numeric value (integral or floating-point from input
- * can not be converted to a non-numeric value type due to specific problem
+ * cannot be converted to a non-numeric value type due to specific problem
* (as opposed to numeric values never being usable).
* Handler may choose to do one of 3 things:
*<ul>
@@ -161,14 +161,41 @@
* to indicate type of failure unless handler produces key to use
*
* @return Either {@link #NOT_HANDLED} to indicate that handler does not know
- * what to do (and exception may be thrown), or value to use as key (possibly
- * <code>null</code>
+ * what to do (and exception may be thrown), or value to use as (possibly
+ * <code>null</code>)
*
* @since 2.8
*/
public Object handleWeirdNumberValue(DeserializationContext ctxt,
- Class<?> targetType, Number valueToConvert,
- String failureMsg)
+ Class<?> targetType, Number valueToConvert, String failureMsg)
+ throws IOException
+ {
+ return NOT_HANDLED;
+ }
+
+ /**
+ * Method called when an embedded (native) value ({@link JsonToken#VALUE_EMBEDDED_OBJECT})
+ * cannot be converted directly into expected value type (usually POJO).
+ * Handler may choose to do one of 3 things:
+ *<ul>
+ * <li>Indicate it does not know what to do by returning {@link #NOT_HANDLED}
+ * </li>
+ * <li>Throw a {@link IOException} to indicate specific fail message (instead of
+ * standard exception caller would throw
+ * </li>
+ * <li>Return actual converted value (of type <code>targetType</code>) to use as
+ * replacement, and continue processing.
+ * </li>
+ * </ul>
+ *
+ * @return Either {@link #NOT_HANDLED} to indicate that handler does not know
+ * what to do (and exception may be thrown), or value to use (possibly
+ * <code>null</code>)
+ *
+ * @since 2.9
+ */
+ public Object handleWeirdNativeValue(DeserializationContext ctxt,
+ JavaType targetType, Object valueToConvert, JsonParser p)
throws IOException
{
return NOT_HANDLED;
@@ -177,7 +204,7 @@
/**
* Method that deserializers should call if the first token of the value to
* deserialize is of unexpected type (that is, type of token that deserializer
- * can not handle). This could occur, for example, if a Number deserializer
+ * cannot handle). This could occur, for example, if a Number deserializer
* encounter {@link JsonToken#START_ARRAY} instead of
* {@link JsonToken#VALUE_NUMBER_INT} or {@link JsonToken#VALUE_NUMBER_FLOAT}.
*<ul>
@@ -267,15 +294,18 @@
* what to do (and exception may be thrown), or value to use (possibly
* <code>null</code>
*
- * @since 2.8
+ * @since 2.9
*/
public Object handleMissingInstantiator(DeserializationContext ctxt,
- Class<?> instClass, JsonParser p, String msg)
+ Class<?> instClass, ValueInstantiator valueInsta, JsonParser p,
+ String msg)
throws IOException
{
- return NOT_HANDLED;
+ // 16-Oct-2016, tatu: Need to delegate to deprecated method from 2.8;
+ // remove redirect from later versions (post-2.9)
+ return handleMissingInstantiator(ctxt, instClass, p, msg);
}
-
+
/**
* Handler method called if resolution of type id from given String failed
* to produce a subtype; usually because logical id is not mapped to actual
@@ -313,4 +343,58 @@
{
return null;
}
+
+ /**
+ * Handler method called if an expected type id for a polymorphic value is
+ * not found and no "default type" is specified or allowed.
+ * Handler may choose to do one of following things:
+ *<ul>
+ * <li>Indicate it does not know what to do by returning `null`
+ * </li>
+ * <li>Indicate that nothing should be deserialized, by return `Void.class`
+ * </li>
+ * <li>Throw a {@link IOException} to indicate specific fail message (instead of
+ * standard exception caller would throw
+ * </li>
+ * <li>Return actual resolved type to use for this particular case.
+ * </li>
+ * </ul>
+ *
+ * @param ctxt Deserialization context to use for accessing information or
+ * constructing exception to throw
+ * @param baseType Base type to use for resolving subtype id
+ * @param failureMsg Informational message that would be thrown as part of
+ * exception, if resolution still fails
+ *
+ * @return Actual type to use, if resolved; `null` if handler does not know what
+ * to do; or `Void.class` to indicate that nothing should be deserialized for
+ * type with the id (which caller may choose to do... or not)
+ *
+ * @since 2.9
+ */
+ public JavaType handleMissingTypeId(DeserializationContext ctxt,
+ JavaType baseType, TypeIdResolver idResolver,
+ String failureMsg)
+ throws IOException
+ {
+ return null;
+ }
+
+ /*
+ /**********************************************************
+ /* Deprecated
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.8
+ * @deprecated Since 2.9: use variant that takes {@link ValueInstantiator}
+ */
+ @Deprecated
+ public Object handleMissingInstantiator(DeserializationContext ctxt,
+ Class<?> instClass, JsonParser p, String msg)
+ throws IOException
+ {
+ return NOT_HANDLED;
+ }
}
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 ded6b68..8aff025 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java
@@ -265,7 +265,7 @@
} catch (IllegalArgumentException iae) {
// We better only expose checked exceptions, since those
// are what caller is expected to handle
- throw JsonMappingException.from(ctxt, iae.getMessage(), iae);
+ throw JsonMappingException.from(ctxt, ClassUtil.exceptionMessage(iae), iae);
}
if (deser == null) {
return null;
@@ -372,11 +372,19 @@
return factory.createArrayDeserializer(ctxt, (ArrayType) type, beanDesc);
}
if (type.isMapLikeType()) {
- MapLikeType mlt = (MapLikeType) type;
- if (mlt.isTrueMapType()) {
- return factory.createMapDeserializer(ctxt,(MapType) mlt, beanDesc);
+ // 11-Mar-2017, tatu: As per [databind#1554], also need to block
+ // handling as Map if overriden with "as POJO" option.
+ // Ideally we'd determine it bit later on (to allow custom handler checks)
+ // 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) {
+ MapLikeType mlt = (MapLikeType) type;
+ if (mlt.isTrueMapType()) {
+ return factory.createMapDeserializer(ctxt,(MapType) mlt, beanDesc);
+ }
+ return factory.createMapLikeDeserializer(ctxt, mlt, beanDesc);
}
- return factory.createMapLikeDeserializer(ctxt, mlt, beanDesc);
}
if (type.isCollectionLikeType()) {
/* 03-Aug-2012, tatu: As per [databind#40], one exception is if shape
@@ -574,23 +582,20 @@
/**********************************************************
*/
- // NOTE: changed 2.6 -> 2.7 to pass context; no way to make backwards compatible
protected JsonDeserializer<Object> _handleUnknownValueDeserializer(DeserializationContext ctxt, JavaType type)
throws JsonMappingException
{
// Let's try to figure out the reason, to give better error messages
Class<?> rawClass = type.getRawClass();
if (!ClassUtil.isConcrete(rawClass)) {
- ctxt.reportMappingException("Can not find a Value deserializer for abstract type %s", type);
+ return ctxt.reportBadDefinition(type, "Cannot find a Value deserializer for abstract type "+type);
}
- ctxt.reportMappingException("Can not find a Value deserializer for type %s", type);
- return null;
+ return ctxt.reportBadDefinition(type, "Cannot find a Value deserializer for type "+type);
}
protected KeyDeserializer _handleUnknownKeyDeserializer(DeserializationContext ctxt, JavaType type)
throws JsonMappingException
{
- ctxt.reportMappingException("Can not find a (Map) Key deserializer for type %s", type);
- return null;
+ return ctxt.reportBadDefinition(type, "Cannot find a (Map) Key deserializer for type "+type);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java
index ffba4e4..d0638520 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerFactory.java
@@ -184,7 +184,7 @@
public abstract KeyDeserializer createKeyDeserializer(DeserializationContext ctxt,
JavaType type)
throws JsonMappingException;
-
+
/**
* Method called to find and create a type information deserializer for given base type,
* if one is needed. If not needed (no polymorphic handling configured for type),
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/NullValueProvider.java b/src/main/java/com/fasterxml/jackson/databind/deser/NullValueProvider.java
new file mode 100644
index 0000000..b62834d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/NullValueProvider.java
@@ -0,0 +1,34 @@
+package com.fasterxml.jackson.databind.deser;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.util.AccessPattern;
+
+/**
+ * Helper interface implemented by classes that are to be used as
+ * null providers during deserialization. Most importantly implemented by
+ * {@link com.fasterxml.jackson.databind.JsonDeserializer} (as a mix-in
+ * interface), but also by converters used to support more configurable
+ * null replacement.
+ *
+ * @since 2.9
+ */
+public interface NullValueProvider
+{
+ /**
+ * Method called to possibly convert incoming `null` token (read via
+ * underlying streaming input source) into other value of type accessor
+ * supports. May return `null`, or value compatible with type binding.
+ *<p>
+ * NOTE: if {@link #getNullAccessPattern()} returns `ALWAYS_NULL` or
+ * `CONSTANT`, this method WILL NOT use provided `ctxt` and it may thus
+ * be passed as `null`.
+ */
+ public Object getNullValue(DeserializationContext ctxt) throws JsonMappingException;
+
+ /**
+ * Accessor that may be used to determine if and when provider must be called to
+ * access null replacement value.
+ */
+ public AccessPattern getNullAccessPattern();
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java
index 8ee86d4..465562e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java
@@ -10,6 +10,7 @@
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Class that represents a "wildcard" set method which can be used
@@ -42,7 +43,12 @@
protected JsonDeserializer<Object> _valueDeserializer;
protected final TypeDeserializer _valueTypeDeserializer;
-
+
+ /**
+ * @since 2.9
+ */
+ protected final KeyDeserializer _keyDeserializer;
+
/*
/**********************************************************
/* Life-cycle
@@ -50,6 +56,7 @@
*/
public SettableAnyProperty(BeanProperty property, AnnotatedMember setter, JavaType type,
+ KeyDeserializer keyDeser,
JsonDeserializer<Object> valueDeser, TypeDeserializer typeDeser)
{
_property = property;
@@ -57,25 +64,20 @@
_type = type;
_valueDeserializer = valueDeser;
_valueTypeDeserializer = typeDeser;
+ _keyDeserializer = keyDeser;
_setterIsField = setter instanceof AnnotatedField;
}
- /**
- * Constructor used for JDK Serialization when reading persisted object
- */
- protected SettableAnyProperty(SettableAnyProperty src)
+ @Deprecated // since 2.9
+ public SettableAnyProperty(BeanProperty property, AnnotatedMember setter, JavaType type,
+ JsonDeserializer<Object> valueDeser, TypeDeserializer typeDeser)
{
- _property = src._property;
- _setter = src._setter;
- _type = src._type;
- _valueDeserializer = src._valueDeserializer;
- _valueTypeDeserializer = src._valueTypeDeserializer;
- _setterIsField = src._setterIsField;
+ this(property, setter, type, null, valueDeser, typeDeser);
}
public SettableAnyProperty withValueDeserializer(JsonDeserializer<Object> deser) {
return new SettableAnyProperty(_property, _setter, _type,
- deser, _valueTypeDeserializer);
+ _keyDeserializer, deser, _valueTypeDeserializer);
}
public void fixAccess(DeserializationConfig config) {
@@ -127,7 +129,9 @@
throws IOException
{
try {
- set(instance, propName, deserialize(p, ctxt));
+ Object key = (_keyDeserializer == null) ? propName
+ : _keyDeserializer.deserializeKey(propName, ctxt);
+ set(instance, key, deserialize(p, ctxt));
} catch (UnresolvedForwardReference reference) {
if (!(_valueDeserializer.getObjectIdReader() != null)) {
throw JsonMappingException.from(p, "Unresolved forward reference but no identity info.", reference);
@@ -151,7 +155,7 @@
}
@SuppressWarnings("unchecked")
- public void set(Object instance, String propName, Object value) throws IOException
+ public void set(Object instance, Object propName, Object value) throws IOException
{
try {
// if annotation in the field (only map is supported now)
@@ -159,7 +163,7 @@
AnnotatedField field = (AnnotatedField) _setter;
Map<Object,Object> val = (Map<Object,Object>) field.getValue(instance);
/* 01-Jun-2016, tatu: At this point it is not quite clear what to do if
- * field is `null` -- we can not necessarily count on zero-args
+ * field is `null` -- we cannot necessarily count on zero-args
* constructor except for a small set of types, so for now just
* ignore if null. May need to figure out something better in future.
*/
@@ -168,7 +172,7 @@
val.put(propName, value);
}
} else {
- // note: can not use 'setValue()' due to taking 2 args
+ // note: cannot use 'setValue()' due to taking 2 args
((AnnotatedMethod) _setter).callOnWith(instance, propName, value);
}
} catch (Exception e) {
@@ -187,15 +191,15 @@
* @param propName Name of property (from Json input) to set
* @param value Value of the property
*/
- protected void _throwAsIOE(Exception e, String propName, Object value)
+ protected void _throwAsIOE(Exception e, Object propName, Object value)
throws IOException
{
if (e instanceof IllegalArgumentException) {
- String actType = (value == null) ? "[NULL]" : value.getClass().getName();
+ String actType = ClassUtil.classNameOf(value);
StringBuilder msg = new StringBuilder("Problem deserializing \"any\" property '").append(propName);
msg.append("' of class "+getClassName()+" (expected type: ").append(_type);
msg.append("; actual type: ").append(actType).append(")");
- String origMsg = e.getMessage();
+ String origMsg = ClassUtil.exceptionMessage(e);
if (origMsg != null) {
msg.append(", problem: ").append(origMsg);
} else {
@@ -203,18 +207,11 @@
}
throw new JsonMappingException(null, msg.toString(), e);
}
- if (e instanceof IOException) {
- throw (IOException) e;
- }
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- }
+ ClassUtil.throwIfIOE(e);
+ ClassUtil.throwIfRTE(e);
// let's wrap the innermost problem
- Throwable t = e;
- while (t.getCause() != null) {
- t = t.getCause();
- }
- throw new JsonMappingException(null, t.getMessage(), t);
+ Throwable t = ClassUtil.getRootCause(e);
+ throw new JsonMappingException(null, ClassUtil.exceptionMessage(t), t);
}
private String getClassName() { return _setter.getDeclaringClass().getName(); }
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java
index 65be982..bd0c044 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java
@@ -7,10 +7,12 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.impl.FailingDeserializer;
+import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.util.Annotations;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.ViewMatcher;
/**
@@ -33,7 +35,7 @@
*/
protected static final JsonDeserializer<Object> MISSING_VALUE_DESERIALIZER = new FailingDeserializer(
"No _valueDeserializer assigned");
-
+
/**
* Logical name of the property (often but not always derived
* from the setter method name)
@@ -49,14 +51,14 @@
* @since 2.2
*/
protected final PropertyName _wrapperName;
-
+
/**
* Class that contains this property (either class that declares
* the property or one of its subclasses), class that is
* deserialized using deserializer that contains this property.
*/
protected final transient Annotations _contextAnnotations;
-
+
/**
* Deserializer used for handling property value.
*<p>
@@ -71,18 +73,26 @@
*/
protected final TypeDeserializer _valueTypeDeserializer;
+ /**
+ * Entity used for possible translation from `null` into non-null
+ * value of type of this property.
+ * Often same as <code>_valueDeserializer</code>, but not always.
+ *
+ * @since 2.9
+ */
+ protected final NullValueProvider _nullProvider;
+
/*
/**********************************************************
/* Configuration that is not yet immutable; generally assigned
- /* during initialization process but can not be passed to
+ /* during initialization process but cannot be passed to
/* constructor.
/**********************************************************
*/
/**
- * If property represents a managed (forward) reference
- * (see [JACKSON-235]), we will need name of reference for
- * later linking.
+ * If property represents a managed (forward) reference, we will need
+ * the name of reference for later linking.
*<p>
* TODO: should try to make immutable.
*/
@@ -103,7 +113,7 @@
* TODO: should try to make immutable.
*/
protected ViewMatcher _viewMatcher;
-
+
/**
* Index of property (within all property of a bean); assigned
* when all properties have been collected. Order of entries
@@ -127,15 +137,6 @@
contextAnnotations, propDef.getMetadata());
}
- @Deprecated // since 2.3
- protected SettableBeanProperty(String propName, JavaType type, PropertyName wrapper,
- TypeDeserializer typeDeser, Annotations contextAnnotations,
- boolean isRequired)
- {
- this(new PropertyName(propName), type, wrapper, typeDeser, contextAnnotations,
- PropertyMetadata.construct(Boolean.valueOf(isRequired), null, null, null));
- }
-
protected SettableBeanProperty(PropertyName propName, JavaType type, PropertyName wrapper,
TypeDeserializer typeDeser, Annotations contextAnnotations,
PropertyMetadata metadata)
@@ -162,6 +163,7 @@
}
_valueTypeDeserializer = typeDeser;
_valueDeserializer = MISSING_VALUE_DESERIALIZER;
+ _nullProvider = MISSING_VALUE_DESERIALIZER;
}
/**
@@ -185,8 +187,10 @@
_viewMatcher = null;
_valueTypeDeserializer = null;
_valueDeserializer = valueDeser;
+ // 29-Jan-2017, tatu: Presumed to be irrelevant for ObjectId values...
+ _nullProvider = valueDeser;
}
-
+
/**
* Basic copy-constructor for sub-classes to use.
*/
@@ -202,13 +206,15 @@
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
_viewMatcher = src._viewMatcher;
+ _nullProvider = src._nullProvider;
}
/**
* Copy-with-deserializer-change constructor for sub-classes to use.
*/
@SuppressWarnings("unchecked")
- protected SettableBeanProperty(SettableBeanProperty src, JsonDeserializer<?> deser)
+ protected SettableBeanProperty(SettableBeanProperty src,
+ JsonDeserializer<?> deser, NullValueProvider nuller)
{
super(src);
_propName = src._propName;
@@ -225,6 +231,11 @@
_valueDeserializer = (JsonDeserializer<Object>) deser;
}
_viewMatcher = src._viewMatcher;
+ // 29-Jan-2017, tatu: Bit messy, but for now has to do...
+ if (nuller == MISSING_VALUE_DESERIALIZER) {
+ nuller = _valueDeserializer;
+ }
+ _nullProvider = nuller;
}
/**
@@ -242,6 +253,7 @@
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
_viewMatcher = src._viewMatcher;
+ _nullProvider = src._nullProvider;
}
/**
@@ -276,11 +288,11 @@
? new PropertyName(simpleName) : _propName.withSimpleName(simpleName);
return (n == _propName) ? this : withName(n);
}
-
- @Deprecated // since 2.3 -- use 'withSimpleName' instead if need be
- public SettableBeanProperty withName(String simpleName) {
- return withName(new PropertyName(simpleName));
- }
+
+ /**
+ * @since 2.9
+ */
+ public abstract SettableBeanProperty withNullProvider(NullValueProvider nva);
public void setManagedReferenceName(String n) {
_managedReferenceName = n;
@@ -297,7 +309,7 @@
_viewMatcher = ViewMatcher.construct(views);
}
}
-
+
/**
* Method used to assign index for property.
*/
@@ -319,6 +331,16 @@
;
}
+ /**
+ * @since 2.9.4
+ */
+ public void markAsIgnorable() { }
+
+ /**
+ * @since 2.9.4
+ */
+ public boolean isIgnorable() { return false; }
+
/*
/**********************************************************
/* BeanProperty impl
@@ -372,7 +394,7 @@
/**********************************************************
*/
- protected final Class<?> getDeclaringClass() {
+ protected Class<?> getDeclaringClass() {
return getMember().getDeclaringClass();
}
@@ -385,7 +407,7 @@
}
public boolean hasValueTypeDeserializer() { return (_valueTypeDeserializer != null); }
-
+
public JsonDeserializer<Object> getValueDeserializer() {
JsonDeserializer<Object> deser = _valueDeserializer;
if (deser == MISSING_VALUE_DESERIALIZER) {
@@ -396,10 +418,15 @@
public TypeDeserializer getValueTypeDeserializer() { return _valueTypeDeserializer; }
+ /**
+ * @since 2.9
+ */
+ public NullValueProvider getNullValueProvider() { return _nullProvider; }
+
public boolean visibleInView(Class<?> activeView) {
return (_viewMatcher == null) || _viewMatcher.isVisibleForView(activeView);
}
-
+
public boolean hasViews() { return _viewMatcher != null; }
/**
@@ -493,15 +520,51 @@
*/
public final Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- JsonToken t = p.getCurrentToken();
-
- if (t == JsonToken.VALUE_NULL) {
- return _valueDeserializer.getNullValue(ctxt);
+ if (p.hasToken(JsonToken.VALUE_NULL)) {
+ return _nullProvider.getNullValue(ctxt);
}
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
}
- return _valueDeserializer.deserialize(p, ctxt);
+ // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
+ Object value = _valueDeserializer.deserialize(p, ctxt);
+ if (value == null) {
+ value = _nullProvider.getNullValue(ctxt);
+ }
+ return value;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public final Object deserializeWith(JsonParser p, DeserializationContext ctxt,
+ Object toUpdate) throws IOException
+ {
+ // 20-Oct-2016, tatu: Not 100% sure what to do; probably best to simply return
+ // null value and let caller decide what to do.
+ if (p.hasToken(JsonToken.VALUE_NULL)) {
+ // ... except for "skip nulls" case which should just do that:
+ if (NullsConstantProvider.isSkipper(_nullProvider)) {
+ return toUpdate;
+ }
+ return _nullProvider.getNullValue(ctxt);
+ }
+ // 20-Oct-2016, tatu: Also tricky -- for now, report an error
+ if (_valueTypeDeserializer != null) {
+ ctxt.reportBadDefinition(getType(),
+ String.format("Cannot merge polymorphic property '%s'",
+ getName()));
+// return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ }
+ // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
+ Object value = _valueDeserializer.deserialize(p, ctxt, toUpdate);
+ if (value == null) {
+ if (NullsConstantProvider.isSkipper(_nullProvider)) {
+ return toUpdate;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ }
+ return value;
}
/*
@@ -517,13 +580,17 @@
protected void _throwAsIOE(JsonParser p, Exception e, Object value) throws IOException
{
if (e instanceof IllegalArgumentException) {
- String actType = (value == null) ? "[NULL]" : value.getClass().getName();
- StringBuilder msg = new StringBuilder("Problem deserializing property '").append(getName());
- msg.append("' (expected type: ").append(getType());
- msg.append("; actual type: ").append(actType).append(")");
- String origMsg = e.getMessage();
+ String actType = ClassUtil.classNameOf(value);
+ StringBuilder msg = new StringBuilder("Problem deserializing property '")
+ .append(getName())
+ .append("' (expected type: ")
+ .append(getType())
+ .append("; actual type: ")
+ .append(actType).append(")");
+ String origMsg = ClassUtil.exceptionMessage(e);
if (origMsg != null) {
- msg.append(", problem: ").append(origMsg);
+ msg.append(", problem: ")
+ .append(origMsg);
} else {
msg.append(" (no error message provided)");
}
@@ -537,18 +604,11 @@
*/
protected IOException _throwAsIOE(JsonParser p, Exception e) throws IOException
{
- if (e instanceof IOException) {
- throw (IOException) e;
- }
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- }
+ ClassUtil.throwIfIOE(e);
+ ClassUtil.throwIfRTE(e);
// let's wrap the innermost problem
- Throwable th = e;
- while (th.getCause() != null) {
- th = th.getCause();
- }
- throw JsonMappingException.from(p, th.getMessage(), th);
+ Throwable th = ClassUtil.getRootCause(e);
+ throw JsonMappingException.from(p, ClassUtil.exceptionMessage(th), th);
}
@Deprecated // since 2.7
@@ -557,10 +617,166 @@
}
// 10-Oct-2015, tatu: _Should_ be deprecated, too, but its remaining
- // callers can not actually provide a JsonParser
+ // callers cannot actually provide a JsonParser
protected void _throwAsIOE(Exception e, Object value) throws IOException {
_throwAsIOE((JsonParser) null, e, value);
}
@Override public String toString() { return "[property '"+getName()+"']"; }
+
+ /*
+ /**********************************************************
+ /* Helper classes
+ /**********************************************************
+ */
+
+ /**
+ * Helper class that is designed to both make it easier to sub-class
+ * delegating subtypes and to reduce likelihood of breakage when
+ * new methods are added.
+ *<p>
+ * Class was specifically added to help with {@code Afterburner}
+ * module, but its use is not limited to only support it.
+ *
+ * @since 2.9
+ */
+ public static abstract class Delegating
+ extends SettableBeanProperty
+ {
+ protected final SettableBeanProperty delegate;
+
+ protected Delegating(SettableBeanProperty d) {
+ super(d);
+ delegate = d;
+ }
+
+ /**
+ * Method sub-classes must implement, to construct a new instance
+ * with given delegate.
+ */
+ protected abstract SettableBeanProperty withDelegate(SettableBeanProperty d);
+
+ protected SettableBeanProperty _with(SettableBeanProperty newDelegate) {
+ if (newDelegate == delegate) {
+ return this;
+ }
+ return withDelegate(newDelegate);
+ }
+
+ @Override
+ public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
+ return _with(delegate.withValueDeserializer(deser));
+ }
+
+ @Override
+ public SettableBeanProperty withName(PropertyName newName) {
+ return _with(delegate.withName(newName));
+ }
+
+ @Override
+ public SettableBeanProperty withNullProvider(NullValueProvider nva) {
+ return _with(delegate.withNullProvider(nva));
+ }
+
+ @Override
+ public void assignIndex(int index) {
+ delegate.assignIndex(index);
+ }
+
+ @Override
+ public void fixAccess(DeserializationConfig config) {
+ delegate.fixAccess(config);
+ }
+
+ /*
+ /**********************************************************
+ /* Accessors
+ /**********************************************************
+ */
+
+ @Override
+ protected Class<?> getDeclaringClass() { return delegate.getDeclaringClass(); }
+
+ @Override
+ public String getManagedReferenceName() { return delegate.getManagedReferenceName(); }
+
+ @Override
+ public ObjectIdInfo getObjectIdInfo() { return delegate.getObjectIdInfo(); }
+
+ @Override
+ public boolean hasValueDeserializer() { return delegate.hasValueDeserializer(); }
+
+ @Override
+ public boolean hasValueTypeDeserializer() { return delegate.hasValueTypeDeserializer(); }
+
+ @Override
+ public JsonDeserializer<Object> getValueDeserializer() { return delegate.getValueDeserializer(); }
+
+ @Override
+ public TypeDeserializer getValueTypeDeserializer() { return delegate.getValueTypeDeserializer(); }
+
+ @Override
+ public boolean visibleInView(Class<?> activeView) { return delegate.visibleInView(activeView); }
+
+ @Override
+ public boolean hasViews() { return delegate.hasViews(); }
+
+ @Override
+ public int getPropertyIndex() { return delegate.getPropertyIndex(); }
+
+ @Override
+ public int getCreatorIndex() { return delegate.getCreatorIndex(); }
+
+ @Override
+ public Object getInjectableValueId() { return delegate.getInjectableValueId(); }
+
+ @Override
+ public AnnotatedMember getMember() {
+ return delegate.getMember();
+ }
+
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> acls) {
+ return delegate.getAnnotation(acls);
+ }
+
+ /*
+ /**********************************************************
+ /* Extended API
+ /**********************************************************
+ */
+
+ public SettableBeanProperty getDelegate() {
+ return delegate;
+ }
+
+ /*
+ /**********************************************************
+ /* Actual mutators
+ /**********************************************************
+ */
+
+ @Override
+ public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
+ Object instance) throws IOException {
+ delegate.deserializeAndSet(p, ctxt, instance);
+ }
+
+ @Override
+ public Object deserializeSetAndReturn(JsonParser p,
+ DeserializationContext ctxt, Object instance) throws IOException
+ {
+ return delegate.deserializeSetAndReturn(p, ctxt, instance);
+ }
+
+ @Override
+ public void set(Object instance, Object value) throws IOException {
+ delegate.set(instance, value);
+ }
+
+ @Override
+ public Object setAndReturn(Object instance, Object value) throws IOException {
+ return delegate.setAndReturn(instance, value);
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/UnresolvedId.java b/src/main/java/com/fasterxml/jackson/databind/deser/UnresolvedId.java
index c828616..af5772a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/UnresolvedId.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/UnresolvedId.java
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.databind.deser;
import com.fasterxml.jackson.core.JsonLocation;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Helper class for {@link UnresolvedForwardReference}, to contain information about unresolved ids.
@@ -32,6 +33,6 @@
@Override
public String toString() {
return String.format("Object id [%s] (for %s) at %s", _id,
- (_type == null) ? "NULL" : _type.getName(), _location);
+ ClassUtil.nameOf(_type), _location);
}
-}
\ No newline at end of file
+}
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 fceac96..a7a6951 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java
@@ -18,7 +18,7 @@
* is a scalar value (String, number, boolean).
*<p>
* Note that this type is not parameterized (even though it would seemingly
- * make sense), because such type information can not be use effectively
+ * make sense), because such type information cannot be use effectively
* during runtime: access is always using either wildcard type, or just
* basic {@link java.lang.Object}; and so adding type parameter seems
* like unnecessary extra work.
@@ -186,7 +186,7 @@
* null or empty List.
*/
public Object createUsingDefault(DeserializationContext ctxt) throws IOException {
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no default no-arguments constructor found");
}
@@ -200,7 +200,7 @@
*/
public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) throws IOException {
// sanity check; shouldn't really get called if no Creator specified
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no creator with arguments specified");
}
@@ -234,7 +234,7 @@
* an intermediate "delegate" value to pass to createor method
*/
public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) throws IOException {
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no delegate creator specified");
}
@@ -243,7 +243,7 @@
* an intermediate "delegate" value to pass to createor method
*/
public Object createUsingArrayDelegate(DeserializationContext ctxt, Object delegate) throws IOException {
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no array delegate creator specified");
}
@@ -259,25 +259,25 @@
}
public Object createFromInt(DeserializationContext ctxt, int value) throws IOException {
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no int/Int-argument constructor/factory method to deserialize from Number value (%s)",
value);
}
public Object createFromLong(DeserializationContext ctxt, long value) throws IOException {
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no long/Long-argument constructor/factory method to deserialize from Number value (%s)",
value);
}
public Object createFromDouble(DeserializationContext ctxt, double value) throws IOException {
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no double/Double-argument constructor/factory method to deserialize from Number value (%s)",
value);
}
public Object createFromBoolean(DeserializationContext ctxt, boolean value) throws IOException {
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, null,
"no boolean/Boolean-argument constructor/factory method to deserialize from boolean value (%s)",
value);
}
@@ -368,13 +368,26 @@
return null;
}
}
- return ctxt.handleMissingInstantiator(getValueClass(), ctxt.getParser(),
+ return ctxt.handleMissingInstantiator(getValueClass(), this, ctxt.getParser(),
"no String-argument constructor/factory method to deserialize from String value ('%s')",
value);
}
/*
/**********************************************************
+ /* Introspection
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ public interface Gettable {
+ public ValueInstantiator getValueInstantiator();
+ }
+
+ /*
+ /**********************************************************
/* Standard Base implementation (since 2.8)
/**********************************************************
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiators.java b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiators.java
index 59e5eac..3f04f60 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiators.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiators.java
@@ -26,7 +26,7 @@
* a custom instantiator already)
*
* @return Instantiator to use; either <code>defaultInstantiator</code> that was passed,
- * or a custom variant; can not be null.
+ * or a custom variant; cannot be null.
*/
public ValueInstantiator findValueInstantiator(DeserializationConfig config,
BeanDescription beanDesc, ValueInstantiator defaultInstantiator);
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 dabe451..0dbc50d 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
@@ -15,7 +15,7 @@
private static final long serialVersionUID = 1L;
/**
- * Deserializer we delegate operations that we can not handle.
+ * Deserializer we delegate operations that we cannot handle.
*/
final protected BeanDeserializerBase _delegate;
@@ -25,7 +25,15 @@
final protected SettableBeanProperty[] _orderedProperties;
final protected AnnotatedMethod _buildMethod;
-
+
+ /**
+ * Type that the builder will produce, target type; as opposed to
+ * `handledType()` which refers to Builder class.
+ *
+ * @since 2.9
+ */
+ protected final JavaType _targetType;
+
/*
/**********************************************************
/* Life-cycle, construction, initialization
@@ -36,13 +44,17 @@
* Main constructor used both for creating new instances (by
* {@link BeanDeserializer#asArrayDeserializer}) and for
* creating copies with different delegate.
+ *
+ * @since 2.9
*/
public BeanAsArrayBuilderDeserializer(BeanDeserializerBase delegate,
+ JavaType targetType,
SettableBeanProperty[] ordered,
AnnotatedMethod buildMethod)
{
super(delegate);
_delegate = delegate;
+ _targetType = targetType;
_orderedProperties = ordered;
_buildMethod = buildMethod;
}
@@ -60,19 +72,19 @@
@Override
public BeanDeserializerBase withObjectIdReader(ObjectIdReader oir) {
return new BeanAsArrayBuilderDeserializer(_delegate.withObjectIdReader(oir),
- _orderedProperties, _buildMethod);
+ _targetType, _orderedProperties, _buildMethod);
}
@Override
public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps) {
return new BeanAsArrayBuilderDeserializer(_delegate.withIgnorableProperties(ignorableProps),
- _orderedProperties, _buildMethod);
+ _targetType, _orderedProperties, _buildMethod);
}
@Override
public BeanDeserializerBase withBeanProperties(BeanPropertyMap props) {
return new BeanAsArrayBuilderDeserializer(_delegate.withBeanProperties(props),
- _orderedProperties, _buildMethod);
+ _targetType, _orderedProperties, _buildMethod);
}
@Override
@@ -82,6 +94,18 @@
/*
/**********************************************************
+ /* Overrides
+ /**********************************************************
+ */
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ // 26-Oct-2016, tatu: No, we can't merge Builder-based POJOs as of now
+ return Boolean.FALSE;
+ }
+
+ /*
+ /**********************************************************
/* JsonDeserializer implementation
/**********************************************************
*/
@@ -90,7 +114,7 @@
throws IOException
{
try {
- return _buildMethod.getMember().invoke(builder);
+ return _buildMethod.getMember().invoke(builder, (Object[]) null);
} catch (Exception e) {
return wrapInstantiationProblem(e, ctxt);
}
@@ -130,9 +154,11 @@
}
++i;
}
- // Ok; extra fields? Let's fail, unless ignoring extra props is fine
- if (!_ignoreAllUnknown) {
- ctxt.reportMappingException("Unexpected JSON values; expected at most %d properties (in JSON Array)",
+ // 09-Nov-2016, tatu: Should call `handleUnknownProperty()` in Context, but it'd give
+ // non-optimal exception message so...
+ if (!_ignoreAllUnknown && ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
+ ctxt.reportInputMismatch(handledType(),
+ "Unexpected JSON values; expected at most %d properties (in JSON Array)",
propCount);
// fall through
}
@@ -144,50 +170,11 @@
}
@Override
- public Object deserialize(JsonParser p, DeserializationContext ctxt, Object builder)
+ public Object deserialize(JsonParser p, DeserializationContext ctxt, Object value)
throws IOException
{
- /* No good way to verify that we have an array... although could I guess
- * check via JsonParser. So let's assume everything is working fine, for now.
- */
- if (_injectables != null) {
- injectValues(ctxt, builder);
- }
- final SettableBeanProperty[] props = _orderedProperties;
- int i = 0;
- final int propCount = props.length;
- while (true) {
- if (p.nextToken() == JsonToken.END_ARRAY) {
- return finishBuild(ctxt, builder);
- }
- if (i == propCount) {
- break;
- }
- SettableBeanProperty prop = props[i];
- if (prop != null) { // normal case
- try {
- builder = prop.deserializeSetAndReturn(p, ctxt, builder);
- } catch (Exception e) {
- wrapAndThrow(e, builder, prop.getName(), ctxt);
- }
- } else { // just skip?
- p.skipChildren();
- }
- ++i;
- }
-
- // Ok; extra fields? Let's fail, unless ignoring extra props is fine
- if (!_ignoreAllUnknown) {
- ctxt.reportWrongTokenException(p, JsonToken.END_ARRAY,
- "Unexpected JSON values; expected at most %d properties (in JSON Array)",
- propCount);
- // never gets here
- }
- // otherwise, skip until end
- do {
- p.skipChildren();
- } while (p.nextToken() != JsonToken.END_ARRAY);
- return finishBuild(ctxt, builder);
+ // 26-Oct-2016, tatu: Will fail, but let the original deserializer provide message
+ return _delegate.deserialize(p, ctxt, value);
}
// needed since 2.1
@@ -247,8 +234,8 @@
p.skipChildren();
}
// Ok; extra fields? Let's fail, unless ignoring extra props is fine
- if (!_ignoreAllUnknown) {
- ctxt.reportWrongTokenException(p, JsonToken.END_ARRAY,
+ if (!_ignoreAllUnknown && ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
+ ctxt.reportWrongTokenException(this, JsonToken.END_ARRAY,
"Unexpected JSON value(s); expected at most %d properties (in JSON Array)",
propCount);
// will never reach here as exception has been thrown
@@ -278,6 +265,7 @@
final SettableBeanProperty[] props = _orderedProperties;
final int propCount = props.length;
+ final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
int i = 0;
Object builder = null;
@@ -287,6 +275,10 @@
p.skipChildren();
continue;
}
+ if ((activeView != null) && !prop.visibleInView(activeView)) {
+ p.skipChildren();
+ continue;
+ }
// if we have already constructed POJO, things are simple:
if (builder != null) {
try {
@@ -314,10 +306,9 @@
* supported (since ordering of elements may not be guaranteed);
* but make explicitly non-supported for now.
*/
- ctxt.reportMappingException("Can not support implicit polymorphic deserialization for POJOs-as-Arrays style: "
- +"nominal type %s, actual type %s",
- _beanType.getRawClass().getName(), builder.getClass().getName());
- return null;
+ 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()));
}
}
continue;
@@ -352,7 +343,7 @@
{
// Let's start with failure
return ctxt.handleUnexpectedToken(handledType(), p.getCurrentToken(), p,
- "Can not deserialize a POJO (of type %s) from non-Array representation (token: %s): "
+ "Cannot deserialize a POJO (of type %s) from non-Array representation (token: %s): "
+"type/property designed to be serialized as JSON Array",
_beanType.getRawClass().getName(),
p.getCurrentToken());
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 1cff5ca..2b39004 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
@@ -21,7 +21,7 @@
private static final long serialVersionUID = 1L;
/**
- * Deserializer we delegate operations that we can not handle.
+ * Deserializer we delegate operations that we cannot handle.
*/
protected final BeanDeserializerBase _delegate;
@@ -126,8 +126,8 @@
++i;
}
// Ok; extra fields? Let's fail, unless ignoring extra props is fine
- if (!_ignoreAllUnknown) {
- ctxt.reportWrongTokenException(p, JsonToken.END_ARRAY,
+ if (!_ignoreAllUnknown && ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
+ ctxt.reportWrongTokenException(this, JsonToken.END_ARRAY,
"Unexpected JSON values; expected at most %d properties (in JSON Array)",
propCount);
// never gets here
@@ -145,6 +145,11 @@
{
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
+
+ if (!p.isExpectedStartArrayToken()) {
+ return _deserializeFromNonArray(p, ctxt);
+ }
+
/* No good way to verify that we have an array... although could I guess
* check via JsonParser. So let's assume everything is working fine, for now.
*/
@@ -175,8 +180,8 @@
}
// Ok; extra fields? Let's fail, unless ignoring extra props is fine
- if (!_ignoreAllUnknown) {
- ctxt.reportWrongTokenException(p, JsonToken.END_ARRAY,
+ if (!_ignoreAllUnknown && ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
+ ctxt.reportWrongTokenException(this, JsonToken.END_ARRAY,
"Unexpected JSON values; expected at most %d properties (in JSON Array)",
propCount);
// never gets here
@@ -222,6 +227,7 @@
final SettableBeanProperty[] props = _orderedProperties;
int i = 0;
final int propCount = props.length;
+
while (true) {
if (p.nextToken() == JsonToken.END_ARRAY) {
return bean;
@@ -246,7 +252,7 @@
}
// Ok; extra fields? Let's fail, unless ignoring extra props is fine
if (!_ignoreAllUnknown) {
- ctxt.reportWrongTokenException(p, JsonToken.END_ARRAY,
+ ctxt.reportWrongTokenException(this, JsonToken.END_ARRAY,
"Unexpected JSON values; expected at most %d properties (in JSON Array)",
propCount);
// will never reach here as exception has been thrown
@@ -277,13 +283,19 @@
final int propCount = props.length;
int i = 0;
Object bean = null;
-
+ final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
+
for (; p.nextToken() != JsonToken.END_ARRAY; ++i) {
SettableBeanProperty prop = (i < propCount) ? props[i] : null;
if (prop == null) { // we get null if there are extra elements; maybe otherwise too?
p.skipChildren();
continue;
}
+ if ((activeView != null) && !prop.visibleInView(activeView)) {
+ p.skipChildren();
+ continue;
+ }
+
// if we have already constructed POJO, things are simple:
if (bean != null) {
try {
@@ -314,9 +326,10 @@
* supported (since ordering of elements may not be guaranteed);
* but make explicitly non-supported for now.
*/
- ctxt.reportMappingException("Can not support implicit polymorphic deserialization for POJOs-as-Arrays style: "
+ 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());
+ _beanType.getRawClass().getName(), bean.getClass().getName()));
}
}
continue;
@@ -350,7 +363,7 @@
throws IOException
{
return ctxt.handleUnexpectedToken(handledType(), p.getCurrentToken(), p,
- "Can not deserialize a POJO (of type %s) from non-Array representation (token: %s): "
+ "Cannot deserialize a POJO (of type %s) from non-Array representation (token: %s): "
+"type/property designed to be serialized as JSON Array",
_beanType.getRawClass().getName(),
p.getCurrentToken());
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 8031ee0..b015bb5 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
@@ -10,7 +10,9 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.NameTransformer;
/**
@@ -41,7 +43,7 @@
* Number of entries stored in the hash area.
*/
private int _size;
-
+
private int _spillCount;
/**
@@ -53,24 +55,120 @@
* Array of properties in the exact order they were handed in. This is
* used by as-array serialization, deserialization.
*/
- private SettableBeanProperty[] _propsInOrder;
+ private final SettableBeanProperty[] _propsInOrder;
- public BeanPropertyMap(boolean caseInsensitive, Collection<SettableBeanProperty> props)
+ /**
+ * Configuration of alias mappings, indexed by unmodified property name
+ * to unmodified aliases, if any; entries only included for properties
+ * that do have aliases.
+ * This is is used for constructing actual reverse lookup mapping, if
+ * needed, taking into account possible case-insensitivity, as well
+ * as possibility of name prefixes.
+ *
+ * @since 2.9
+ */
+ private final Map<String,List<PropertyName>> _aliasDefs;
+
+ /**
+ * Mapping from secondary names (aliases) to primary names.
+ *
+ * @since 2.9
+ */
+ private final Map<String,String> _aliasMapping;
+
+ /**
+ * @since 2.9
+ */
+ public BeanPropertyMap(boolean caseInsensitive, Collection<SettableBeanProperty> props,
+ Map<String,List<PropertyName>> aliasDefs)
{
_caseInsensitive = caseInsensitive;
_propsInOrder = props.toArray(new SettableBeanProperty[props.size()]);
+ _aliasDefs = aliasDefs;
+ _aliasMapping = _buildAliasMapping(aliasDefs);
init(props);
}
+ /* Copy constructors used when a property can replace existing one
+ *
+ * @since 2.9.6
+ */
+ private BeanPropertyMap(BeanPropertyMap src,
+ SettableBeanProperty newProp, int hashIndex, int orderedIndex)
+ {
+ // First, copy most fields as is:
+ _caseInsensitive = src._caseInsensitive;
+ _hashMask = src._hashMask;
+ _size = src._size;
+ _spillCount = src._spillCount;
+ _aliasDefs = src._aliasDefs;
+ _aliasMapping = src._aliasMapping;
+
+ // but then make deep copy of arrays to modify
+ _hashArea = Arrays.copyOf(src._hashArea, src._hashArea.length);
+ _propsInOrder = Arrays.copyOf(src._propsInOrder, src._propsInOrder.length);
+ _hashArea[hashIndex] = newProp;
+ _propsInOrder[orderedIndex] = newProp;
+ }
+
+ /* Copy constructors used when a property needs to be appended (can't replace)
+ *
+ * @since 2.9.6
+ */
+ private BeanPropertyMap(BeanPropertyMap src,
+ SettableBeanProperty newProp, String key, int slot)
+ {
+ // First, copy most fields as is:
+ _caseInsensitive = src._caseInsensitive;
+ _hashMask = src._hashMask;
+ _size = src._size;
+ _spillCount = src._spillCount;
+ _aliasDefs = src._aliasDefs;
+ _aliasMapping = src._aliasMapping;
+
+ // but then make deep copy of arrays to modify
+ _hashArea = Arrays.copyOf(src._hashArea, src._hashArea.length);
+ int last = src._propsInOrder.length;
+ // and append property at the end of ordering
+ _propsInOrder = Arrays.copyOf(src._propsInOrder, last+1);
+ _propsInOrder[last] = newProp;
+
+ final int hashSize = _hashMask+1;
+ int ix = (slot<<1);
+
+ // primary slot not free?
+ if (_hashArea[ix] != null) {
+ // secondary?
+ ix = (hashSize + (slot >> 1)) << 1;
+ if (_hashArea[ix] != null) {
+ // ok, spill over.
+ ix = ((hashSize + (hashSize >> 1) ) << 1) + _spillCount;
+ _spillCount += 2;
+ if (ix >= _hashArea.length) {
+ _hashArea = Arrays.copyOf(_hashArea, _hashArea.length + 4);
+ }
+ }
+ }
+ _hashArea[ix] = key;
+ _hashArea[ix+1] = newProp;
+ }
+
+ @Deprecated // since 2.8
+ public BeanPropertyMap(boolean caseInsensitive, Collection<SettableBeanProperty> props)
+ {
+ this(caseInsensitive, props, Collections.<String,List<PropertyName>>emptyMap());
+ }
+
/**
* @since 2.8
*/
protected BeanPropertyMap(BeanPropertyMap base, boolean caseInsensitive)
{
_caseInsensitive = caseInsensitive;
+ _aliasDefs = base._aliasDefs;
+ _aliasMapping = base._aliasMapping;
- // 16-May-2016, tatu: Alas, not enough to just change flag, need to re-init
- // as well.
+ // 16-May-2016, tatu: Alas, not enough to just change flag, need to re-init as well.
_propsInOrder = Arrays.copyOf(base._propsInOrder, base._propsInOrder.length);
init(Arrays.asList(_propsInOrder));
}
@@ -107,7 +205,7 @@
if (prop == null) {
continue;
}
-
+
String key = getPropertyName(prop);
int slot = _hashCode(key);
int ix = (slot<<1);
@@ -125,19 +223,15 @@
}
}
}
-//System.err.println(" add '"+key+" at #"+(ix>>1)+"/"+size+" (hashed at "+slot+")");
hashed[ix] = key;
hashed[ix+1] = prop;
+
+ // and aliases
}
-/*
-for (int i = 0; i < hashed.length; i += 2) {
-System.err.printf("#%02d: %s\n", i>>1, (hashed[i] == null) ? "-" : hashed[i]);
-}
-*/
_hashArea = hashed;
_spillCount = spillCount;
}
-
+
private final static int findSize(int size)
{
if (size <= 5) {
@@ -157,10 +251,17 @@
/**
* @since 2.6
*/
- public static BeanPropertyMap construct(Collection<SettableBeanProperty> props, boolean caseInsensitive) {
- return new BeanPropertyMap(caseInsensitive, props);
+ public static BeanPropertyMap construct(Collection<SettableBeanProperty> props,
+ boolean caseInsensitive, Map<String,List<PropertyName>> aliasMapping) {
+ 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
@@ -176,49 +277,13 @@
for (int i = 1, end = _hashArea.length; i < end; i += 2) {
SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
if ((prop != null) && prop.getName().equals(key)) {
- _hashArea[i] = newProp;
- _propsInOrder[_findFromOrdered(prop)] = newProp;
- return this;
+ return new BeanPropertyMap(this, newProp, i, _findFromOrdered(prop));
}
}
// If not, append
final int slot = _hashCode(key);
- final int hashSize = _hashMask+1;
- int ix = (slot<<1);
-
- // primary slot not free?
- if (_hashArea[ix] != null) {
- // secondary?
- ix = (hashSize + (slot >> 1)) << 1;
- if (_hashArea[ix] != null) {
- // ok, spill over.
- ix = ((hashSize + (hashSize >> 1) ) << 1) + _spillCount;
- _spillCount += 2;
- if (ix >= _hashArea.length) {
- _hashArea = Arrays.copyOf(_hashArea, _hashArea.length + 4);
- // Uncomment for debugging only
- /*
-for (int i = 0; i < _hashArea.length; i += 2) {
- if (_hashArea[i] != null) {
- System.err.println("Property #"+(i/2)+" '"+_hashArea[i]+"'...");
- }
-}
-System.err.println("And new propr #"+slot+" '"+key+"'");
-*/
-
- }
- }
- }
- _hashArea[ix] = key;
- _hashArea[ix+1] = newProp;
- int last = _propsInOrder.length;
- _propsInOrder = Arrays.copyOf(_propsInOrder, last+1);
- _propsInOrder[last] = newProp;
-
- // should we just create a new one? Or is resetting ok?
-
- return this;
+ return new BeanPropertyMap(this, newProp, key, slot);
}
public BeanPropertyMap assignIndexes()
@@ -258,9 +323,16 @@
newProps.add(_rename(prop, transformer));
}
// should we try to re-index? Ordering probably changed but caller probably doesn't want changes...
- return new BeanPropertyMap(_caseInsensitive, newProps);
+ // 26-Feb-2017, tatu: Probably SHOULD handle renaming wrt Aliases?
+ return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs);
}
+ /*
+ /**********************************************************
+ /* Public API, mutators
+ /**********************************************************
+ */
+
/**
* Mutant factory method that will use this instance as the base, and
* construct an instance that is otherwise same except for excluding
@@ -288,137 +360,47 @@
}
}
// should we try to re-index? Apparently no need
- return new BeanPropertyMap(_caseInsensitive, newProps);
+ return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs);
}
-
- /**
- * Specialized method that can be used to replace an existing entry
- * (note: entry MUST exist; otherwise exception is thrown) with
- * specified replacement.
- */
+
+ @Deprecated // in 2.9.4 -- must call method that takes old and new property to avoid mismatch
public void replace(SettableBeanProperty newProp)
{
String key = getPropertyName(newProp);
int ix = _findIndexInHash(key);
-
- if (ix >= 0) {
- SettableBeanProperty prop = (SettableBeanProperty) _hashArea[ix];
- _hashArea[ix] = newProp;
- // also, replace in in-order
- _propsInOrder[_findFromOrdered(prop)] = newProp;
- return;
+ if (ix < 0) {
+ throw new NoSuchElementException("No entry '"+key+"' found, can't replace");
}
-
- throw new NoSuchElementException("No entry '"+key+"' found, can't replace");
- }
-
- private List<SettableBeanProperty> properties() {
- ArrayList<SettableBeanProperty> p = new ArrayList<SettableBeanProperty>(_size);
- for (int i = 1, end = _hashArea.length; i < end; i += 2) {
- SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
- if (prop != null) {
- p.add(prop);
- }
- }
- return p;
+ SettableBeanProperty prop = (SettableBeanProperty) _hashArea[ix];
+ _hashArea[ix] = newProp;
+ // also, replace in in-order
+ _propsInOrder[_findFromOrdered(prop)] = newProp;
}
/**
- * Accessor for traversing over all contained properties.
+ * Specialized method that can be used to replace an existing entry
+ * (note: entry MUST exist; otherwise exception is thrown) with
+ * specified replacement.
+ *
+ * @since 2.9.4
*/
- @Override
- public Iterator<SettableBeanProperty> iterator() {
- return properties().iterator();
- }
-
- /**
- * Method that will re-create initial insertion-ordering of
- * properties contained in this map. Note that if properties
- * have been removed, array may contain nulls; otherwise
- * it should be consecutive.
- *
- * @since 2.1
- */
- public SettableBeanProperty[] getPropertiesInInsertionOrder() {
- return _propsInOrder;
- }
-
- // Confining this case insensitivity to this function (and the find method) in case we want to
- // apply a particular locale to the lower case function. For now, using the default.
- protected final String getPropertyName(SettableBeanProperty prop) {
- return _caseInsensitive ? prop.getName().toLowerCase() : prop.getName();
- }
-
- /**
- * @since 2.3
- */
- public SettableBeanProperty find(int index)
+ public void replace(SettableBeanProperty origProp, SettableBeanProperty newProp)
{
- // note: will scan the whole area, including primary, secondary and
- // possible spill-area
- for (int i = 1, end = _hashArea.length; i < end; i += 2) {
- SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
- if ((prop != null) && (index == prop.getPropertyIndex())) {
- return prop;
+ int i = 1;
+ int end = _hashArea.length;
+
+ for (;; i += 2) {
+ if (i > end) {
+ throw new NoSuchElementException("No entry '"+origProp.getName()+"' found, can't replace");
+ }
+ if (_hashArea[i] == origProp) {
+ _hashArea[i] = newProp;
+ break;
}
}
- return null;
+ _propsInOrder[_findFromOrdered(origProp)] = newProp;
}
- public SettableBeanProperty find(String key)
- {
- if (key == null) {
- throw new IllegalArgumentException("Can not pass null property name");
- }
- if (_caseInsensitive) {
- key = key.toLowerCase();
- }
-
- // inlined `_hashCode(key)`
- int slot = key.hashCode() & _hashMask;
-// int h = key.hashCode();
-// int slot = (h + (h >> 13)) & _hashMask;
-
- int ix = (slot<<1);
- Object match = _hashArea[ix];
- if ((match == key) || key.equals(match)) {
- return (SettableBeanProperty) _hashArea[ix+1];
- }
- return _find2(key, slot, match);
- }
-
- private final SettableBeanProperty _find2(String key, int slot, Object match)
- {
- if (match == null) {
- return null;
- }
- // no? secondary?
- int hashSize = _hashMask+1;
- int ix = hashSize + (slot>>1) << 1;
- match = _hashArea[ix];
- if (key.equals(match)) {
- return (SettableBeanProperty) _hashArea[ix+1];
- }
- if (match != null) { // _findFromSpill(...)
- int i = (hashSize + (hashSize>>1)) << 1;
- for (int end = i + _spillCount; i < end; i += 2) {
- match = _hashArea[i];
- if ((match == key) || key.equals(match)) {
- return (SettableBeanProperty) _hashArea[i+1];
- }
- }
- }
- return null;
- }
-
- /*
- /**********************************************************
- /* Public API
- /**********************************************************
- */
-
- public int size() { return _size; }
-
/**
* Specialized method for removing specified existing entry.
* NOTE: entry MUST exist, otherwise an exception is thrown.
@@ -452,6 +434,181 @@
init(props);
}
+ /*
+ /**********************************************************
+ /* Public API, simple accessors
+ /**********************************************************
+ */
+
+ public int size() { return _size; }
+
+ /**
+ * @since 2.9
+ */
+ public boolean isCaseInsensitive() {
+ return _caseInsensitive;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public boolean hasAliases() {
+ return !_aliasDefs.isEmpty();
+ }
+
+ /**
+ * Accessor for traversing over all contained properties.
+ */
+ @Override
+ public Iterator<SettableBeanProperty> iterator() {
+ return _properties().iterator();
+ }
+
+ private List<SettableBeanProperty> _properties() {
+ ArrayList<SettableBeanProperty> p = new ArrayList<SettableBeanProperty>(_size);
+ for (int i = 1, end = _hashArea.length; i < end; i += 2) {
+ SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
+ if (prop != null) {
+ p.add(prop);
+ }
+ }
+ return p;
+ }
+
+ /**
+ * Method that will re-create initial insertion-ordering of
+ * properties contained in this map. Note that if properties
+ * have been removed, array may contain nulls; otherwise
+ * it should be consecutive.
+ *
+ * @since 2.1
+ */
+ public SettableBeanProperty[] getPropertiesInInsertionOrder() {
+ return _propsInOrder;
+ }
+
+ // Confining this case insensitivity to this function (and the find method) in case we want to
+ // apply a particular locale to the lower case function. For now, using the default.
+ protected final String getPropertyName(SettableBeanProperty prop) {
+ return _caseInsensitive ? prop.getName().toLowerCase() : prop.getName();
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, property lookup
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.3
+ */
+ public SettableBeanProperty find(int index)
+ {
+ // note: will scan the whole area, including primary, secondary and
+ // possible spill-area
+ for (int i = 1, end = _hashArea.length; i < end; i += 2) {
+ SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
+ if ((prop != null) && (index == prop.getPropertyIndex())) {
+ return prop;
+ }
+ }
+ return null;
+ }
+
+ public SettableBeanProperty find(String key)
+ {
+ if (key == null) {
+ throw new IllegalArgumentException("Cannot pass null property name");
+ }
+ if (_caseInsensitive) {
+ key = key.toLowerCase();
+ }
+
+ // inlined `_hashCode(key)`
+ int slot = key.hashCode() & _hashMask;
+// int h = key.hashCode();
+// int slot = (h + (h >> 13)) & _hashMask;
+
+ int ix = (slot<<1);
+ Object match = _hashArea[ix];
+ if ((match == key) || key.equals(match)) {
+ return (SettableBeanProperty) _hashArea[ix+1];
+ }
+ return _find2(key, slot, match);
+ }
+
+ private final SettableBeanProperty _find2(String key, int slot, Object match)
+ {
+ if (match == null) {
+ // 26-Feb-2017, tatu: Need to consider aliases
+ return _findWithAlias(_aliasMapping.get(key));
+ }
+ // no? secondary?
+ int hashSize = _hashMask+1;
+ int ix = hashSize + (slot>>1) << 1;
+ match = _hashArea[ix];
+ if (key.equals(match)) {
+ return (SettableBeanProperty) _hashArea[ix+1];
+ }
+ if (match != null) { // _findFromSpill(...)
+ int i = (hashSize + (hashSize>>1)) << 1;
+ for (int end = i + _spillCount; i < end; i += 2) {
+ match = _hashArea[i];
+ if ((match == key) || key.equals(match)) {
+ return (SettableBeanProperty) _hashArea[i+1];
+ }
+ }
+ }
+ // 26-Feb-2017, tatu: Need to consider aliases
+ return _findWithAlias(_aliasMapping.get(key));
+ }
+
+ private SettableBeanProperty _findWithAlias(String keyFromAlias)
+ {
+ if (keyFromAlias == null) {
+ return null;
+ }
+ // NOTE: need to inline much of handling do avoid cyclic calls via alias
+ // first, inlined main `find(String)`
+ int slot = _hashCode(keyFromAlias);
+ int ix = (slot<<1);
+ Object match = _hashArea[ix];
+ if (keyFromAlias.equals(match)) {
+ return (SettableBeanProperty) _hashArea[ix+1];
+ }
+ if (match == null) {
+ return null;
+ }
+ return _find2ViaAlias(keyFromAlias, slot, match);
+ }
+
+ private SettableBeanProperty _find2ViaAlias(String key, int slot, Object match)
+ {
+ // no? secondary?
+ int hashSize = _hashMask+1;
+ int ix = hashSize + (slot>>1) << 1;
+ match = _hashArea[ix];
+ if (key.equals(match)) {
+ return (SettableBeanProperty) _hashArea[ix+1];
+ }
+ if (match != null) { // _findFromSpill(...)
+ int i = (hashSize + (hashSize>>1)) << 1;
+ for (int end = i + _spillCount; i < end; i += 2) {
+ match = _hashArea[i];
+ if ((match == key) || key.equals(match)) {
+ return (SettableBeanProperty) _hashArea[i+1];
+ }
+ }
+ }
+ return null;
+ }
+
+ /*
+ /**********************************************************
+ /* Public API, deserialization support
+ /**********************************************************
+ */
+
/**
* Convenience method that tries to find property with given name, and
* if it is found, call {@link SettableBeanProperty#deserializeAndSet}
@@ -476,6 +633,12 @@
return true;
}
+ /*
+ /**********************************************************
+ /* Std method overrides
+ /**********************************************************
+ */
+
@Override
public String toString()
{
@@ -495,6 +658,11 @@
sb.append(')');
}
sb.append(']');
+ if (!_aliasDefs.isEmpty()) {
+ sb.append("(aliases: ");
+ sb.append(_aliasDefs);
+ sb.append(")");
+ }
return sb.toString();
}
@@ -531,9 +699,7 @@
t = t.getCause();
}
// Errors to be passed as is
- if (t instanceof Error) {
- throw (Error) t;
- }
+ ClassUtil.throwIfError(t);
// StackOverflowErrors are tricky ones; need to be careful...
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
// Ditto for IOExceptions; except we may want to wrap JSON exceptions
@@ -542,9 +708,7 @@
throw (IOException) t;
}
} else if (!wrap) { // allow disabling wrapping for unchecked exceptions
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
+ ClassUtil.throwIfRTE(t);
}
throw JsonMappingException.wrapWithPath(t, bean, fieldName);
}
@@ -604,4 +768,27 @@
*/
return key.hashCode() & _hashMask;
}
+
+ // @since 2.9
+ private Map<String,String> _buildAliasMapping(Map<String,List<PropertyName>> defs)
+ {
+ if ((defs == null) || defs.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Map<String,String> aliases = new HashMap<>();
+ for (Map.Entry<String,List<PropertyName>> entry : defs.entrySet()) {
+ String key = entry.getKey();
+ if (_caseInsensitive) {
+ key = key.toLowerCase();
+ }
+ for (PropertyName pn : entry.getValue()) {
+ String mapped = pn.getSimpleName();
+ if (_caseInsensitive) {
+ mapped = mapped.toLowerCase();
+ }
+ aliases.put(mapped, key);
+ }
+ }
+ return aliases;
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCandidate.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCandidate.java
new file mode 100644
index 0000000..64ab178
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCandidate.java
@@ -0,0 +1,122 @@
+package com.fasterxml.jackson.databind.deser.impl;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.PropertyName;
+import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
+import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
+
+public final class CreatorCandidate
+{
+ protected final AnnotationIntrospector _intr;
+ protected final AnnotatedWithParams _creator;
+ protected final int _paramCount;
+ protected final Param[] _params;
+
+ protected CreatorCandidate(AnnotationIntrospector intr,
+ AnnotatedWithParams ct, Param[] params, int count) {
+ _intr = intr;
+ _creator = ct;
+ _params = params;
+ _paramCount = count;
+ }
+
+ public static CreatorCandidate construct(AnnotationIntrospector intr,
+ AnnotatedWithParams creator, BeanPropertyDefinition[] propDefs)
+ {
+ final int pcount = creator.getParameterCount();
+ Param[] params = new Param[pcount];
+ for (int i = 0; i < pcount; ++i) {
+ AnnotatedParameter annParam = creator.getParameter(i);
+ JacksonInject.Value injectId = intr.findInjectableValue(annParam);
+ params[i] = new Param(annParam, (propDefs == null) ? null : propDefs[i], injectId);
+ }
+ return new CreatorCandidate(intr, creator, params, pcount);
+ }
+
+ public AnnotatedWithParams creator() { return _creator; }
+ public int paramCount() { return _paramCount; }
+ public JacksonInject.Value injection(int i) { return _params[i].injection; }
+ public AnnotatedParameter parameter(int i) { return _params[i].annotated; }
+ public BeanPropertyDefinition propertyDef(int i) { return _params[i].propDef; }
+
+ public PropertyName paramName(int i) {
+ BeanPropertyDefinition propDef = _params[i].propDef;
+ if (propDef != null) {
+ return propDef.getFullName();
+ }
+ return null;
+ }
+
+ public PropertyName explicitParamName(int i) {
+ BeanPropertyDefinition propDef = _params[i].propDef;
+ if (propDef != null) {
+ if (propDef.isExplicitlyNamed()) {
+ return propDef.getFullName();
+ }
+ }
+ return null;
+ }
+
+ public PropertyName findImplicitParamName(int i) {
+ String str = _intr.findImplicitPropertyName(_params[i].annotated);
+ if (str != null && !str.isEmpty()) {
+ return PropertyName.construct(str);
+ }
+ return null;
+ }
+
+ /**
+ * Specialized accessor that finds index of the one and only parameter
+ * with NO injection and returns that; or, if none or more than one found,
+ * returns -1.
+ */
+ public int findOnlyParamWithoutInjection()
+ {
+ int missing = -1;
+ for (int i = 0; i < _paramCount; ++i) {
+ if (_params[i].injection == null) {
+ if (missing >= 0) {
+ return -1;
+ }
+ missing = i;
+ }
+ }
+ return missing;
+ }
+
+ @Override
+ public String toString() {
+ return _creator.toString();
+ }
+
+ public final static class Param {
+ public final AnnotatedParameter annotated;
+ public final BeanPropertyDefinition propDef;
+ public final JacksonInject.Value injection;
+
+ public Param(AnnotatedParameter p, BeanPropertyDefinition pd,
+ JacksonInject.Value i)
+ {
+ annotated = p;
+ propDef = pd;
+ injection = i;
+ }
+
+ public PropertyName fullName() {
+ if (propDef == null) {
+ return null;
+ }
+ return propDef.getFullName();
+ }
+
+ public boolean hasFullName() {
+ if (propDef == null) {
+ return false;
+ }
+ PropertyName n = propDef.getFullName();
+ return n.hasSimpleName();
+ }
+ }
+}
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 2999341..978c408 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
@@ -7,7 +7,6 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
-import com.fasterxml.jackson.databind.deser.CreatorProperty;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
@@ -69,11 +68,10 @@
protected SettableBeanProperty[] _propertyBasedArgs;
- protected AnnotatedParameter _incompleteParameter;
-
/*
- * /********************************************************** /* Life-cycle
- * /**********************************************************
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
*/
public CreatorCollector(BeanDescription beanDesc, MapperConfig<?> config) {
@@ -83,11 +81,13 @@
.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS);
}
- public ValueInstantiator constructValueInstantiator(
- DeserializationConfig config) {
- final JavaType delegateType = _computeDelegateType(
+ public ValueInstantiator constructValueInstantiator(DeserializationContext ctxt)
+ throws JsonMappingException
+ {
+ final DeserializationConfig config = ctxt.getConfig();
+ final JavaType delegateType = _computeDelegateType(ctxt,
_creators[C_DELEGATE], _delegateArgs);
- final JavaType arrayDelegateType = _computeDelegateType(
+ final JavaType arrayDelegateType = _computeDelegateType(ctxt,
_creators[C_ARRAY_DELEGATE], _arrayDelegateArgs);
final JavaType type = _beanDesc.getType();
@@ -108,13 +108,13 @@
inst.configureFromLongCreator(_creators[C_LONG]);
inst.configureFromDoubleCreator(_creators[C_DOUBLE]);
inst.configureFromBooleanCreator(_creators[C_BOOLEAN]);
- inst.configureIncompleteParameter(_incompleteParameter);
return inst;
}
/*
- * /********************************************************** /* Setters
- * /**********************************************************
+ /**********************************************************
+ /* Setters
+ /**********************************************************
*/
/**
@@ -131,8 +131,7 @@
_creators[C_DEFAULT] = _fixAccess(creator);
}
- public void addStringCreator(AnnotatedWithParams creator,
- boolean explicit) {
+ public void addStringCreator(AnnotatedWithParams creator, boolean explicit) {
verifyNonDup(creator, C_STRING, explicit);
}
@@ -144,19 +143,19 @@
verifyNonDup(creator, C_LONG, explicit);
}
- public void addDoubleCreator(AnnotatedWithParams creator,
- boolean explicit) {
+ public void addDoubleCreator(AnnotatedWithParams creator, boolean explicit) {
verifyNonDup(creator, C_DOUBLE, explicit);
}
- public void addBooleanCreator(AnnotatedWithParams creator,
- boolean explicit) {
+ public void addBooleanCreator(AnnotatedWithParams creator, boolean explicit) {
verifyNonDup(creator, C_BOOLEAN, explicit);
}
public void addDelegatingCreator(AnnotatedWithParams creator,
- boolean explicit, SettableBeanProperty[] injectables) {
- if (creator.getParameterType(0).isCollectionLikeType()) {
+ boolean explicit, SettableBeanProperty[] injectables,
+ int delegateeIndex)
+ {
+ if (creator.getParameterType(delegateeIndex).isCollectionLikeType()) {
if (verifyNonDup(creator, C_ARRAY_DELEGATE, explicit)) {
_arrayDelegateArgs = injectables;
}
@@ -168,7 +167,8 @@
}
public void addPropertyCreator(AnnotatedWithParams creator,
- boolean explicit, SettableBeanProperty[] properties) {
+ boolean explicit, SettableBeanProperty[] properties)
+ {
if (verifyNonDup(creator, C_PROPS, explicit)) {
// Better ensure we have no duplicate names either...
if (properties.length > 1) {
@@ -177,15 +177,14 @@
String name = properties[i].getName();
// Need to consider Injectables, which may not have
// a name at all, and need to be skipped
- if (name.length() == 0
- && properties[i].getInjectableValueId() != null) {
+ if (name.isEmpty() && (properties[i].getInjectableValueId() != null)) {
continue;
}
Integer old = names.put(name, Integer.valueOf(i));
if (old != null) {
throw new IllegalArgumentException(String.format(
- "Duplicate creator property \"%s\" (index %s vs %d)",
- name, old, i));
+ "Duplicate creator property \"%s\" (index %s vs %d) for type %s ",
+ name, old, i, ClassUtil.nameOf(_beanDesc.getBeanClass())));
}
}
}
@@ -193,54 +192,10 @@
}
}
- public void addIncompeteParameter(AnnotatedParameter parameter) {
- if (_incompleteParameter == null) {
- _incompleteParameter = parameter;
- }
- }
-
- // Bunch of methods deprecated in 2.5, to be removed from 2.6 or later
-
- @Deprecated // since 2.5
- public void addStringCreator(AnnotatedWithParams creator) {
- addStringCreator(creator, false);
- }
-
- @Deprecated // since 2.5
- public void addIntCreator(AnnotatedWithParams creator) {
- addBooleanCreator(creator, false);
- }
-
- @Deprecated // since 2.5
- public void addLongCreator(AnnotatedWithParams creator) {
- addBooleanCreator(creator, false);
- }
-
- @Deprecated // since 2.5
- public void addDoubleCreator(AnnotatedWithParams creator) {
- addBooleanCreator(creator, false);
- }
-
- @Deprecated // since 2.5
- public void addBooleanCreator(AnnotatedWithParams creator) {
- addBooleanCreator(creator, false);
- }
-
- @Deprecated // since 2.5
- public void addDelegatingCreator(AnnotatedWithParams creator,
- CreatorProperty[] injectables) {
- addDelegatingCreator(creator, false, injectables);
- }
-
- @Deprecated // since 2.5
- public void addPropertyCreator(AnnotatedWithParams creator,
- CreatorProperty[] properties) {
- addPropertyCreator(creator, false, properties);
- }
-
/*
- * /********************************************************** /* Accessors
- * /**********************************************************
+ /**********************************************************
+ /* Accessors
+ /**********************************************************
*/
/**
@@ -265,12 +220,15 @@
}
/*
- * /********************************************************** /* Helper
- * methods /**********************************************************
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
*/
- private JavaType _computeDelegateType(AnnotatedWithParams creator,
- SettableBeanProperty[] delegateArgs) {
+ private JavaType _computeDelegateType(DeserializationContext ctxt,
+ AnnotatedWithParams creator, SettableBeanProperty[] delegateArgs)
+ throws JsonMappingException
+ {
if (!_hasNonDefaultCreator || (creator == null)) {
return null;
}
@@ -284,7 +242,28 @@
}
}
}
- return creator.getParameterType(ix);
+ final DeserializationConfig config = ctxt.getConfig();
+
+ // 03-May-2018, tatu: need to check possible annotation-based
+ // custom deserializer [databind#2012],
+ // type refinement(s) [databind#2016].
+ JavaType baseType = creator.getParameterType(ix);
+ AnnotationIntrospector intr = config.getAnnotationIntrospector();
+ if (intr != null) {
+ AnnotatedParameter delegate = creator.getParameter(ix);
+
+ // First: custom deserializer(s):
+ Object deserDef = intr.findDeserializer(delegate);
+ if (deserDef != null) {
+ JsonDeserializer<Object> deser = ctxt.deserializerInstance(delegate, deserDef);
+ baseType = baseType.withValueHandler(deser);
+ } else {
+ // Second: type refinement(s), if no explicit deserializer was located
+ baseType = intr.refineDeserializationType(config,
+ delegate, baseType);
+ }
+ }
+ return baseType;
}
private <T extends AnnotatedMember> T _fixAccess(T member) {
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 4069f38..4b30ce3 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
@@ -18,16 +18,27 @@
*/
public class ExternalTypeHandler
{
+ private final JavaType _beanType;
+
private final ExtTypedProperty[] _properties;
- private final HashMap<String, Integer> _nameToPropertyIndex;
+
+ /**
+ * Mapping from external property ids to one or more indexes;
+ * in most cases single index as <code>Integer</code>, but
+ * occasionally same name maps to multiple ones: if so,
+ * <code>List<Integer></code>.
+ */
+ private final Map<String, Object> _nameToPropertyIndex;
private final String[] _typeIds;
private final TokenBuffer[] _tokens;
- protected ExternalTypeHandler(ExtTypedProperty[] properties,
- HashMap<String, Integer> nameToPropertyIndex,
+ protected ExternalTypeHandler(JavaType beanType,
+ ExtTypedProperty[] properties,
+ Map<String, Object> nameToPropertyIndex,
String[] typeIds, TokenBuffer[] tokens)
{
+ _beanType = beanType;
_properties = properties;
_nameToPropertyIndex = nameToPropertyIndex;
_typeIds = typeIds;
@@ -36,6 +47,7 @@
protected ExternalTypeHandler(ExternalTypeHandler h)
{
+ _beanType = h._beanType;
_properties = h._properties;
_nameToPropertyIndex = h._nameToPropertyIndex;
int len = _properties.length;
@@ -44,6 +56,13 @@
}
/**
+ * @since 2.9
+ */
+ public static Builder builder(JavaType beanType) {
+ return new Builder(beanType);
+ }
+
+ /**
* Method called to start collection process by creating non-blueprint
* instances.
*/
@@ -54,23 +73,43 @@
/**
* Method called to see if given property/value pair is an external type
* id; and if so handle it. This is <b>only</b> to be called in case
- * containing POJO has similarly named property as the external type id;
+ * containing POJO has similarly named property as the external type id AND
+ * value is of scalar type:
* otherwise {@link #handlePropertyValue} should be called instead.
*/
+ @SuppressWarnings("unchecked")
public boolean handleTypePropertyValue(JsonParser p, DeserializationContext ctxt,
String propName, Object bean)
throws IOException
{
- Integer I = _nameToPropertyIndex.get(propName);
- if (I == null) {
+ Object ob = _nameToPropertyIndex.get(propName);
+ if (ob == null) {
return false;
}
- int index = I.intValue();
+ final String typeId = p.getText();
+ // 28-Nov-2016, tatu: For [databind#291], need separate handling
+ if (ob instanceof List<?>) {
+ boolean result = false;
+ for (Integer index : (List<Integer>) ob) {
+ if (_handleTypePropertyValue(p, ctxt, propName, bean,
+ typeId, index.intValue())) {
+ result = true;
+ }
+ }
+ return result;
+ }
+ return _handleTypePropertyValue(p, ctxt, propName, bean,
+ typeId, ((Integer) ob).intValue());
+ }
+
+ private final boolean _handleTypePropertyValue(JsonParser p, DeserializationContext ctxt,
+ String propName, Object bean, String typeId, int index)
+ throws IOException
+ {
ExtTypedProperty prop = _properties[index];
- if (!prop.hasTypePropertyName(propName)) {
+ if (!prop.hasTypePropertyName(propName)) { // when could/should this ever happen?
return false;
}
- String typeId = p.getText();
// note: can NOT skip child values (should always be String anyway)
boolean canDeserialize = (bean != null) && (_tokens[index] != null);
// Minor optimization: deserialize properties as soon as we have all we need:
@@ -92,14 +131,44 @@
*
* @return True, if the given property was properly handled
*/
+ @SuppressWarnings("unchecked")
public boolean handlePropertyValue(JsonParser p, DeserializationContext ctxt,
String propName, Object bean) throws IOException
{
- Integer I = _nameToPropertyIndex.get(propName);
- if (I == null) {
+ Object ob = _nameToPropertyIndex.get(propName);
+ if (ob == null) {
return false;
}
- int index = I.intValue();
+ // 28-Nov-2016, tatu: For [databind#291], need separate handling
+ if (ob instanceof List<?>) {
+ Iterator<Integer> it = ((List<Integer>) ob).iterator();
+ Integer index = it.next();
+
+ ExtTypedProperty prop = _properties[index];
+ // For now, let's assume it's same type (either type id OR value)
+ // for all mappings, so we'll only check first one
+ if (prop.hasTypePropertyName(propName)) {
+ String typeId = p.getText();
+ p.skipChildren();
+ _typeIds[index] = typeId;
+ while (it.hasNext()) {
+ _typeIds[it.next()] = typeId;
+ }
+ } else {
+ @SuppressWarnings("resource")
+ TokenBuffer tokens = new TokenBuffer(p, ctxt);
+ tokens.copyCurrentStructure(p);
+ _tokens[index] = tokens;
+ while (it.hasNext()) {
+ _tokens[it.next()] = tokens;
+ }
+ }
+ return true;
+ }
+
+ // Otherwise only maps to a single value, in which case we can
+ // handle things in bit more optimal way...
+ int index = ((Integer) ob).intValue();
ExtTypedProperty prop = _properties[index];
boolean canDeserialize;
if (prop.hasTypePropertyName(propName)) {
@@ -113,9 +182,8 @@
_tokens[index] = tokens;
canDeserialize = (bean != null) && (_typeIds[index] != null);
}
- /* Minor optimization: let's deserialize properties as soon as
- * we have all pertinent information:
- */
+ // Minor optimization: let's deserialize properties as soon as
+ // we have all pertinent information:
if (canDeserialize) {
String typeId = _typeIds[index];
// clear stored data, to avoid deserializing+setting twice:
@@ -146,7 +214,7 @@
// [databind#118]: Need to mind natural types, for which no type id
// will be included.
JsonToken t = tokens.firstToken();
- if (t != null && t.isScalarValue()) {
+ if (t.isScalarValue()) { // can't be null as we never store empty buffers
JsonParser buffered = tokens.asParser(p);
buffered.nextToken();
SettableBeanProperty extProp = _properties[i].getProperty();
@@ -157,7 +225,8 @@
}
// 26-Oct-2012, tatu: As per [databind#94], must allow use of 'defaultImpl'
if (!_properties[i].hasDefaultType()) {
- ctxt.reportMappingException("Missing external type id property '%s'",
+ ctxt.reportInputMismatch(bean.getClass(),
+ "Missing external type id property '%s'",
_properties[i].getTypePropertyName());
} else {
typeId = _properties[i].getDefaultTypeId();
@@ -168,7 +237,8 @@
if(prop.isRequired() ||
ctxt.isEnabled(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY)) {
- ctxt.reportMappingException("Missing property '%s' for external type id '%s'",
+ ctxt.reportInputMismatch(bean.getClass(),
+ "Missing property '%s' for external type id '%s'",
prop.getName(), _properties[i].getTypePropertyName());
}
return bean;
@@ -192,7 +262,6 @@
for (int i = 0; i < len; ++i) {
String typeId = _typeIds[i];
final ExtTypedProperty extProp = _properties[i];
-
if (typeId == null) {
// let's allow missing both type and property (may already have been set, too)
if (_tokens[i] == null) {
@@ -201,17 +270,24 @@
// but not just one
// 26-Oct-2012, tatu: As per [databind#94], must allow use of 'defaultImpl'
if (!extProp.hasDefaultType()) {
- ctxt.reportMappingException("Missing external type id property '%s'",
+ ctxt.reportInputMismatch(_beanType,
+ "Missing external type id property '%s'",
extProp.getTypePropertyName());
} else {
typeId = extProp.getDefaultTypeId();
}
} else if (_tokens[i] == null) {
SettableBeanProperty prop = extProp.getProperty();
- ctxt.reportMappingException("Missing property '%s' for external type id '%s'",
- prop.getName(), _properties[i].getTypePropertyName());
+ if (prop.isRequired() ||
+ ctxt.isEnabled(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY)) {
+ ctxt.reportInputMismatch(_beanType,
+ "Missing property '%s' for external type id '%s'",
+ prop.getName(), _properties[i].getTypePropertyName());
+ }
}
- values[i] = _deserialize(p, ctxt, i, typeId);
+ if (_tokens[i] != null) {
+ values[i] = _deserialize(p, ctxt, i, typeId);
+ }
final SettableBeanProperty prop = extProp.getProperty();
// also: if it's creator prop, fill in
@@ -222,11 +298,21 @@
SettableBeanProperty typeProp = extProp.getTypeProperty();
// for now, should only be needed for creator properties, too
if ((typeProp != null) && (typeProp.getCreatorIndex() >= 0)) {
- buffer.assignParameter(typeProp, typeId);
+ // 31-May-2018, tatu: [databind#1328] if id is NOT plain `String`, need to
+ // apply deserializer... fun fun.
+ final Object v;
+ if (typeProp.getType().hasRawClass(String.class)) {
+ v = typeId;
+ } else {
+ TokenBuffer tb = new TokenBuffer(p, ctxt);
+ tb.writeString(typeId);
+ v = typeProp.getValueDeserializer().deserialize(tb.asParserOnFirstToken(), ctxt);
+ tb.close();
+ }
+ buffer.assignParameter(typeProp, v);
}
}
}
-
Object bean = creator.build(ctxt, buffer);
// third: assign non-creator properties
for (int i = 0; i < len; ++i) {
@@ -294,17 +380,39 @@
public static class Builder
{
- private final ArrayList<ExtTypedProperty> _properties = new ArrayList<ExtTypedProperty>();
- private final HashMap<String, Integer> _nameToPropertyIndex = new HashMap<String, Integer>();
+ private final JavaType _beanType;
+
+ private final List<ExtTypedProperty> _properties = new ArrayList<>();
+ private final Map<String, Object> _nameToPropertyIndex = new HashMap<>();
+
+ protected Builder(JavaType t) {
+ _beanType = t;
+ }
public void addExternal(SettableBeanProperty property, TypeDeserializer typeDeser)
{
Integer index = _properties.size();
_properties.add(new ExtTypedProperty(property, typeDeser));
- _nameToPropertyIndex.put(property.getName(), index);
- _nameToPropertyIndex.put(typeDeser.getPropertyName(), index);
+ _addPropertyIndex(property.getName(), index);
+ _addPropertyIndex(typeDeser.getPropertyName(), index);
}
+ private void _addPropertyIndex(String name, Integer index) {
+ Object ob = _nameToPropertyIndex.get(name);
+ if (ob == null) {
+ _nameToPropertyIndex.put(name, index);
+ } else if (ob instanceof List<?>) {
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>) ob;
+ list.add(index);
+ } else {
+ List<Object> list = new LinkedList<>();
+ list.add(ob);
+ list.add(index);
+ _nameToPropertyIndex.put(name, list);
+ }
+ }
+
/**
* Method called after all external properties have been assigned, to further
* link property with polymorphic value with possible property for type id
@@ -325,13 +433,8 @@
}
extProps[i] = extProp;
}
- return new ExternalTypeHandler(extProps, _nameToPropertyIndex, null, null);
- }
-
- @Deprecated // since 2.8; may be removed as early as 2.9
- public ExternalTypeHandler build() {
- return new ExternalTypeHandler(_properties.toArray(new ExtTypedProperty[_properties.size()]),
- _nameToPropertyIndex, null, null);
+ return new ExternalTypeHandler(_beanType, extProps, _nameToPropertyIndex,
+ null, null);
}
}
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 a777bde..9df6742 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
@@ -1,5 +1,7 @@
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.JsonMappingException;
@@ -23,8 +25,8 @@
}
@Override
- public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws JsonMappingException{
- ctxt.reportMappingException(_message);
+ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ ctxt.reportInputMismatch(this, _message);
return null;
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java
index 756dda2..973c85b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java
@@ -5,7 +5,10 @@
import java.lang.reflect.Field;
import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
@@ -32,24 +35,33 @@
*/
final protected transient Field _field;
+ /**
+ * @since 2.9
+ */
+ final protected boolean _skipNulls;
+
public FieldProperty(BeanPropertyDefinition propDef, JavaType type,
TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedField field)
{
super(propDef, type, typeDeser, contextAnnotations);
_annotated = field;
_field = field.getAnnotated();
+ _skipNulls = NullsConstantProvider.isSkipper(_nullProvider);
}
- protected FieldProperty(FieldProperty src, JsonDeserializer<?> deser) {
- super(src, deser);
+ protected FieldProperty(FieldProperty src, JsonDeserializer<?> deser,
+ NullValueProvider nva) {
+ super(src, deser, nva);
_annotated = src._annotated;
_field = src._field;
+ _skipNulls = NullsConstantProvider.isSkipper(nva);
}
protected FieldProperty(FieldProperty src, PropertyName newName) {
super(src, newName);
_annotated = src._annotated;
_field = src._field;
+ _skipNulls = src._skipNulls;
}
/**
@@ -64,19 +76,27 @@
throw new IllegalArgumentException("Missing field (broken JDK (de)serialization?)");
}
_field = f;
+ _skipNulls = src._skipNulls;
}
-
+
@Override
- public FieldProperty withName(PropertyName newName) {
+ public SettableBeanProperty withName(PropertyName newName) {
return new FieldProperty(this, newName);
}
-
+
@Override
- public FieldProperty withValueDeserializer(JsonDeserializer<?> deser) {
+ public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
if (_valueDeserializer == deser) {
return this;
}
- return new FieldProperty(this, deser);
+ // 07-May-2019, tatu: As per [databind#2303], must keep VD/NVP in-sync if they were
+ NullValueProvider nvp = (_valueDeserializer == _nullProvider) ? deser : _nullProvider;
+ return new FieldProperty(this, deser, nvp);
+ }
+
+ @Override
+ public SettableBeanProperty withNullProvider(NullValueProvider nva) {
+ return new FieldProperty(this, _valueDeserializer, nva);
}
@Override
@@ -108,7 +128,24 @@
public void deserializeAndSet(JsonParser p,
DeserializationContext ctxt, Object instance) throws IOException
{
- Object value = deserialize(p, ctxt);
+ Object value;
+ if (p.hasToken(JsonToken.VALUE_NULL)) {
+ if (_skipNulls) {
+ return;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ } else if (_valueTypeDeserializer == null) {
+ value = _valueDeserializer.deserialize(p, ctxt);
+ // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
+ if (value == null) {
+ if (_skipNulls) {
+ return;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ }
+ } else {
+ value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ }
try {
_field.set(instance, value);
} catch (Exception e) {
@@ -120,7 +157,24 @@
public Object deserializeSetAndReturn(JsonParser p,
DeserializationContext ctxt, Object instance) throws IOException
{
- Object value = deserialize(p, ctxt);
+ Object value;
+ if (p.hasToken(JsonToken.VALUE_NULL)) {
+ if (_skipNulls) {
+ return instance;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ } else if (_valueTypeDeserializer == null) {
+ value = _valueDeserializer.deserialize(p, ctxt);
+ // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
+ if (value == null) {
+ if (_skipNulls) {
+ return instance;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ }
+ } else {
+ value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ }
try {
_field.set(instance, value);
} catch (Exception e) {
@@ -128,9 +182,9 @@
}
return instance;
}
-
+
@Override
- public final void set(Object instance, Object value) throws IOException
+ public void set(Object instance, Object value) throws IOException
{
try {
_field.set(instance, value);
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/InnerClassProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/InnerClassProperty.java
index cac8057..bdc72dd 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/InnerClassProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/InnerClassProperty.java
@@ -1,10 +1,10 @@
package com.fasterxml.jackson.databind.deser.impl;
import java.io.IOException;
-import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.*;
@@ -17,16 +17,11 @@
* to regular implementation.
*/
public final class InnerClassProperty
- extends SettableBeanProperty
+ extends SettableBeanProperty.Delegating
{
private static final long serialVersionUID = 1L;
/**
- * Actual property that we use after value construction.
- */
- protected final SettableBeanProperty _delegate;
-
- /**
* Constructor used when deserializing this property.
* Transient since there is no need to persist; only needed during
* construction of objects.
@@ -42,7 +37,6 @@
Constructor<?> ctor)
{
super(delegate);
- _delegate = delegate;
_creator = ctor;
}
@@ -50,66 +44,24 @@
* Constructor used with JDK Serialization; needed to handle transient
* Constructor, wrap/unwrap in/out-of Annotated variant.
*/
- protected InnerClassProperty(InnerClassProperty src, AnnotatedConstructor ann)
+ protected InnerClassProperty(SettableBeanProperty src, AnnotatedConstructor ann)
{
super(src);
- _delegate = src._delegate;
_annotated = ann;
_creator = (_annotated == null) ? null : _annotated.getAnnotated();
if (_creator == null) {
throw new IllegalArgumentException("Missing constructor (broken JDK (de)serialization?)");
}
}
-
- protected InnerClassProperty(InnerClassProperty src, JsonDeserializer<?> deser)
- {
- super(src, deser);
- _delegate = src._delegate.withValueDeserializer(deser);
- _creator = src._creator;
- }
-
- protected InnerClassProperty(InnerClassProperty src, PropertyName newName) {
- super(src, newName);
- _delegate = src._delegate.withName(newName);
- _creator = src._creator;
- }
@Override
- public InnerClassProperty withName(PropertyName newName) {
- return new InnerClassProperty(this, newName);
- }
-
- @Override
- public InnerClassProperty withValueDeserializer(JsonDeserializer<?> deser) {
- if (_valueDeserializer == deser) {
+ protected SettableBeanProperty withDelegate(SettableBeanProperty d) {
+ if (d == this.delegate) {
return this;
}
- return new InnerClassProperty(this, deser);
+ return new InnerClassProperty(d, _creator);
}
- @Override
- public void assignIndex(int index) { _delegate.assignIndex(index); }
-
- @Override
- public int getPropertyIndex() { return _delegate.getPropertyIndex(); }
-
- @Override
- public int getCreatorIndex() { return _delegate.getCreatorIndex(); }
-
- @Override
- public void fixAccess(DeserializationConfig config) {
- _delegate.fixAccess(config);
- }
-
- // // // BeanProperty impl
-
- @Override
- public <A extends Annotation> A getAnnotation(Class<A> acls) {
- return _delegate.getAnnotation(acls);
- }
-
- @Override public AnnotatedMember getMember() { return _delegate.getMember(); }
-
/*
/**********************************************************
/* Deserialization methods
@@ -117,44 +69,39 @@
*/
@Override
- public void deserializeAndSet(JsonParser jp, DeserializationContext ctxt, Object bean)
+ public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object bean)
throws IOException
{
- JsonToken t = jp.getCurrentToken();
+ JsonToken t = p.getCurrentToken();
Object value;
if (t == JsonToken.VALUE_NULL) {
value = _valueDeserializer.getNullValue(ctxt);
} else if (_valueTypeDeserializer != null) {
- value = _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
+ value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
} else { // the usual case
try {
value = _creator.newInstance(bean);
} catch (Exception e) {
- ClassUtil.unwrapAndThrowAsIAE(e, "Failed to instantiate class "+_creator.getDeclaringClass().getName()+", problem: "+e.getMessage());
+ ClassUtil.unwrapAndThrowAsIAE(e, String.format(
+"Failed to instantiate class %s, problem: %s",
+_creator.getDeclaringClass().getName(), e.getMessage()));
value = null;
}
- _valueDeserializer.deserialize(jp, ctxt, value);
+ _valueDeserializer.deserialize(p, ctxt, value);
}
set(bean, value);
}
@Override
- public Object deserializeSetAndReturn(JsonParser jp,
- DeserializationContext ctxt, Object instance)
+ public Object deserializeSetAndReturn(JsonParser p, DeserializationContext ctxt, Object instance)
throws IOException
{
- return setAndReturn(instance, deserialize(jp, ctxt));
- }
-
- @Override
- public final void set(Object instance, Object value) throws IOException {
- _delegate.set(instance, value);
+ return setAndReturn(instance, deserialize(p, ctxt));
}
- @Override
- public Object setAndReturn(Object instance, Object value) throws IOException {
- return _delegate.setAndReturn(instance, value);
- }
+// these are fine with defaults
+// public final void set(Object instance, Object value) throws IOException { }
+// public Object setAndReturn(Object instance, Object value) throws IOException { }
/*
/**********************************************************
@@ -169,9 +116,9 @@
Object writeReplace() {
// need to construct a fake instance to support serialization
- if (_annotated != null) {
- return this;
+ if (_annotated == null) {
+ return new InnerClassProperty(this, new AnnotatedConstructor(null, _creator, null, null));
}
- return new InnerClassProperty(this, new AnnotatedConstructor(null, _creator, null, null));
+ return this;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/JavaUtilCollectionsDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/JavaUtilCollectionsDeserializers.java
new file mode 100644
index 0000000..f16d95e
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/JavaUtilCollectionsDeserializers.java
@@ -0,0 +1,189 @@
+package com.fasterxml.jackson.databind.deser.impl;
+
+import java.util.*;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.databind.util.Converter;
+
+/**
+ * Helper class used to contain logic for deserializing "special" containers
+ * from {@code java.util.Collections} and {@code java.util.Arrays}. This is needed
+ * because they do not have usable no-arguments constructor: however, are easy enough
+ * to deserialize using delegating deserializer.
+ *
+ * @since 2.9.4
+ */
+public abstract class JavaUtilCollectionsDeserializers
+{
+ private final static int TYPE_SINGLETON_SET = 1;
+ private final static int TYPE_SINGLETON_LIST = 2;
+ private final static int TYPE_SINGLETON_MAP = 3;
+
+ private final static int TYPE_UNMODIFIABLE_SET = 4;
+ private final static int TYPE_UNMODIFIABLE_LIST = 5;
+ private final static int TYPE_UNMODIFIABLE_MAP = 6;
+
+ public final static int TYPE_AS_LIST = 7;
+
+ // 10-Jan-2018, tatu: There are a few "well-known" special containers in JDK too:
+
+ private final static Class<?> CLASS_AS_ARRAYS_LIST = Arrays.asList(null, null).getClass();
+
+ private final static Class<?> CLASS_SINGLETON_SET;
+ private final static Class<?> CLASS_SINGLETON_LIST;
+ private final static Class<?> CLASS_SINGLETON_MAP;
+
+ private final static Class<?> CLASS_UNMODIFIABLE_SET;
+ private final static Class<?> CLASS_UNMODIFIABLE_LIST;
+
+ /* 02-Mar-2019, tatu: for [databind#2265], need to consider possible alternate type...
+ * which we essentially coerce into the other one
+ */
+ private final static Class<?> CLASS_UNMODIFIABLE_LIST_ALIAS;
+ private final static Class<?> CLASS_UNMODIFIABLE_MAP;
+
+ static {
+ Set<?> set = Collections.singleton(Boolean.TRUE);
+ CLASS_SINGLETON_SET = set.getClass();
+ CLASS_UNMODIFIABLE_SET = Collections.unmodifiableSet(set).getClass();
+
+ List<?> list = Collections.singletonList(Boolean.TRUE);
+ CLASS_SINGLETON_LIST = list.getClass();
+ CLASS_UNMODIFIABLE_LIST = Collections.unmodifiableList(list).getClass();
+ // for [databind#2265]
+ CLASS_UNMODIFIABLE_LIST_ALIAS = Collections.unmodifiableList(new LinkedList<Object>()).getClass();
+
+ Map<?,?> map = Collections.singletonMap("a", "b");
+ CLASS_SINGLETON_MAP = map.getClass();
+ CLASS_UNMODIFIABLE_MAP = Collections.unmodifiableMap(map).getClass();
+ }
+
+ public static JsonDeserializer<?> findForCollection(DeserializationContext ctxt,
+ JavaType type)
+ throws JsonMappingException
+ {
+ JavaUtilCollectionsConverter conv;
+
+ // 10-Jan-2017, tatu: Some types from `java.util.Collections`/`java.util.Arrays` need bit of help...
+ if (type.hasRawClass(CLASS_AS_ARRAYS_LIST)) {
+ conv = converter(TYPE_AS_LIST, type, List.class);
+ } else if (type.hasRawClass(CLASS_SINGLETON_LIST)) {
+ conv = converter(TYPE_SINGLETON_LIST, type, List.class);
+ } else if (type.hasRawClass(CLASS_SINGLETON_SET)) {
+ conv = converter(TYPE_SINGLETON_SET, type, Set.class);
+ // [databind#2265]: we may have another impl type for unmodifiable Lists, check both
+ } else if (type.hasRawClass(CLASS_UNMODIFIABLE_LIST) || type.hasRawClass(CLASS_UNMODIFIABLE_LIST_ALIAS)) {
+ conv = converter(TYPE_UNMODIFIABLE_LIST, type, List.class);
+ } else if (type.hasRawClass(CLASS_UNMODIFIABLE_SET)) {
+ conv = converter(TYPE_UNMODIFIABLE_SET, type, Set.class);
+ } else {
+ return null;
+ }
+ return new StdDelegatingDeserializer<Object>(conv);
+ }
+
+ public static JsonDeserializer<?> findForMap(DeserializationContext ctxt,
+ JavaType type)
+ throws JsonMappingException
+ {
+ JavaUtilCollectionsConverter conv;
+
+ // 10-Jan-2017, tatu: Some types from `java.util.Collections`/`java.util.Arrays` need bit of help...
+ if (type.hasRawClass(CLASS_SINGLETON_MAP)) {
+ conv = converter(TYPE_SINGLETON_MAP, type, Map.class);
+ } else if (type.hasRawClass(CLASS_UNMODIFIABLE_MAP)) {
+ conv = converter(TYPE_UNMODIFIABLE_MAP, type, Map.class);
+ } else {
+ return null;
+ }
+ return new StdDelegatingDeserializer<Object>(conv);
+ }
+
+ static JavaUtilCollectionsConverter converter(int kind,
+ JavaType concreteType, Class<?> rawSuper)
+ {
+ return new JavaUtilCollectionsConverter(kind, concreteType.findSuperType(rawSuper));
+ }
+
+ /**
+ * Implementation used for converting from various generic container
+ * types ({@link java.util.Set}, {@link java.util.List}, {@link java.util.Map})
+ * into more specific implementations accessible via {@code java.util.Collections}.
+ */
+ private static class JavaUtilCollectionsConverter implements Converter<Object,Object>
+ {
+ private final JavaType _inputType;
+
+ private final int _kind;
+
+ private JavaUtilCollectionsConverter(int kind, JavaType inputType) {
+ _inputType = inputType;
+ _kind = kind;
+ }
+
+ @Override
+ public Object convert(Object value) {
+ if (value == null) { // is this legal to get?
+ return null;
+ }
+
+ switch (_kind) {
+ case TYPE_SINGLETON_SET:
+ {
+ Set<?> set = (Set<?>) value;
+ _checkSingleton(set.size());
+ return Collections.singleton(set.iterator().next());
+ }
+ case TYPE_SINGLETON_LIST:
+ {
+ List<?> list = (List<?>) value;
+ _checkSingleton(list.size());
+ return Collections.singletonList(list.get(0));
+ }
+ case TYPE_SINGLETON_MAP:
+ {
+ Map<?,?> map = (Map<?,?>) value;
+ _checkSingleton(map.size());
+ Map.Entry<?,?> entry = map.entrySet().iterator().next();
+ return Collections.singletonMap(entry.getKey(), entry.getValue());
+ }
+
+ case TYPE_UNMODIFIABLE_SET:
+ return Collections.unmodifiableSet((Set<?>) value);
+ case TYPE_UNMODIFIABLE_LIST:
+ return Collections.unmodifiableList((List<?>) value);
+ case TYPE_UNMODIFIABLE_MAP:
+ return Collections.unmodifiableMap((Map<?,?>) value);
+
+ case TYPE_AS_LIST:
+ default:
+ // Here we do not actually care about impl type, just return List as-is:
+ return value;
+ }
+ }
+
+ @Override
+ public JavaType getInputType(TypeFactory typeFactory) {
+ return _inputType;
+ }
+
+ @Override
+ public JavaType getOutputType(TypeFactory typeFactory) {
+ // we don't actually care, so:
+ return _inputType;
+ }
+
+ private void _checkSingleton(int size) {
+ if (size != 1) {
+ // not the best error ever but... has to do
+ throw new IllegalArgumentException("Can not deserialize Singleton container from "+size+" entries");
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ManagedReferenceProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ManagedReferenceProperty.java
index 21b465a..f0cbfda 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ManagedReferenceProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ManagedReferenceProperty.java
@@ -1,15 +1,12 @@
package com.fasterxml.jackson.databind.deser.impl;
import java.io.IOException;
-import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
-import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
-import com.fasterxml.jackson.databind.util.Annotations;
/**
* Wrapper property that is used to handle managed (forward) properties
@@ -17,7 +14,8 @@
* then to back property.
*/
public final class ManagedReferenceProperty
- extends SettableBeanProperty
+ // Changed to extends delegating base class in 2.9
+ extends SettableBeanProperty.Delegating
{
private static final long serialVersionUID = 1L;
@@ -29,73 +27,31 @@
*/
protected final boolean _isContainer;
- protected final SettableBeanProperty _managedProperty;
-
protected final SettableBeanProperty _backProperty;
public ManagedReferenceProperty(SettableBeanProperty forward, String refName,
- SettableBeanProperty backward, Annotations contextAnnotations, boolean isContainer)
+ SettableBeanProperty backward, boolean isContainer)
{
- super(forward.getFullName(), forward.getType(), forward.getWrapperName(),
- forward.getValueTypeDeserializer(), contextAnnotations,
- forward.getMetadata());
+ super(forward);
_referenceName = refName;
- _managedProperty = forward;
_backProperty = backward;
_isContainer = isContainer;
}
- protected ManagedReferenceProperty(ManagedReferenceProperty src, JsonDeserializer<?> deser)
- {
- super(src, deser);
- _referenceName = src._referenceName;
- _isContainer = src._isContainer;
- _managedProperty = src._managedProperty;
- _backProperty = src._backProperty;
- }
-
- protected ManagedReferenceProperty(ManagedReferenceProperty src, PropertyName newName) {
- super(src, newName);
- _referenceName = src._referenceName;
- _isContainer = src._isContainer;
- _managedProperty = src._managedProperty;
- _backProperty = src._backProperty;
- }
-
@Override
- public ManagedReferenceProperty withName(PropertyName newName) {
- return new ManagedReferenceProperty(this, newName);
+ protected SettableBeanProperty withDelegate(SettableBeanProperty d) {
+ throw new IllegalStateException("Should never try to reset delegate");
}
- @Override
- public ManagedReferenceProperty withValueDeserializer(JsonDeserializer<?> deser) {
- if (_valueDeserializer == deser) {
- return this;
- }
- return new ManagedReferenceProperty(this, deser);
- }
-
+ // need to override to ensure both get fixed
@Override
public void fixAccess(DeserializationConfig config) {
- _managedProperty.fixAccess(config);
+ delegate.fixAccess(config);
_backProperty.fixAccess(config);
}
/*
/**********************************************************
- /* BeanProperty impl
- /**********************************************************
- */
-
- @Override
- public <A extends Annotation> A getAnnotation(Class<A> acls) {
- return _managedProperty.getAnnotation(acls);
- }
-
- @Override public AnnotatedMember getMember() { return _managedProperty.getMember(); }
-
- /*
- /**********************************************************
/* Overridden methods
/**********************************************************
*/
@@ -103,7 +59,7 @@
@Override
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance)
throws IOException {
- set(instance, _managedProperty.deserialize(p, ctxt));
+ set(instance, delegate.deserialize(p, ctxt));
}
@Override
@@ -146,6 +102,6 @@
}
}
// and then the forward reference itself
- return _managedProperty.setAndReturn(instance, value);
+ return delegate.setAndReturn(instance, value);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/MergingSettableBeanProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MergingSettableBeanProperty.java
new file mode 100644
index 0000000..db9fda7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MergingSettableBeanProperty.java
@@ -0,0 +1,133 @@
+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.deser.*;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+
+/**
+ * {@link SettableBeanProperty} implementation that will try to access value of
+ * the property first, and if non-null value found, pass that for update
+ * (using {@link com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext, Object)})
+ * instead of constructing a new value. This is necessary to support "merging" properties.
+ *<p>
+ * Note that there are many similarities to {@link SetterlessProperty}, which predates
+ * this variant; and that one is even used in cases where there is no mutator
+ * available.
+ *
+ * @since 2.9
+ */
+public class MergingSettableBeanProperty
+ extends SettableBeanProperty.Delegating
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Member (field, method) used for accessing existing value.
+ */
+ protected final AnnotatedMember _accessor;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ protected MergingSettableBeanProperty(SettableBeanProperty delegate,
+ AnnotatedMember accessor)
+ {
+ super(delegate);
+ _accessor = accessor;
+ }
+
+ protected MergingSettableBeanProperty(MergingSettableBeanProperty src,
+ SettableBeanProperty delegate)
+ {
+ super(delegate);
+ _accessor = src._accessor;
+ }
+
+ public static MergingSettableBeanProperty construct(SettableBeanProperty delegate,
+ AnnotatedMember accessor)
+ {
+ return new MergingSettableBeanProperty(delegate, accessor);
+ }
+
+ @Override
+ protected SettableBeanProperty withDelegate(SettableBeanProperty d) {
+ return new MergingSettableBeanProperty(d, _accessor);
+ }
+
+ /*
+ /**********************************************************
+ /* Deserialization methods
+ /**********************************************************
+ */
+
+ @Override
+ public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
+ Object instance) throws IOException
+ {
+ Object oldValue = _accessor.getValue(instance);
+ Object newValue;
+ // 20-Oct-2016, tatu: Couple of possibilities of how to proceed; for
+ // now, default to "normal" handling without merging
+ if (oldValue == null) {
+ newValue = delegate.deserialize(p, ctxt);
+ } else {
+ newValue = delegate.deserializeWith(p, ctxt, oldValue);
+ }
+ if (newValue != oldValue) {
+ // 18-Apr-2017, tatu: Null handling should occur within delegate, which may
+ // set/skip/transform it, or throw an exception.
+ delegate.set(instance, newValue);
+ }
+ }
+
+ @Override
+ public Object deserializeSetAndReturn(JsonParser p,
+ DeserializationContext ctxt, Object instance) throws IOException
+ {
+ Object oldValue = _accessor.getValue(instance);
+ Object newValue;
+ // 20-Oct-2016, tatu: Couple of possibilities of how to proceed; for
+ // now, default to "normal" handling without merging
+ if (oldValue == null) {
+ newValue = delegate.deserialize(p, ctxt);
+ } else {
+ newValue = delegate.deserializeWith(p, ctxt, oldValue);
+ }
+ // 23-Oct-2016, tatu: One possible complication here; should we always
+ // try calling setter on builder? Presumably should not be required,
+ // but may need to revise
+ if (newValue != oldValue) {
+ // 31-Oct-2016, tatu: Basically should just ignore as null can't really
+ // contribute to merging.
+ if (newValue != null) {
+ return delegate.setAndReturn(instance, newValue);
+ }
+ }
+ return instance;
+ }
+
+ @Override
+ public void set(Object instance, Object value) throws IOException {
+ // 31-Oct-2016, tatu: Basically should just ignore as null can't really
+ // contribute to merging.
+ if (value != null) {
+ delegate.set(instance, value);
+ }
+ }
+
+ @Override
+ public Object setAndReturn(Object instance, Object value) throws IOException {
+ // 31-Oct-2016, tatu: Basically should just ignore as null can't really
+ // contribute to merging.
+ if (value != null) {
+ return delegate.setAndReturn(instance, value);
+ }
+ return instance;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java
index bf4e0fe..08111d4 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java
@@ -5,8 +5,9 @@
import java.lang.reflect.Method;
import com.fasterxml.jackson.core.JsonParser;
-
+import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
@@ -28,6 +29,11 @@
* "regular" method-accessible properties.
*/
protected final transient Method _setter;
+
+ /**
+ * @since 2.9
+ */
+ final protected boolean _skipNulls;
public MethodProperty(BeanPropertyDefinition propDef,
JavaType type, TypeDeserializer typeDeser,
@@ -36,18 +42,22 @@
super(propDef, type, typeDeser, contextAnnotations);
_annotated = method;
_setter = method.getAnnotated();
+ _skipNulls = NullsConstantProvider.isSkipper(_nullProvider);
}
- protected MethodProperty(MethodProperty src, JsonDeserializer<?> deser) {
- super(src, deser);
+ protected MethodProperty(MethodProperty src, JsonDeserializer<?> deser,
+ NullValueProvider nva) {
+ super(src, deser, nva);
_annotated = src._annotated;
_setter = src._setter;
+ _skipNulls = NullsConstantProvider.isSkipper(nva);
}
protected MethodProperty(MethodProperty src, PropertyName newName) {
super(src, newName);
_annotated = src._annotated;
_setter = src._setter;
+ _skipNulls = src._skipNulls;
}
/**
@@ -57,19 +67,27 @@
super(src);
_annotated = src._annotated;
_setter = m;
+ _skipNulls = src._skipNulls;
}
-
+
@Override
- public MethodProperty withName(PropertyName newName) {
+ public SettableBeanProperty withName(PropertyName newName) {
return new MethodProperty(this, newName);
}
@Override
- public MethodProperty withValueDeserializer(JsonDeserializer<?> deser) {
+ public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
if (_valueDeserializer == deser) {
return this;
}
- return new MethodProperty(this, deser);
+ // 07-May-2019, tatu: As per [databind#2303], must keep VD/NVP in-sync if they were
+ NullValueProvider nvp = (_valueDeserializer == _nullProvider) ? deser : _nullProvider;
+ return new MethodProperty(this, deser, nvp);
+ }
+
+ @Override
+ public SettableBeanProperty withNullProvider(NullValueProvider nva) {
+ return new MethodProperty(this, _valueDeserializer, nva);
}
@Override
@@ -101,7 +119,24 @@
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object instance) throws IOException
{
- Object value = deserialize(p, ctxt);
+ Object value;
+ if (p.hasToken(JsonToken.VALUE_NULL)) {
+ if (_skipNulls) {
+ return;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ } else if (_valueTypeDeserializer == null) {
+ value = _valueDeserializer.deserialize(p, ctxt);
+ // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
+ if (value == null) {
+ if (_skipNulls) {
+ return;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ }
+ } else {
+ value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ }
try {
_setter.invoke(instance, value);
} catch (Exception e) {
@@ -113,7 +148,24 @@
public Object deserializeSetAndReturn(JsonParser p,
DeserializationContext ctxt, Object instance) throws IOException
{
- Object value = deserialize(p, ctxt);
+ Object value;
+ if (p.hasToken(JsonToken.VALUE_NULL)) {
+ if (_skipNulls) {
+ return instance;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ } else if (_valueTypeDeserializer == null) {
+ value = _valueDeserializer.deserialize(p, ctxt);
+ // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
+ if (value == null) {
+ if (_skipNulls) {
+ return instance;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ }
+ } else {
+ value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ }
try {
Object result = _setter.invoke(instance, value);
return (result == null) ? instance : result;
@@ -129,7 +181,7 @@
try {
_setter.invoke(instance, value);
} catch (Exception e) {
- // 15-Sep-2015, tatu: How coud we get a ref to JsonParser?
+ // 15-Sep-2015, tatu: How could we get a ref to JsonParser?
_throwAsIOE(e, value);
}
}
@@ -141,7 +193,7 @@
Object result = _setter.invoke(instance, value);
return (result == null) ? instance : result;
} catch (Exception e) {
- // 15-Sep-2015, tatu: How coud we get a ref to JsonParser?
+ // 15-Sep-2015, tatu: How could we get a ref to JsonParser?
_throwAsIOE(e, value);
return null;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsAsEmptyProvider.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsAsEmptyProvider.java
new file mode 100644
index 0000000..51e385f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsAsEmptyProvider.java
@@ -0,0 +1,33 @@
+package com.fasterxml.jackson.databind.deser.impl;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
+import com.fasterxml.jackson.databind.exc.InvalidNullException;
+import com.fasterxml.jackson.databind.util.AccessPattern;
+
+/**
+ * Simple {@link NullValueProvider} that will always throw a
+ * {@link InvalidNullException} when a null is encountered.
+ */
+public class NullsAsEmptyProvider
+ implements NullValueProvider, java.io.Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final JsonDeserializer<?> _deserializer;
+
+ public NullsAsEmptyProvider(JsonDeserializer<?> deser) {
+ _deserializer = deser;
+ }
+
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ return AccessPattern.DYNAMIC;
+ }
+
+ @Override
+ public Object getNullValue(DeserializationContext ctxt)
+ throws JsonMappingException {
+ return _deserializer.getEmptyValue(ctxt);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsConstantProvider.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsConstantProvider.java
new file mode 100644
index 0000000..5995a32
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsConstantProvider.java
@@ -0,0 +1,79 @@
+package com.fasterxml.jackson.databind.deser.impl;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
+import com.fasterxml.jackson.databind.exc.InvalidNullException;
+import com.fasterxml.jackson.databind.util.AccessPattern;
+
+/**
+ * Simple {@link NullValueProvider} that will always throw a
+ * {@link InvalidNullException} when a null is encountered.
+ */
+public class NullsConstantProvider
+ implements NullValueProvider, java.io.Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ private final static NullsConstantProvider SKIPPER = new NullsConstantProvider(null);
+
+ private final static NullsConstantProvider NULLER = new NullsConstantProvider(null);
+
+ protected final Object _nullValue;
+
+ protected final AccessPattern _access;
+
+ protected NullsConstantProvider(Object nvl) {
+ _nullValue = nvl;
+ _access = (_nullValue == null) ? AccessPattern.ALWAYS_NULL
+ : AccessPattern.CONSTANT;
+ }
+
+ /**
+ * Static accessor for a stateless instance used as marker, to indicate
+ * that all input `null` values should be skipped (ignored), so that
+ * no corresponding property value is set (with POJOs), and no content
+ * values (array/Collection elements, Map entries) are added.
+ */
+ public static NullsConstantProvider skipper() {
+ return SKIPPER;
+ }
+
+ public static NullsConstantProvider nuller() {
+ return NULLER;
+ }
+
+ public static NullsConstantProvider forValue(Object nvl) {
+ if (nvl == null) {
+ return NULLER;
+ }
+ return new NullsConstantProvider(nvl);
+ }
+
+ /**
+ * Utility method that can be used to check if given null value provider
+ * is "skipper", marker provider that means that all input `null`s should
+ * be skipped (ignored), instead of converted
+ */
+ public static boolean isSkipper(NullValueProvider p) {
+ return (p == SKIPPER);
+ }
+
+ /**
+ * Utility method that can be used to check if given null value provider
+ * is "nuller", no-operation provider that will always simply return
+ * Java `null` for any and all input `null`s.
+ */
+ public static boolean isNuller(NullValueProvider p) {
+ return (p == NULLER);
+ }
+
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ return _access;
+ }
+
+ @Override
+ public Object getNullValue(DeserializationContext ctxt) {
+ return _nullValue;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsFailProvider.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsFailProvider.java
new file mode 100644
index 0000000..ba870c7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsFailProvider.java
@@ -0,0 +1,44 @@
+package com.fasterxml.jackson.databind.deser.impl;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
+import com.fasterxml.jackson.databind.exc.InvalidNullException;
+import com.fasterxml.jackson.databind.util.AccessPattern;
+
+/**
+ * Simple {@link NullValueProvider} that will always throw a
+ * {@link InvalidNullException} when a null is encountered.
+ */
+public class NullsFailProvider
+ implements NullValueProvider, java.io.Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final PropertyName _name;
+ protected final JavaType _type;
+
+ protected NullsFailProvider(PropertyName name, JavaType type) {
+ _name = name;
+ _type = type;
+ }
+
+ public static NullsFailProvider constructForProperty(BeanProperty prop) {
+ return new NullsFailProvider(prop.getFullName(), prop.getType());
+ }
+
+ public static NullsFailProvider constructForRootValue(JavaType t) {
+ return new NullsFailProvider(null, t);
+ }
+
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ // Must be called every time to effect the exception...
+ return AccessPattern.DYNAMIC;
+ }
+
+ @Override
+ public Object getNullValue(DeserializationContext ctxt)
+ throws JsonMappingException {
+ throw InvalidNullException.from(ctxt, _name, _type);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReader.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReader.java
index 6e90e35..99b6e75 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReader.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReader.java
@@ -4,8 +4,9 @@
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdResolver;
-import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
+
import com.fasterxml.jackson.core.JsonParser;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
@@ -55,13 +56,6 @@
idProperty = idProp;
}
- @Deprecated // since 2.4
- protected ObjectIdReader(JavaType t, PropertyName propName, ObjectIdGenerator<?> gen,
- JsonDeserializer<?> deser, SettableBeanProperty idProp)
- {
- this(t,propName, gen, deser, idProp, new SimpleObjectIdResolver());
- }
-
/**
* Factory method called by {@link com.fasterxml.jackson.databind.ser.std.BeanSerializerBase}
* with the initial information based on standard settings for the type
@@ -74,14 +68,6 @@
return new ObjectIdReader(idType, propName, generator, deser, idProp, resolver);
}
- @Deprecated // since 2.4
- public static ObjectIdReader construct(JavaType idType, PropertyName propName,
- ObjectIdGenerator<?> generator, JsonDeserializer<?> deser,
- SettableBeanProperty idProp)
- {
- return construct(idType, propName, generator, deser, idProp, new SimpleObjectIdResolver());
- }
-
/*
/**********************************************************
/* API
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReferenceProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReferenceProperty.java
index 2034d91..449cc79 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReferenceProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdReferenceProperty.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
@@ -24,9 +25,10 @@
_objectIdInfo = objectIdInfo;
}
- public ObjectIdReferenceProperty(ObjectIdReferenceProperty src, JsonDeserializer<?> deser)
+ public ObjectIdReferenceProperty(ObjectIdReferenceProperty src, JsonDeserializer<?> deser,
+ NullValueProvider nva)
{
- super(src, deser);
+ super(src, deser, nva);
_forward = src._forward;
_objectIdInfo = src._objectIdInfo;
}
@@ -39,19 +41,26 @@
}
@Override
- public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
- if (_valueDeserializer == deser) {
- return this;
- }
- return new ObjectIdReferenceProperty(this, deser);
- }
-
- @Override
public SettableBeanProperty withName(PropertyName newName) {
return new ObjectIdReferenceProperty(this, newName);
}
@Override
+ public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
+ if (_valueDeserializer == deser) {
+ return this;
+ }
+ // 07-May-2019, tatu: As per [databind#2303], must keep VD/NVP in-sync if they were
+ NullValueProvider nvp = (_valueDeserializer == _nullProvider) ? deser : _nullProvider;
+ return new ObjectIdReferenceProperty(this, deser, nvp);
+ }
+
+ @Override
+ public SettableBeanProperty withNullProvider(NullValueProvider nva) {
+ return new ObjectIdReferenceProperty(this, _valueDeserializer, nva);
+ }
+
+ @Override
public void fixAccess(DeserializationConfig config) {
if (_forward != null) {
_forward.fixAccess(config);
@@ -86,7 +95,7 @@
} catch (UnresolvedForwardReference reference) {
boolean usingIdentityInfo = (_objectIdInfo != null) || (_valueDeserializer.getObjectIdReader() != null);
if (!usingIdentityInfo) {
- throw JsonMappingException.from(p, "Unresolved forward reference but no identity info.", reference);
+ throw JsonMappingException.from(p, "Unresolved forward reference but no identity info", reference);
}
reference.getRoid().appendReferring(new PropertyReferring(this, reference, _type.getRawClass(), instance));
return null;
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdValueProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdValueProperty.java
index 4787fd0..7fe91fc 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdValueProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ObjectIdValueProperty.java
@@ -29,9 +29,10 @@
_objectIdReader = objectIdReader;
}
- protected ObjectIdValueProperty(ObjectIdValueProperty src, JsonDeserializer<?> deser)
+ protected ObjectIdValueProperty(ObjectIdValueProperty src, JsonDeserializer<?> deser,
+ NullValueProvider nva)
{
- super(src, deser);
+ super(src, deser, nva);
_objectIdReader = src._objectIdReader;
}
@@ -41,18 +42,25 @@
}
@Override
- public ObjectIdValueProperty withName(PropertyName newName) {
+ public SettableBeanProperty withName(PropertyName newName) {
return new ObjectIdValueProperty(this, newName);
}
@Override
- public ObjectIdValueProperty withValueDeserializer(JsonDeserializer<?> deser) {
+ public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
if (_valueDeserializer == deser) {
return this;
}
- return new ObjectIdValueProperty(this, deser);
+ // 07-May-2019, tatu: As per [databind#2303], must keep VD/NVP in-sync if they were
+ NullValueProvider nvp = (_valueDeserializer == _nullProvider) ? deser : _nullProvider;
+ return new ObjectIdValueProperty(this, deser, nvp);
}
-
+
+ @Override
+ public SettableBeanProperty withNullProvider(NullValueProvider nva) {
+ return new ObjectIdValueProperty(this, _valueDeserializer, nva);
+ }
+
// // // BeanProperty impl
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java
index f0c4093..566b54f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java
@@ -1,14 +1,11 @@
package com.fasterxml.jackson.databind.deser.impl;
import java.io.IOException;
-import java.util.Collection;
-import java.util.HashMap;
+import java.util.*;
import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
@@ -53,9 +50,11 @@
/**********************************************************
*/
- protected PropertyBasedCreator(ValueInstantiator valueInstantiator,
+ protected PropertyBasedCreator(DeserializationContext ctxt,
+ ValueInstantiator valueInstantiator,
SettableBeanProperty[] creatorProps,
- boolean caseInsensitive)
+ boolean caseInsensitive,
+ boolean addAliases)
{
_valueInstantiator = valueInstantiator;
if (caseInsensitive) {
@@ -66,31 +65,89 @@
final int len = creatorProps.length;
_propertyCount = len;
_allProperties = new SettableBeanProperty[len];
+
+ // 26-Feb-2017, tatu: Let's start by aliases, so that there is no
+ // possibility of accidental override of primary names
+ if (addAliases) {
+ final DeserializationConfig config = ctxt.getConfig();
+ for (SettableBeanProperty prop : creatorProps) {
+ // 22-Jan-2018, tatu: ignorable entries should be ignored, even if got aliases
+ if (!prop.isIgnorable()) {
+ List<PropertyName> aliases = prop.findAliases(config);
+ if (!aliases.isEmpty()) {
+ for (PropertyName pn : aliases) {
+ _propertyLookup.put(pn.getSimpleName(), prop);
+ }
+ }
+ }
+ }
+ }
for (int i = 0; i < len; ++i) {
SettableBeanProperty prop = creatorProps[i];
_allProperties[i] = prop;
- _propertyLookup.put(prop.getName(), prop);
+ // 22-Jan-2018, tatu: ignorable entries should be skipped
+ if (!prop.isIgnorable()) {
+ _propertyLookup.put(prop.getName(), prop);
+ }
}
}
/**
- * Factory method used for building actual instances: resolves deserializers
- * and checks for "null values".
+ * Factory method used for building actual instances to be used with POJOS:
+ * resolves deserializers, checks for "null values".
+ *
+ * @since 2.9
*/
public static PropertyBasedCreator construct(DeserializationContext ctxt,
- ValueInstantiator valueInstantiator, SettableBeanProperty[] srcProps)
+ ValueInstantiator valueInstantiator, SettableBeanProperty[] srcCreatorProps,
+ BeanPropertyMap allProperties)
throws JsonMappingException
{
- final int len = srcProps.length;
+ final int len = srcCreatorProps.length;
SettableBeanProperty[] creatorProps = new SettableBeanProperty[len];
for (int i = 0; i < len; ++i) {
- SettableBeanProperty prop = srcProps[i];
+ SettableBeanProperty prop = srcCreatorProps[i];
if (!prop.hasValueDeserializer()) {
prop = prop.withValueDeserializer(ctxt.findContextualValueDeserializer(prop.getType(), prop));
}
creatorProps[i] = prop;
}
- return new PropertyBasedCreator(valueInstantiator, creatorProps,
+ return new PropertyBasedCreator(ctxt, valueInstantiator, creatorProps,
+ allProperties.isCaseInsensitive(),
+ allProperties.hasAliases());
+ }
+
+ /**
+ * Factory method used for building actual instances to be used with types
+ * OTHER than POJOs.
+ * resolves deserializers and checks for "null values".
+ *
+ * @since 2.9
+ */
+ public static PropertyBasedCreator construct(DeserializationContext ctxt,
+ ValueInstantiator valueInstantiator, SettableBeanProperty[] srcCreatorProps,
+ boolean caseInsensitive)
+ throws JsonMappingException
+ {
+ final int len = srcCreatorProps.length;
+ SettableBeanProperty[] creatorProps = new SettableBeanProperty[len];
+ for (int i = 0; i < len; ++i) {
+ SettableBeanProperty prop = srcCreatorProps[i];
+ if (!prop.hasValueDeserializer()) {
+ prop = prop.withValueDeserializer(ctxt.findContextualValueDeserializer(prop.getType(), prop));
+ }
+ creatorProps[i] = prop;
+ }
+ return new PropertyBasedCreator(ctxt, valueInstantiator, creatorProps,
+ caseInsensitive, false);
+ }
+
+ @Deprecated // since 2.9
+ public static PropertyBasedCreator construct(DeserializationContext ctxt,
+ ValueInstantiator valueInstantiator, SettableBeanProperty[] srcCreatorProps)
+ throws JsonMappingException
+ {
+ return construct(ctxt, valueInstantiator, srcCreatorProps,
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
}
@@ -158,7 +215,7 @@
/**
* Simple override of standard {@link java.util.HashMap} to support
- * case-insensitive access to creator properties.
+ * case-insensitive access to creator properties
*
* @since 2.8.5
*/
@@ -168,8 +225,7 @@
@Override
public SettableBeanProperty get(Object key0) {
- String key = (String) key0;
- return super.get(key.toLowerCase());
+ return super.get(((String) key0).toLowerCase());
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java
index 73238b2..23f7503 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java
@@ -131,8 +131,8 @@
value = _creatorParameters[prop.getCreatorIndex()] = _findMissing(prop);
}
if (value == null && _context.isEnabled(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)) {
- throw _context.mappingException(
- "Null value for creator property '%s'; DeserializationFeature.FAIL_ON_NULL_FOR_CREATOR_PARAMETERS enabled",
+ return _context.reportInputMismatch(prop,
+ "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_FOR_CREATOR_PARAMETERS` enabled",
prop.getName(), prop.getCreatorIndex());
}
return value;
@@ -169,13 +169,14 @@
if (_context.isEnabled(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)) {
for (int ix = 0; ix < props.length; ++ix) {
- if (_creatorParameters[ix] == null) {
- _context.reportMappingException("Null value for creator property '%s'; DeserializationFeature.FAIL_ON_NULL_FOR_CREATOR_PARAMETERS enabled",
- props[ix].getName(), props[ix].getCreatorIndex());
- }
+ if (_creatorParameters[ix] == null) {
+ SettableBeanProperty prop = props[ix];
+ _context.reportInputMismatch(prop.getType(),
+ "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_FOR_CREATOR_PARAMETERS` enabled",
+ prop.getName(), props[ix].getCreatorIndex());
+ }
}
}
-
return _creatorParameters;
}
@@ -189,11 +190,12 @@
}
// Second: required?
if (prop.isRequired()) {
- _context.reportMappingException("Missing required creator property '%s' (index %d)",
+ _context.reportInputMismatch(prop, "Missing required creator property '%s' (index %d)",
prop.getName(), prop.getCreatorIndex());
}
if (_context.isEnabled(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES)) {
- _context.reportMappingException("Missing creator property '%s' (index %d); DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES enabled",
+ _context.reportInputMismatch(prop,
+ "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled",
prop.getName(), prop.getCreatorIndex());
}
// Third: default value
@@ -260,7 +262,6 @@
{
final int ix = prop.getCreatorIndex();
_creatorParameters[ix] = value;
-
if (_paramsSeenBig == null) {
int old = _paramsSeen;
int newValue = (old | (1 << ix));
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/SetterlessProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/SetterlessProperty.java
index 8df84b6..a901f07 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/SetterlessProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/SetterlessProperty.java
@@ -8,6 +8,7 @@
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
@@ -40,8 +41,9 @@
_getter = method.getAnnotated();
}
- protected SetterlessProperty(SetterlessProperty src, JsonDeserializer<?> deser) {
- super(src, deser);
+ protected SetterlessProperty(SetterlessProperty src, JsonDeserializer<?> deser,
+ NullValueProvider nva) {
+ super(src, deser, nva);
_annotated = src._annotated;
_getter = src._getter;
}
@@ -53,16 +55,23 @@
}
@Override
- public SetterlessProperty withName(PropertyName newName) {
+ public SettableBeanProperty withName(PropertyName newName) {
return new SetterlessProperty(this, newName);
}
-
+
@Override
- public SetterlessProperty withValueDeserializer(JsonDeserializer<?> deser) {
+ public SettableBeanProperty withValueDeserializer(JsonDeserializer<?> deser) {
if (_valueDeserializer == deser) {
return this;
}
- return new SetterlessProperty(this, deser);
+ // 07-May-2019, tatu: As per [databind#2303], must keep VD/NVP in-sync if they were
+ NullValueProvider nvp = (_valueDeserializer == _nullProvider) ? deser : _nullProvider;
+ return new SetterlessProperty(this, deser, nvp);
+ }
+
+ @Override
+ public SettableBeanProperty withNullProvider(NullValueProvider nva) {
+ return new SetterlessProperty(this, _valueDeserializer, nva);
}
@Override
@@ -96,36 +105,32 @@
{
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_NULL) {
- /* Hmmh. Is this a problem? We won't be setting anything, so it's
- * equivalent of empty Collection/Map in this case
- */
+ // Hmmh. Is this a problem? We won't be setting anything, so it's
+ // equivalent of empty Collection/Map in this case
return;
}
-
- // For [#501] fix we need to implement this but:
+ // For [databind#501] fix we need to implement this but:
if (_valueTypeDeserializer != null) {
- ctxt.reportMappingException(
+ ctxt.reportBadDefinition(getType(), String.format(
"Problem deserializing 'setterless' property (\"%s\"): no way to handle typed deser with setterless yet",
- getName());
+ getName()));
// return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
}
-
// Ok: then, need to fetch Collection/Map to modify:
Object toModify;
try {
- toModify = _getter.invoke(instance);
+ toModify = _getter.invoke(instance, (Object[]) null);
} catch (Exception e) {
_throwAsIOE(p, e);
return; // never gets here
}
- /* Note: null won't work, since we can't then inject anything
- * in. At least that's not good in common case. However,
- * theoretically the case where we get JSON null might
- * be compatible. If so, implementation could be changed.
- */
+ // Note: null won't work, since we can't then inject anything in. At least
+ // that's not good in common case. However, theoretically the case where
+ // we get JSON null might be compatible. If so, implementation could be changed.
if (toModify == null) {
- throw JsonMappingException.from(p,
- "Problem deserializing 'setterless' property '"+getName()+"': get method returned null");
+ ctxt.reportBadDefinition(getType(), String.format(
+ "Problem deserializing 'setterless' property '%s': get method returned null",
+ getName()));
}
_valueDeserializer.deserialize(p, ctxt, toModify);
}
@@ -137,16 +142,16 @@
deserializeAndSet(p, ctxt, instance);
return instance;
}
-
+
@Override
public final void set(Object instance, Object value) throws IOException {
- throw new UnsupportedOperationException("Should never call 'set' on setterless property");
+ throw new UnsupportedOperationException("Should never call `set()` on setterless property ('"+getName()+"')");
}
@Override
public Object setAndReturn(Object instance, Object value) throws IOException
{
set(instance, value);
- return null;
+ return instance;
}
}
\ No newline at end of file
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 0f23fd1..8d5039d 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
@@ -37,6 +37,11 @@
return _deserializer.handledType();
}
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return _deserializer.supportsUpdate(config);
+ }
+
@Override
public JsonDeserializer<?> getDelegatee() {
return _deserializer.getDelegatee();
@@ -58,13 +63,13 @@
}
@Override
- public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
+ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- return _deserializer.deserializeWithType(jp, ctxt, _typeDeserializer);
+ return _deserializer.deserializeWithType(p, ctxt, _typeDeserializer);
}
@Override
- public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) throws IOException
{
// should never happen? (if it can, could call on that object)
@@ -72,12 +77,12 @@
}
@Override
- public Object deserialize(JsonParser jp, DeserializationContext ctxt,
+ public Object deserialize(JsonParser p, DeserializationContext ctxt,
Object intoValue) throws IOException
{
/* 01-Mar-2013, tatu: Hmmh. Tough call as to what to do... need
* to delegate, but will this work reliably? Let's just hope so:
*/
- return _deserializer.deserialize(jp, ctxt, intoValue);
+ return _deserializer.deserialize(p, ctxt, intoValue);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ValueInjector.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ValueInjector.java
index 46e3bc9..148ba8f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ValueInjector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ValueInjector.java
@@ -2,13 +2,8 @@
import java.io.IOException;
-import com.fasterxml.jackson.databind.BeanProperty;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.PropertyMetadata;
-import com.fasterxml.jackson.databind.PropertyName;
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
-import com.fasterxml.jackson.databind.util.Annotations;
/**
* Class that encapsulates details of value injection that occurs before
@@ -19,29 +14,33 @@
public class ValueInjector
extends BeanProperty.Std
{
+ private static final long serialVersionUID = 1L;
+
/**
* Identifier used for looking up value to inject
*/
protected final Object _valueId;
public ValueInjector(PropertyName propName, JavaType type,
- Annotations contextAnnotations, AnnotatedMember mutator,
- Object valueId)
+ AnnotatedMember mutator, Object valueId)
{
- super(propName, type, null, contextAnnotations, mutator,
- PropertyMetadata.STD_OPTIONAL);
+ super(propName, type, null, mutator, PropertyMetadata.STD_OPTIONAL);
_valueId = valueId;
}
- @Deprecated // since 2.3
- public ValueInjector(String propName, JavaType type,
- Annotations contextAnnotations, AnnotatedMember mutator,
- Object valueId)
+ /**
+ * @deprecated in 2.9 (remove from 3.0)
+ */
+ @Deprecated // see [databind#1835]
+ public ValueInjector(PropertyName propName, JavaType type,
+ com.fasterxml.jackson.databind.util.Annotations contextAnnotations, // removed from later versions
+ AnnotatedMember mutator, Object valueId)
{
- this(new PropertyName(propName), type, contextAnnotations, mutator, valueId);
+ this(propName, type, mutator, valueId);
}
public Object findValue(DeserializationContext context, Object beanInstance)
+ throws JsonMappingException
{
return context.findInjectableValue(_valueId, this, beanInstance);
}
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 0394068..c92c8a4 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
@@ -5,8 +5,8 @@
import java.util.concurrent.ArrayBlockingQueue;
import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
@@ -26,23 +26,24 @@
/**********************************************************
*/
- public ArrayBlockingQueueDeserializer(JavaType collectionType,
+ public ArrayBlockingQueueDeserializer(JavaType containerType,
JsonDeserializer<Object> valueDeser, TypeDeserializer valueTypeDeser,
ValueInstantiator valueInstantiator)
{
- super(collectionType, valueDeser, valueTypeDeser, valueInstantiator);
+ super(containerType, valueDeser, valueTypeDeser, valueInstantiator);
}
/**
* Constructor used when creating contextualized instances.
*/
- protected ArrayBlockingQueueDeserializer(JavaType collectionType,
+ protected ArrayBlockingQueueDeserializer(JavaType containerType,
JsonDeserializer<Object> valueDeser, TypeDeserializer valueTypeDeser,
ValueInstantiator valueInstantiator,
- JsonDeserializer<Object> delegateDeser, Boolean unwrapSingle)
+ JsonDeserializer<Object> delegateDeser,
+ NullValueProvider nuller, Boolean unwrapSingle)
{
- super(collectionType, valueDeser, valueTypeDeser, valueInstantiator,
- delegateDeser, unwrapSingle);
+ super(containerType, valueDeser, valueTypeDeser, valueInstantiator, delegateDeser,
+ nuller, unwrapSingle);
}
/**
@@ -59,16 +60,13 @@
@Override
@SuppressWarnings("unchecked")
protected ArrayBlockingQueueDeserializer withResolved(JsonDeserializer<?> dd,
- JsonDeserializer<?> vd, TypeDeserializer vtd, Boolean unwrapSingle)
+ JsonDeserializer<?> vd, TypeDeserializer vtd,
+ NullValueProvider nuller, Boolean unwrapSingle)
{
- if ((dd == _delegateDeserializer) && (vd == _valueDeserializer) && (vtd == _valueTypeDeserializer)
- && (_unwrapSingle == unwrapSingle)) {
- return this;
- }
- return new ArrayBlockingQueueDeserializer(_collectionType,
+ return new ArrayBlockingQueueDeserializer(_containerType,
(JsonDeserializer<Object>) vd, vtd,
- _valueInstantiator, (JsonDeserializer<Object>) dd, unwrapSingle);
-
+ _valueInstantiator, (JsonDeserializer<Object>) dd,
+ nuller, unwrapSingle);
}
/*
@@ -76,63 +74,35 @@
/* JsonDeserializer API
/**********************************************************
*/
-
- @SuppressWarnings("unchecked")
+
@Override
- public Collection<Object> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
+ protected Collection<Object> createDefaultInstance(DeserializationContext ctxt)
+ throws IOException
{
- if (_delegateDeserializer != null) {
- return (Collection<Object>) _valueInstantiator.createUsingDelegate(ctxt,
- _delegateDeserializer.deserialize(jp, ctxt));
- }
- if (jp.getCurrentToken() == JsonToken.VALUE_STRING) {
- String str = jp.getText();
- if (str.length() == 0) {
- return (Collection<Object>) _valueInstantiator.createFromString(ctxt, str);
- }
- }
- return deserialize(jp, ctxt, null);
+ // 07-Nov-2016, tatu: Important: cannot create using default ctor (one
+ // does not exist); and also need to know exact size. Hence, return
+ // null from here
+ return null;
}
@Override
- public Collection<Object> deserialize(JsonParser jp, DeserializationContext ctxt, Collection<Object> result0) throws IOException
+ public Collection<Object> deserialize(JsonParser p, DeserializationContext ctxt,
+ Collection<Object> result0) throws IOException
{
- // Ok: must point to START_ARRAY (or equivalent)
- if (!jp.isExpectedStartArrayToken()) {
- return handleNonArray(jp, ctxt, new ArrayBlockingQueue<Object>(1));
- }
- ArrayList<Object> tmp = new ArrayList<Object>();
-
- JsonDeserializer<Object> valueDes = _valueDeserializer;
- JsonToken t;
- final TypeDeserializer typeDeser = _valueTypeDeserializer;
-
- try {
- while ((t = jp.nextToken()) != JsonToken.END_ARRAY) {
- Object value;
-
- if (t == JsonToken.VALUE_NULL) {
- value = valueDes.getNullValue(ctxt);
- } else if (typeDeser == null) {
- value = valueDes.deserialize(jp, ctxt);
- } else {
- value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
- }
- tmp.add(value);
- }
- } catch (Exception e) {
- throw JsonMappingException.wrapWithPath(e, tmp, tmp.size());
- }
if (result0 != null) {
- result0.addAll(tmp);
- return result0;
+ return super.deserialize(p, ctxt, result0);
}
- return new ArrayBlockingQueue<Object>(tmp.size(), false, tmp);
+ // Ok: must point to START_ARRAY (or equivalent)
+ if (!p.isExpectedStartArrayToken()) {
+ return handleNonArray(p, ctxt, new ArrayBlockingQueue<Object>(1));
+ }
+ result0 = super.deserialize(p, ctxt, new ArrayList<Object>());
+ return new ArrayBlockingQueue<Object>(result0.size(), false, result0);
}
@Override
- public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
+ 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(jp, ctxt);
+ return typeDeserializer.deserializeTypedFromArray(p, ctxt);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicReferenceDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicReferenceDeserializer.java
index 8faa423..486106c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicReferenceDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/AtomicReferenceDeserializer.java
@@ -3,6 +3,7 @@
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
public class AtomicReferenceDeserializer
@@ -16,15 +17,13 @@
/**********************************************************
*/
- @Deprecated // since 2.8
- public AtomicReferenceDeserializer(JavaType fullType) {
- this(fullType, null, null);
- }
-
- public AtomicReferenceDeserializer(JavaType fullType,
+ /**
+ * @since 2.9
+ */
+ public AtomicReferenceDeserializer(JavaType fullType, ValueInstantiator inst,
TypeDeserializer typeDeser, JsonDeserializer<?> deser)
{
- super(fullType, typeDeser, deser);
+ super(fullType, inst, typeDeser, deser);
}
/*
@@ -35,42 +34,39 @@
@Override
public AtomicReferenceDeserializer withResolved(TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
- return new AtomicReferenceDeserializer(_fullType, typeDeser, valueDeser);
+ return new AtomicReferenceDeserializer(_fullType, _valueInstantiator,
+ typeDeser, valueDeser);
}
@Override
- public AtomicReference<Object> getNullValue(DeserializationContext ctxt) {
- return new AtomicReference<Object>();
+ public AtomicReference<Object> getNullValue(DeserializationContext ctxt) throws JsonMappingException {
+ return new AtomicReference<Object>(_valueDeserializer.getNullValue(ctxt));
}
@Override
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ return new AtomicReference<Object>();
+ }
+
+ @Override
public AtomicReference<Object> referenceValue(Object contents) {
return new AtomicReference<Object>(contents);
}
- /*
@Override
- public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
- TypeDeserializer typeDeser) throws IOException
- {
- final JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_NULL) { // can this actually happen?
- return getNullValue(ctxt);
- }
- // 22-Oct-2015, tatu: This handling is probably not needed (or is wrong), but
- // could be result of older (pre-2.7) Jackson trying to serialize natural types.
- // Because of this, let's allow for now, unless proven problematic
- if ((t != null) && t.isScalarValue()) {
- return deserialize(p, ctxt);
- }
- // 19-Apr-2016, tatu: Alas, due to there not really being anything for AtomicReference
- // itself, need to just ignore `typeDeser`, use TypeDeserializer we do have for contents
- // and it might just work.
-
- if (_valueTypeDeserializer == null) {
- return deserialize(p, ctxt);
- }
- return new AtomicReference<Object>(_valueTypeDeserializer.deserializeTypedFromAny(p, ctxt));
+ public Object getReferenced(AtomicReference<Object> reference) {
+ return reference.get();
}
- */
+
+ @Override // since 2.9
+ public AtomicReference<Object> updateReference(AtomicReference<Object> reference, Object contents) {
+ reference.set(contents);
+ return reference;
+ }
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ // yes; regardless of value deserializer reference itself may be updated
+ return Boolean.TRUE;
+ }
}
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 c3cb45f..c255d89 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
@@ -4,15 +4,16 @@
import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
+
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
-import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
-import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference;
-import com.fasterxml.jackson.databind.deser.ValueInstantiator;
+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.util.ClassUtil;
/**
* Basic serializer that can take JSON "Array" structure and
@@ -32,8 +33,6 @@
// // Configuration
- protected final JavaType _collectionType;
-
/**
* Value deserializer.
*/
@@ -55,15 +54,6 @@
*/
protected final JsonDeserializer<Object> _delegateDeserializer;
- /**
- * Specific override for this instance (from proper, or global per-type overrides)
- * to indicate whether single value may be taken to mean an unwrapped one-element array
- * or not. If null, left to global defaults.
- *
- * @since 2.7
- */
- protected final Boolean _unwrapSingle;
-
// NOTE: no PropertyBasedCreator, as JSON Arrays have no properties
/*
@@ -80,25 +70,24 @@
JsonDeserializer<Object> valueDeser,
TypeDeserializer valueTypeDeser, ValueInstantiator valueInstantiator)
{
- this(collectionType, valueDeser, valueTypeDeser, valueInstantiator, null, null);
+ this(collectionType, valueDeser, valueTypeDeser, valueInstantiator, null, null, null);
}
/**
* Constructor used when creating contextualized instances.
+ *
+ * @since 2.9
*/
protected CollectionDeserializer(JavaType collectionType,
JsonDeserializer<Object> valueDeser, TypeDeserializer valueTypeDeser,
- ValueInstantiator valueInstantiator,
- JsonDeserializer<Object> delegateDeser,
- Boolean unwrapSingle)
+ ValueInstantiator valueInstantiator, JsonDeserializer<Object> delegateDeser,
+ NullValueProvider nuller, Boolean unwrapSingle)
{
- super(collectionType);
- _collectionType = collectionType;
+ super(collectionType, nuller, unwrapSingle);
_valueDeserializer = valueDeser;
_valueTypeDeserializer = valueTypeDeser;
_valueInstantiator = valueInstantiator;
_delegateDeserializer = delegateDeser;
- _unwrapSingle = unwrapSingle;
}
/**
@@ -107,42 +96,28 @@
*/
protected CollectionDeserializer(CollectionDeserializer src)
{
- super(src._collectionType);
- _collectionType = src._collectionType;
+ super(src);
_valueDeserializer = src._valueDeserializer;
_valueTypeDeserializer = src._valueTypeDeserializer;
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
- _unwrapSingle = src._unwrapSingle;
}
/**
* Fluent-factory method call to construct contextual instance.
*
- * @since 2.7
+ * @since 2.9
*/
@SuppressWarnings("unchecked")
protected CollectionDeserializer withResolved(JsonDeserializer<?> dd,
JsonDeserializer<?> vd, TypeDeserializer vtd,
- Boolean unwrapSingle)
+ NullValueProvider nuller, Boolean unwrapSingle)
{
- if ((dd == _delegateDeserializer) && (vd == _valueDeserializer) && (vtd == _valueTypeDeserializer)
- && (_unwrapSingle == unwrapSingle)) {
- return this;
- }
- return new CollectionDeserializer(_collectionType,
+//if (true) throw new Error();
+ return new CollectionDeserializer(_containerType,
(JsonDeserializer<Object>) vd, vtd,
- _valueInstantiator, (JsonDeserializer<Object>) dd, unwrapSingle);
- }
-
- /**
- * @deprecated Since 2.7 as it does not pass `unwrapSingle`
- */
- @Deprecated // since 2.7 -- will not retain "unwrapSingle" setting
- protected CollectionDeserializer withResolved(JsonDeserializer<?> dd,
- JsonDeserializer<?> vd, TypeDeserializer vtd)
- {
- return withResolved(dd, vd, vtd, _unwrapSingle);
+ _valueInstantiator, (JsonDeserializer<Object>) dd,
+ nuller, unwrapSingle);
}
// Important: do NOT cache if polymorphic values
@@ -176,17 +151,19 @@
if (_valueInstantiator.canCreateUsingDelegate()) {
JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
if (delegateType == null) {
- throw new IllegalArgumentException("Invalid delegate-creator definition for "+_collectionType
- +": value instantiator ("+_valueInstantiator.getClass().getName()
- +") returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'");
+ ctxt.reportBadDefinition(_containerType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'",
+_containerType,
+ _valueInstantiator.getClass().getName()));
}
delegateDeser = findDeserializer(ctxt, delegateType, property);
} else if (_valueInstantiator.canCreateUsingArrayDelegate()) {
JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
if (delegateType == null) {
- throw new IllegalArgumentException("Invalid array-delegate-creator definition for "+_collectionType
- +": value instantiator ("+_valueInstantiator.getClass().getName()
- +") returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'");
+ ctxt.reportBadDefinition(_containerType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'",
+ _containerType,
+ _valueInstantiator.getClass().getName()));
}
delegateDeser = findDeserializer(ctxt, delegateType, property);
}
@@ -201,7 +178,7 @@
// May have a content converter
valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser);
- final JavaType vt = _collectionType.getContentType();
+ final JavaType vt = _containerType.getContentType();
if (valueDeser == null) {
valueDeser = ctxt.findContextualValueDeserializer(vt, property);
} else { // if directly assigned, probably not yet contextual, so:
@@ -212,7 +189,17 @@
if (valueTypeDeser != null) {
valueTypeDeser = valueTypeDeser.forProperty(property);
}
- return withResolved(delegateDeser, valueDeser, valueTypeDeser, unwrapSingle);
+ NullValueProvider nuller = findContentNullProvider(ctxt, property, valueDeser);
+ if ( (unwrapSingle != _unwrapSingle)
+ || (nuller != _nullProvider)
+ || (delegateDeser != _delegateDeserializer)
+ || (valueDeser != _valueDeserializer)
+ || (valueTypeDeser != _valueTypeDeserializer)
+ ) {
+ return withResolved(delegateDeser, valueDeser, valueTypeDeser,
+ nuller, unwrapSingle);
+ }
+ return this;
}
/*
@@ -222,21 +209,21 @@
*/
@Override
- public JavaType getContentType() {
- return _collectionType.getContentType();
- }
-
- @Override
public JsonDeserializer<Object> getContentDeserializer() {
return _valueDeserializer;
}
-
+
+ @Override
+ public ValueInstantiator getValueInstantiator() {
+ return _valueInstantiator;
+ }
+
/*
/**********************************************************
/* JsonDeserializer API
/**********************************************************
*/
-
+
@SuppressWarnings("unchecked")
@Override
public Collection<Object> deserialize(JsonParser p, DeserializationContext ctxt)
@@ -246,19 +233,28 @@
return (Collection<Object>) _valueInstantiator.createUsingDelegate(ctxt,
_delegateDeserializer.deserialize(p, 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.
- */
+ // 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)) {
String str = p.getText();
if (str.length() == 0) {
return (Collection<Object>) _valueInstantiator.createFromString(ctxt, str);
}
}
- return deserialize(p, ctxt, (Collection<Object>) _valueInstantiator.createUsingDefault(ctxt));
+ return deserialize(p, ctxt, createDefaultInstance(ctxt));
}
+ /**
+ * @since 2.9
+ */
+ @SuppressWarnings("unchecked")
+ protected Collection<Object> createDefaultInstance(DeserializationContext ctxt)
+ throws IOException
+ {
+ return (Collection<Object>) _valueInstantiator.createUsingDefault(ctxt);
+ }
+
@Override
public Collection<Object> deserialize(JsonParser p, DeserializationContext ctxt,
Collection<Object> result)
@@ -272,38 +268,36 @@
p.setCurrentValue(result);
JsonDeserializer<Object> valueDes = _valueDeserializer;
+ // Let's offline handling of values with Object Ids (simplifies code here)
+ if (valueDes.getObjectIdReader() != null) {
+ return _deserializeWithObjectId(p, ctxt, result);
+ }
final TypeDeserializer typeDeser = _valueTypeDeserializer;
- CollectionReferringAccumulator referringAccumulator =
- (valueDes.getObjectIdReader() == null) ? null :
- new CollectionReferringAccumulator(_collectionType.getContentType().getRawClass(), result);
-
JsonToken t;
while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
try {
Object value;
if (t == JsonToken.VALUE_NULL) {
- value = valueDes.getNullValue(ctxt);
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
- if (referringAccumulator != null) {
- referringAccumulator.add(value);
- } else {
- result.add(value);
- }
+ result.add(value);
+
+ /* 17-Dec-2017, tatu: should not occur at this level...
} catch (UnresolvedForwardReference reference) {
- if (referringAccumulator == null) {
- throw JsonMappingException
- .from(p, "Unresolved forward reference but no identity info", reference);
- }
- Referring ref = referringAccumulator.handleUnresolvedReference(reference);
- reference.getRoid().appendReferring(ref);
+ throw JsonMappingException
+ .from(p, "Unresolved forward reference but no identity info", reference);
+ */
} catch (Exception e) {
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
- if (!wrap && e instanceof RuntimeException) {
- throw (RuntimeException)e;
+ if (!wrap) {
+ ClassUtil.throwIfRTE(e);
}
throw JsonMappingException.wrapWithPath(e, result, result.size());
}
@@ -335,7 +329,7 @@
((_unwrapSingle == null) &&
ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
if (!canWrap) {
- return (Collection<Object>) ctxt.handleUnexpectedToken(_collectionType.getRawClass(), p);
+ return (Collection<Object>) ctxt.handleUnexpectedToken(_containerType.getRawClass(), p);
}
JsonDeserializer<Object> valueDes = _valueDeserializer;
final TypeDeserializer typeDeser = _valueTypeDeserializer;
@@ -345,7 +339,11 @@
try {
if (t == JsonToken.VALUE_NULL) {
- value = valueDes.getNullValue(ctxt);
+ // 03-Feb-2017, tatu: Hmmh. I wonder... let's try skipping here, too
+ if (_skipNullValues) {
+ return result;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
@@ -359,7 +357,56 @@
return result;
}
- public final static class CollectionReferringAccumulator {
+ protected Collection<Object> _deserializeWithObjectId(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);
+ }
+ // [databind#631]: Assign current value, to be accessible by custom serializers
+ p.setCurrentValue(result);
+
+ final JsonDeserializer<Object> valueDes = _valueDeserializer;
+ final TypeDeserializer typeDeser = _valueTypeDeserializer;
+ CollectionReferringAccumulator referringAccumulator =
+ new CollectionReferringAccumulator(_containerType.getContentType().getRawClass(), result);
+
+ JsonToken t;
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
+ try {
+ Object value;
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(p, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(p, ctxt, typeDeser);
+ }
+ referringAccumulator.add(value);
+ } catch (UnresolvedForwardReference reference) {
+ Referring ref = referringAccumulator.handleUnresolvedReference(reference);
+ reference.getRoid().appendReferring(ref);
+ } catch (Exception e) {
+ boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
+ if (!wrap) {
+ ClassUtil.throwIfRTE(e);
+ }
+ throw JsonMappingException.wrapWithPath(e, result, result.size());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Helper class for dealing with Object Id references for values contained in
+ * collections being deserialized.
+ */
+ public static class CollectionReferringAccumulator {
private final Class<?> _elementType;
private final Collection<Object> _result;
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 251a51c..123cf6b 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
@@ -3,10 +3,14 @@
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
+import com.fasterxml.jackson.databind.deser.ValueInstantiator;
+import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.databind.util.AccessPattern;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Intermediate base deserializer class that adds more shared accessor
@@ -15,9 +19,64 @@
@SuppressWarnings("serial")
public abstract class ContainerDeserializerBase<T>
extends StdDeserializer<T>
+ implements ValueInstantiator.Gettable // since 2.9
{
- protected ContainerDeserializerBase(JavaType selfType) {
+ protected final JavaType _containerType;
+
+ /**
+ * Handler we need for dealing with nulls.
+ *
+ * @since 2.9
+ */
+ protected final NullValueProvider _nullProvider;
+
+ /**
+ * Specific override for this instance (from proper, or global per-type overrides)
+ * to indicate whether single value may be taken to mean an unwrapped one-element array
+ * or not. If null, left to global defaults.
+ *
+ * @since 2.9 (demoted from sub-classes where added in 2.7)
+ */
+ protected final Boolean _unwrapSingle;
+
+ /**
+ * Marker flag set if the <code>_nullProvider</code> indicates that all null
+ * content values should be skipped (instead of being possibly converted).
+ *
+ * @since 2.9
+ */
+ protected final boolean _skipNullValues;
+
+ protected ContainerDeserializerBase(JavaType selfType,
+ NullValueProvider nuller, Boolean unwrapSingle) {
super(selfType);
+ _containerType = selfType;
+ _unwrapSingle = unwrapSingle;
+ _nullProvider = nuller;
+ _skipNullValues = NullsConstantProvider.isSkipper(nuller);
+ }
+
+ protected ContainerDeserializerBase(JavaType selfType) {
+ this(selfType, null, null);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected ContainerDeserializerBase(ContainerDeserializerBase<?> base) {
+ this(base, base._nullProvider, base._unwrapSingle);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected ContainerDeserializerBase(ContainerDeserializerBase<?> base,
+ NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base._containerType);
+ _containerType = base._containerType;
+ _nullProvider = nuller;
+ _unwrapSingle = unwrapSingle;
+ _skipNullValues = NullsConstantProvider.isSkipper(nuller);
}
/*
@@ -26,16 +85,25 @@
/**********************************************************
*/
+ @Override // since 2.9
+ public JavaType getValueType() { return _containerType; }
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return Boolean.TRUE;
+ }
+
@Override
public SettableBeanProperty findBackReference(String refName) {
JsonDeserializer<Object> valueDeser = getContentDeserializer();
if (valueDeser == null) {
- throw new IllegalArgumentException("Can not handle managed/back reference '"+refName
- +"': type: container deserializer of type "+getClass().getName()+" returned null for 'getContentDeserializer()'");
+ throw new IllegalArgumentException(String.format(
+ "Cannot handle managed/back reference '%s': type: container deserializer of type %s returned null for 'getContentDeserializer()'",
+ refName, getClass().getName()));
}
return valueDeser.findBackReference(refName);
}
-
+
/*
/**********************************************************
/* Extended API
@@ -46,13 +114,48 @@
* Accessor for declared type of contained value elements; either exact
* type, or one of its supertypes.
*/
- public abstract JavaType getContentType();
+ public JavaType getContentType() {
+ if (_containerType == null) {
+ return TypeFactory.unknownType(); // should never occur but...
+ }
+ return _containerType.getContentType();
+ }
/**
* Accesor for deserializer use for deserializing content values.
*/
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
+ // and may not be shared; for some deserializers this may be further refined.
+ return AccessPattern.DYNAMIC;
+ }
+
+ @Override // since 2.9
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ ValueInstantiator vi = getValueInstantiator();
+ if (vi == null || !vi.canCreateUsingDefault()) {
+ JavaType type = getValueType();
+ ctxt.reportBadDefinition(type,
+ String.format("Cannot create empty instance of %s, no default Creator", type));
+ }
+ try {
+ return vi.createUsingDefault(ctxt);
+ } catch (IOException e) {
+ return ClassUtil.throwAsMappingException(ctxt, e);
+ }
+ }
+
/*
/**********************************************************
/* Shared methods for sub-classes
@@ -62,24 +165,20 @@
/**
* Helper method called by various Map(-like) deserializers.
*/
- protected void wrapAndThrow(Throwable t, Object ref, String key) throws IOException
+ protected <BOGUS> BOGUS wrapAndThrow(Throwable t, Object ref, String key) throws IOException
{
// to handle StackOverflow:
while (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
// Errors and "plain" IOExceptions to be passed as is
- if (t instanceof Error) {
- throw (Error) t;
- }
+ ClassUtil.throwIfError(t);
// ... except for mapping exceptions
if (t instanceof IOException && !(t instanceof JsonMappingException)) {
throw (IOException) t;
}
// for [databind#1141]
- if (key == null) {
- key = "N/A";
- }
- throw JsonMappingException.wrapWithPath(t, ref, key);
+ throw JsonMappingException.wrapWithPath(t, ref,
+ ClassUtil.nonNull(key, "N/A"));
}
}
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 4a840bc..14bddb3 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
@@ -1,20 +1,20 @@
package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
+import java.lang.reflect.Constructor;
import java.sql.Timestamp;
import java.text.*;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
+
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
-import com.fasterxml.jackson.databind.BeanProperty;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonMappingException;
+
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.StdDateFormat;
/**
@@ -59,7 +59,7 @@
}
return null;
}
-
+
/*
/**********************************************************
/* Intermediate class for Date-based ones
@@ -80,7 +80,7 @@
* Let's also keep format String for reference, to use for error messages
*/
protected final String _formatString;
-
+
protected DateBasedDeserializer(Class<?> clz) {
super(clz);
_customFormat = null;
@@ -97,54 +97,87 @@
protected abstract DateBasedDeserializer<T> withDateFormat(DateFormat df, String formatStr);
@Override
- public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
+ BeanProperty property)
throws JsonMappingException
{
- if (property != null) {
- JsonFormat.Value format = findFormatOverrides(ctxt, property,
- this.handledType());
- if (format != null) {
- TimeZone tz = format.getTimeZone();
- // First: fully custom pattern?
- if (format.hasPattern()) {
- final String pattern = format.getPattern();
+ final JsonFormat.Value format = findFormatOverrides(ctxt, property,
+ handledType());
+
+ if (format != null) {
+ TimeZone tz = format.getTimeZone();
+ final Boolean lenient = format.getLenient();
+
+ // First: fully custom pattern?
+ if (format.hasPattern()) {
+ final String pattern = format.getPattern();
+ final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
+ SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
+ if (tz == null) {
+ tz = ctxt.getTimeZone();
+ }
+ df.setTimeZone(tz);
+ if (lenient != null) {
+ df.setLenient(lenient);
+ }
+ return withDateFormat(df, pattern);
+ }
+ // But if not, can still override timezone
+ if (tz != null) {
+ DateFormat df = ctxt.getConfig().getDateFormat();
+ // one shortcut: with our custom format, can simplify handling a bit
+ if (df.getClass() == StdDateFormat.class) {
final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
- SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
- if (tz == null) {
- tz = ctxt.getTimeZone();
+ StdDateFormat std = (StdDateFormat) df;
+ std = std.withTimeZone(tz);
+ std = std.withLocale(loc);
+ if (lenient != null) {
+ std = std.withLenient(lenient);
}
+ df = std;
+ } else {
+ // otherwise need to clone, re-set timezone:
+ df = (DateFormat) df.clone();
df.setTimeZone(tz);
- return withDateFormat(df, pattern);
- }
- // But if not, can still override timezone
- if (tz != null) {
- DateFormat df = ctxt.getConfig().getDateFormat();
- // one shortcut: with our custom format, can simplify handling a bit
- if (df.getClass() == StdDateFormat.class) {
- final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
- StdDateFormat std = (StdDateFormat) df;
- std = std.withTimeZone(tz);
- std = std.withLocale(loc);
- df = std;
- } else {
- // otherwise need to clone, re-set timezone:
- df = (DateFormat) df.clone();
- df.setTimeZone(tz);
+ if (lenient != null) {
+ df.setLenient(lenient);
}
- return withDateFormat(df, _formatString);
}
+ return withDateFormat(df, _formatString);
+ }
+ // or maybe even just leniency?
+ if (lenient != null) {
+ DateFormat df = ctxt.getConfig().getDateFormat();
+ String pattern = _formatString;
+ // one shortcut: with our custom format, can simplify handling a bit
+ if (df.getClass() == StdDateFormat.class) {
+ StdDateFormat std = (StdDateFormat) df;
+ std = std.withLenient(lenient);
+ df = std;
+ pattern = std.toPattern();
+ } else {
+ // otherwise need to clone,
+ df = (DateFormat) df.clone();
+ df.setLenient(lenient);
+ if (df instanceof SimpleDateFormat) {
+ ((SimpleDateFormat) df).toPattern();
+ }
+ }
+ if (pattern == null) {
+ pattern = "[unknown]";
+ }
+ return withDateFormat(df, pattern);
}
}
return this;
}
-
+
@Override
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
throws IOException
{
if (_customFormat != null) {
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_STRING) {
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
String str = p.getText().trim();
if (str.length() == 0) {
return (Date) getEmptyValue(ctxt);
@@ -158,16 +191,6 @@
}
}
}
- // [databind#381]
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Date parsed = _parseDate(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
}
return super._parseDate(p, ctxt);
}
@@ -185,29 +208,32 @@
/**
* We may know actual expected type; if so, it will be
* used for instantiation.
+ *
+ * @since 2.9
*/
- protected final Class<? extends Calendar> _calendarClass;
-
+ protected final Constructor<Calendar> _defaultCtor;
+
public CalendarDeserializer() {
super(Calendar.class);
- _calendarClass = null;
+ _defaultCtor = null;
}
+ @SuppressWarnings("unchecked")
public CalendarDeserializer(Class<? extends Calendar> cc) {
super(cc);
- _calendarClass = cc;
+ _defaultCtor = (Constructor<Calendar>) ClassUtil.findConstructor(cc, false);
}
public CalendarDeserializer(CalendarDeserializer src, DateFormat df, String formatString) {
super(src, df, formatString);
- _calendarClass = src._calendarClass;
+ _defaultCtor = src._defaultCtor;
}
@Override
protected CalendarDeserializer withDateFormat(DateFormat df, String formatString) {
return new CalendarDeserializer(this, df, formatString);
}
-
+
@Override
public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -215,11 +241,11 @@
if (d == null) {
return null;
}
- if (_calendarClass == null) {
+ if (_defaultCtor == null) {
return ctxt.constructCalendar(d);
}
try {
- Calendar c = _calendarClass.newInstance();
+ Calendar c = _defaultCtor.newInstance();
c.setTimeInMillis(d.getTime());
TimeZone tz = ctxt.getTimeZone();
if (tz != null) {
@@ -227,7 +253,7 @@
}
return c;
} catch (Exception e) {
- return (Calendar) ctxt.handleInstantiationProblem(_calendarClass, d, e);
+ return (Calendar) ctxt.handleInstantiationProblem(handledType(), d, e);
}
}
}
@@ -255,8 +281,8 @@
}
@Override
- public java.util.Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
- return _parseDate(jp, ctxt);
+ public java.util.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ return _parseDate(p, ctxt);
}
}
@@ -278,8 +304,8 @@
}
@Override
- public java.sql.Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
- Date d = _parseDate(jp, ctxt);
+ public java.sql.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ Date d = _parseDate(p, ctxt);
return (d == null) ? null : new java.sql.Date(d.getTime());
}
}
@@ -304,9 +330,9 @@
}
@Override
- public java.sql.Timestamp deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
+ public java.sql.Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- Date d = _parseDate(jp, ctxt);
+ Date d = _parseDate(p, ctxt);
return (d == null) ? null : new Timestamp(d.getTime());
}
}
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 0c14ca2..f436878 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
@@ -4,11 +4,11 @@
import java.util.Collection;
import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
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.util.AccessPattern;
/**
* Base class that simplifies implementations of {@link JsonDeserializer}s
@@ -32,22 +32,19 @@
/**********************************************************************
*/
- public DelegatingDeserializer(JsonDeserializer<?> delegatee)
+ public DelegatingDeserializer(JsonDeserializer<?> d)
{
- super(_figureType(delegatee));
- _delegatee = delegatee;
+ super(d.handledType());
+ _delegatee = d;
}
- protected abstract JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee);
+ /*
+ /**********************************************************************
+ /* Abstract methods to implement
+ /**********************************************************************
+ */
- private static Class<?> _figureType(JsonDeserializer<?> deser)
- {
- Class<?> cls = deser.handledType();
- if (cls != null) {
- return cls;
- }
- return Object.class;
- }
+ protected abstract JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee);
/*
/**********************************************************************
@@ -76,23 +73,13 @@
return newDelegatingInstance(del);
}
- /**
- * @deprecated Since 2.3, use {@link #newDelegatingInstance} instead
- */
- @Deprecated
- protected JsonDeserializer<?> _createContextual(DeserializationContext ctxt,
- BeanProperty property, JsonDeserializer<?> newDelegatee)
+ @Override
+ public JsonDeserializer<?> replaceDelegatee(JsonDeserializer<?> delegatee)
{
- if (newDelegatee == _delegatee) {
+ if (delegatee == _delegatee) {
return this;
}
- return newDelegatingInstance(newDelegatee);
- }
-
- @Override
- public SettableBeanProperty findBackReference(String logicalName) {
- // [Issue#253]: Hope this works....
- return _delegatee.findBackReference(logicalName);
+ return newDelegatingInstance(delegatee);
}
/*
@@ -102,27 +89,27 @@
*/
@Override
- public Object deserialize(JsonParser jp, DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ public Object deserialize(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
- return _delegatee.deserialize(jp, ctxt);
+ return _delegatee.deserialize(p, ctxt);
}
@SuppressWarnings("unchecked")
@Override
- public Object deserialize(JsonParser jp, DeserializationContext ctxt,
+ public Object deserialize(JsonParser p, DeserializationContext ctxt,
Object intoValue)
- throws IOException, JsonProcessingException
+ throws IOException
{
- return ((JsonDeserializer<Object>)_delegatee).deserialize(jp, ctxt, intoValue);
+ return ((JsonDeserializer<Object>)_delegatee).deserialize(p, ctxt, intoValue);
}
@Override
- public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
- throws IOException, JsonProcessingException
+ throws IOException
{
- return _delegatee.deserializeWithType(jp, ctxt, typeDeserializer);
+ return _delegatee.deserializeWithType(p, ctxt, typeDeserializer);
}
/*
@@ -132,12 +119,27 @@
*/
@Override
- public JsonDeserializer<?> replaceDelegatee(JsonDeserializer<?> delegatee)
- {
- if (delegatee == _delegatee) {
- return this;
- }
- return newDelegatingInstance(delegatee);
+ public boolean isCachable() { return _delegatee.isCachable(); }
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return _delegatee.supportsUpdate(config);
+ }
+
+ @Override
+ public JsonDeserializer<?> getDelegatee() {
+ return _delegatee;
+ }
+
+ @Override
+ public SettableBeanProperty findBackReference(String logicalName) {
+ // [databind#253]: Hope this works....
+ return _delegatee.findBackReference(logicalName);
+ }
+
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ return _delegatee.getNullAccessPattern();
}
@Override
@@ -151,26 +153,8 @@
}
@Override
- @Deprecated // remove in 2.7
- public Object getNullValue() { return _delegatee.getNullValue(); }
-
- // Remove in 2.7
- @Override
- @Deprecated // remove in 2.7
- public Object getEmptyValue() { return _delegatee.getEmptyValue(); }
-
-
- @Override
public Collection<Object> getKnownPropertyNames() { return _delegatee.getKnownPropertyNames(); }
-
- @Override
- public boolean isCachable() { return _delegatee.isCachable(); }
@Override
public ObjectIdReader getObjectIdReader() { return _delegatee.getObjectIdReader(); }
-
- @Override
- public JsonDeserializer<?> getDelegatee() {
- return _delegatee;
- }
}
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 c3ad93e..c697e1c 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
@@ -2,9 +2,13 @@
import java.io.IOException;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+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;
@@ -19,6 +23,7 @@
@JacksonStdImpl // was missing until 2.6
public class EnumDeserializer
extends StdScalarDeserializer<Object>
+ implements ContextualDeserializer
{
private static final long serialVersionUID = 1L;
@@ -41,16 +46,42 @@
* @since 2.7.3
*/
protected CompactStringObjectMap _lookupByToString;
-
- public EnumDeserializer(EnumResolver byNameResolver)
+
+ protected final Boolean _caseInsensitive;
+
+ /**
+ * @since 2.9
+ */
+ public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)
{
super(byNameResolver.getEnumClass());
_lookupByName = byNameResolver.constructLookup();
_enumsByIndex = byNameResolver.getRawEnums();
_enumDefaultValue = byNameResolver.getDefaultValue();
+ _caseInsensitive = caseInsensitive;
}
/**
+ * @since 2.9
+ */
+ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)
+ {
+ super(base);
+ _lookupByName = base._lookupByName;
+ _enumsByIndex = base._enumsByIndex;
+ _enumDefaultValue = base._enumDefaultValue;
+ _caseInsensitive = caseInsensitive;
+ }
+
+ /**
+ * @deprecated Since 2.9
+ */
+ @Deprecated
+ public EnumDeserializer(EnumResolver byNameResolver) {
+ this(byNameResolver, null);
+ }
+
+ /**
* @deprecated Since 2.8
*/
@Deprecated
@@ -98,6 +129,28 @@
return new FactoryBasedEnumDeserializer(enumClass, factory);
}
+ /**
+ * @since 2.9
+ */
+ public EnumDeserializer withResolved(Boolean caseInsensitive) {
+ if (_caseInsensitive == caseInsensitive) {
+ return this;
+ }
+ return new EnumDeserializer(this, caseInsensitive);
+ }
+
+ @Override // since 2.9
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
+ BeanProperty property) throws JsonMappingException
+ {
+ Boolean caseInsensitive = findFormatFeature(ctxt, property, handledType(),
+ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
+ if (caseInsensitive == null) {
+ caseInsensitive = _caseInsensitive;
+ }
+ return withResolved(caseInsensitive);
+ }
+
/*
/**********************************************************
/* Default JsonDeserializer implementation
@@ -167,17 +220,30 @@
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
return getEmptyValue(ctxt);
}
- } else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
- // [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
- char c = name.charAt(0);
- if (c >= '0' && c <= '9') {
- try {
- int index = Integer.parseInt(name);
- if (index >= 0 && index < _enumsByIndex.length) {
- return _enumsByIndex[index];
+ } else {
+ // [databind#1313]: Case insensitive enum deserialization
+ if (Boolean.TRUE.equals(_caseInsensitive)) {
+ Object match = lookup.findCaseInsensitive(name);
+ if (match != null) {
+ return match;
+ }
+ } else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
+ // [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
+ char c = name.charAt(0);
+ if (c >= '0' && c <= '9') {
+ try {
+ int index = Integer.parseInt(name);
+ if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
+ return ctxt.handleWeirdStringValue(_enumClass(), name,
+"value looks like quoted Enum index, but `MapperFeature.ALLOW_COERCION_OF_SCALARS` prevents use"
+ );
+ }
+ if (index >= 0 && index < _enumsByIndex.length) {
+ return _enumsByIndex[index];
+ }
+ } catch (NumberFormatException e) {
+ // fine, ignore, was not an integer
}
- } catch (NumberFormatException e) {
- // fine, ignore, was not an integer
}
}
}
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 2fdd04a..d8931ea 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
@@ -6,7 +6,14 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
+import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
+import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
+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.util.ClassUtil;
/**
* Deserializer for {@link EnumMap} values.
@@ -17,12 +24,10 @@
@SuppressWarnings({ "unchecked", "rawtypes" })
public class EnumMapDeserializer
extends ContainerDeserializerBase<EnumMap<?,?>>
- implements ContextualDeserializer
+ implements ContextualDeserializer, ResolvableDeserializer
{
private static final long serialVersionUID = 1;
- protected final JavaType _mapType;
-
protected final Class<?> _enumClass;
protected KeyDeserializer _keyDeserializer;
@@ -34,31 +39,127 @@
* is the type deserializer that can handle it
*/
protected final TypeDeserializer _valueTypeDeserializer;
+
+ // // Instance construction settings:
+ /**
+ * @since 2.9
+ */
+ protected final ValueInstantiator _valueInstantiator;
+
+ /**
+ * Deserializer that is used iff delegate-based creator is
+ * to be used for deserializing from JSON Object.
+ */
+ protected JsonDeserializer<Object> _delegateDeserializer;
+
+ /**
+ * If the Map is to be instantiated using non-default constructor
+ * or factory method
+ * that takes one or more named properties as argument(s),
+ * this creator is used for instantiation.
+ */
+ protected PropertyBasedCreator _propertyBasedCreator;
+
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
- public EnumMapDeserializer(JavaType mapType, KeyDeserializer keyDeserializer, JsonDeserializer<?> valueDeser, TypeDeserializer valueTypeDeser)
+ /**
+ * @since 2.9
+ */
+ public EnumMapDeserializer(JavaType mapType, ValueInstantiator valueInst,
+ KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser, TypeDeserializer vtd,
+ NullValueProvider nuller)
{
- super(mapType);
- _mapType = mapType;
+ super(mapType, nuller, null);
_enumClass = mapType.getKeyType().getRawClass();
- _keyDeserializer = keyDeserializer;
+ _keyDeserializer = keyDeser;
_valueDeserializer = (JsonDeserializer<Object>) valueDeser;
- _valueTypeDeserializer = valueTypeDeser;
+ _valueTypeDeserializer = vtd;
+ _valueInstantiator = valueInst;
}
- public EnumMapDeserializer withResolved(KeyDeserializer keyDeserializer, JsonDeserializer<?> valueDeserializer, TypeDeserializer valueTypeDeser)
+ /**
+ * @since 2.9
+ */
+ protected EnumMapDeserializer(EnumMapDeserializer base,
+ KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser, TypeDeserializer vtd,
+ NullValueProvider nuller)
{
- if ((keyDeserializer == _keyDeserializer) && (valueDeserializer == _valueDeserializer) && (valueTypeDeser == _valueTypeDeserializer)) {
- return this;
- }
- return new EnumMapDeserializer(_mapType, keyDeserializer, valueDeserializer, _valueTypeDeserializer);
+ super(base, nuller, base._unwrapSingle);
+ _enumClass = base._enumClass;
+ _keyDeserializer = keyDeser;
+ _valueDeserializer = (JsonDeserializer<Object>) valueDeser;
+ _valueTypeDeserializer = vtd;
+
+ _valueInstantiator = base._valueInstantiator;
+ _delegateDeserializer = base._delegateDeserializer;
+ _propertyBasedCreator = base._propertyBasedCreator;
+ }
+
+ @Deprecated // since 2.9
+ public EnumMapDeserializer(JavaType mapType, KeyDeserializer keyDeser,
+ JsonDeserializer<?> valueDeser, TypeDeserializer vtd)
+ {
+ this(mapType, null, keyDeser, valueDeser, vtd, null);
}
+ public EnumMapDeserializer withResolved(KeyDeserializer keyDeserializer,
+ JsonDeserializer<?> valueDeserializer, TypeDeserializer valueTypeDeser,
+ NullValueProvider nuller)
+ {
+ if ((keyDeserializer == _keyDeserializer) && (nuller == _nullProvider)
+ && (valueDeserializer == _valueDeserializer) && (valueTypeDeser == _valueTypeDeserializer)) {
+ return this;
+ }
+ return new EnumMapDeserializer(this,
+ keyDeserializer, valueDeserializer, valueTypeDeser, nuller);
+ }
+
+ /*
+ /**********************************************************
+ /* Validation, post-processing (ResolvableDeserializer)
+ /**********************************************************
+ */
+
+ @Override
+ public void resolve(DeserializationContext ctxt) throws JsonMappingException
+ {
+ // May need to resolve types for delegate- and/or property-based creators:
+ if (_valueInstantiator != null) {
+ if (_valueInstantiator.canCreateUsingDelegate()) {
+ JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
+ if (delegateType == null) {
+ ctxt.reportBadDefinition(_containerType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'",
+ _containerType,
+ _valueInstantiator.getClass().getName()));
+ }
+ /* Theoretically should be able to get CreatorProperty for delegate
+ * parameter to pass; but things get tricky because DelegateCreator
+ * may contain injectable values. So, for now, let's pass nothing.
+ */
+ _delegateDeserializer = findDeserializer(ctxt, delegateType, null);
+ } else if (_valueInstantiator.canCreateUsingArrayDelegate()) {
+ JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
+ if (delegateType == null) {
+ ctxt.reportBadDefinition(_containerType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'",
+ _containerType,
+ _valueInstantiator.getClass().getName()));
+ }
+ _delegateDeserializer = findDeserializer(ctxt, delegateType, null);
+ } else if (_valueInstantiator.canCreateFromObjectWith()) {
+ SettableBeanProperty[] creatorProps = _valueInstantiator.getFromObjectArguments(ctxt.getConfig());
+ _propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, creatorProps,
+ ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ }
+ }
+ }
+
/**
* Method called to finalize setup of this deserializer,
* when it is known for which property deserializer is needed for.
@@ -69,24 +170,24 @@
// note: instead of finding key deserializer, with enums we actually
// work with regular deserializers (less code duplication; but not
// quite as clean as it ought to be)
- KeyDeserializer kd = _keyDeserializer;
- if (kd == null) {
- kd = ctxt.findKeyDeserializer(_mapType.getKeyType(), property);
+ KeyDeserializer keyDeser = _keyDeserializer;
+ if (keyDeser == null) {
+ keyDeser = ctxt.findKeyDeserializer(_containerType.getKeyType(), property);
}
- JsonDeserializer<?> vd = _valueDeserializer;
- final JavaType vt = _mapType.getContentType();
- if (vd == null) {
- vd = ctxt.findContextualValueDeserializer(vt, property);
+ JsonDeserializer<?> valueDeser = _valueDeserializer;
+ final JavaType vt = _containerType.getContentType();
+ if (valueDeser == null) {
+ valueDeser = ctxt.findContextualValueDeserializer(vt, property);
} else { // if directly assigned, probably not yet contextual, so:
- vd = ctxt.handleSecondaryContextualization(vd, property, vt);
+ valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt);
}
TypeDeserializer vtd = _valueTypeDeserializer;
if (vtd != null) {
vtd = vtd.forProperty(property);
}
- return withResolved(kd, vd, vtd);
+ return withResolved(keyDeser, valueDeser, vtd, findContentNullProvider(ctxt, property, valueDeser));
}
-
+
/**
* Because of costs associated with constructing Enum resolvers,
* let's cache instances by default.
@@ -106,15 +207,16 @@
*/
@Override
- public JavaType getContentType() {
- return _mapType.getContentType();
- }
-
- @Override
public JsonDeserializer<Object> getContentDeserializer() {
return _valueDeserializer;
}
+ // Must override since we do not expose ValueInstantiator
+ @Override // since 2.9
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return constructMap(ctxt);
+ }
+
/*
/**********************************************************
/* Actual deserialization
@@ -125,49 +227,85 @@
public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
+ if (_propertyBasedCreator != null) {
+ return _deserializeUsingProperties(p, ctxt);
+ }
+ if (_delegateDeserializer != null) {
+ return (EnumMap<?,?>) _valueInstantiator.createUsingDelegate(ctxt,
+ _delegateDeserializer.deserialize(p, ctxt));
+ }
// Ok: must point to START_OBJECT
- if (p.getCurrentToken() != JsonToken.START_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 (EnumMap<?,?>) _valueInstantiator.createFromString(ctxt, p.getText());
+ }
+ // slightly redundant (since String was passed above), but also handles empty array case:
return _deserializeFromEmpty(p, ctxt);
}
- EnumMap result = constructMap();
+ EnumMap result = constructMap(ctxt);
+ return deserialize(p, ctxt, result);
+ }
+
+ @Override
+ public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt,
+ EnumMap result)
+ throws IOException
+ {
+ // [databind#631]: Assign current value, to be accessible by custom deserializers
+ p.setCurrentValue(result);
+
final JsonDeserializer<Object> valueDes = _valueDeserializer;
final TypeDeserializer typeDeser = _valueTypeDeserializer;
- while ((p.nextToken()) == JsonToken.FIELD_NAME) {
- String keyName = p.getCurrentName(); // just for error message
+ String keyStr;
+ if (p.isExpectedStartObjectToken()) {
+ keyStr = p.nextFieldName();
+ } else {
+ JsonToken t = p.getCurrentToken();
+ if (t != JsonToken.FIELD_NAME) {
+ if (t == JsonToken.END_OBJECT) {
+ return result;
+ }
+ ctxt.reportWrongTokenException(this, JsonToken.FIELD_NAME, null);
+ }
+ keyStr = p.getCurrentName();
+ }
+
+ for (; keyStr != null; keyStr = p.nextFieldName()) {
// but we need to let key deserializer handle it separately, nonetheless
- Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyName, ctxt);
+ Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyStr, ctxt);
+ JsonToken t = p.nextToken();
if (key == null) {
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
- return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyName,
+ return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyStr,
"value not one of declared Enum instance names for %s",
- _mapType.getKeyType());
+ _containerType.getKeyType());
}
- /* 24-Mar-2012, tatu: Null won't work as a key anyway, so let's
- * just skip the entry then. But we must skip the value as well, if so.
- */
- p.nextToken();
+ // 24-Mar-2012, tatu: Null won't work as a key anyway, so let's
+ // just skip the entry then. But we must skip the value as well, if so.
p.skipChildren();
continue;
}
// And then the value...
- JsonToken t = p.nextToken();
- /* note: MUST check for nulls separately: deserializers will
- * not handle them (and maybe fail or return bogus data)
- */
+ // note: MUST check for nulls separately: deserializers will
+ // not handle them (and maybe fail or return bogus data)
Object value;
try {
if (t == JsonToken.VALUE_NULL) {
- value = valueDes.getNullValue(ctxt);
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
} catch (Exception e) {
- wrapAndThrow(e, result, keyName);
- return null;
+ return wrapAndThrow(e, result, keyStr);
}
result.put(key, value);
}
@@ -175,15 +313,104 @@
}
@Override
- public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
- throws IOException, JsonProcessingException
+ 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.deserializeTypedFromObject(jp, ctxt);
+ return typeDeserializer.deserializeTypedFromObject(p, ctxt);
}
-
- protected EnumMap<?,?> constructMap() {
- return new EnumMap(_enumClass);
+
+ protected EnumMap<?,?> constructMap(DeserializationContext ctxt) throws JsonMappingException {
+ if (_valueInstantiator == null) {
+ return new EnumMap(_enumClass);
+ }
+ try {
+ if (!_valueInstantiator.canCreateUsingDefault()) {
+ return (EnumMap<?,?>) ctxt.handleMissingInstantiator(handledType(),
+ getValueInstantiator(), null,
+ "no default constructor found");
+ }
+ return (EnumMap<?,?>) _valueInstantiator.createUsingDefault(ctxt);
+ } catch (IOException e) {
+ return ClassUtil.throwAsMappingException(ctxt, e);
+ }
+ }
+
+ public EnumMap<?,?> _deserializeUsingProperties(JsonParser p, DeserializationContext ctxt) throws IOException
+ {
+ final PropertyBasedCreator creator = _propertyBasedCreator;
+ // null -> no ObjectIdReader for EnumMaps
+ PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, null);
+
+ String keyName;
+ if (p.isExpectedStartObjectToken()) {
+ keyName = p.nextFieldName();
+ } else if (p.hasToken(JsonToken.FIELD_NAME)) {
+ keyName = p.getCurrentName();
+ } else {
+ keyName = null;
+ }
+
+ for (; keyName != null; keyName = p.nextFieldName()) {
+ JsonToken t = p.nextToken(); // to get to value
+ // creator property?
+ SettableBeanProperty prop = creator.findCreatorProperty(keyName);
+ if (prop != null) {
+ // Last property to set?
+ if (buffer.assignParameter(prop, prop.deserialize(p, ctxt))) {
+ p.nextToken(); // from value to END_OBJECT or FIELD_NAME
+ EnumMap<?,?> result;
+ try {
+ result = (EnumMap<?,?>)creator.build(ctxt, buffer);
+ } catch (Exception e) {
+ return wrapAndThrow(e, _containerType.getRawClass(), keyName);
+ }
+ return deserialize(p, ctxt, result);
+ }
+ continue;
+ }
+ // other property? needs buffering
+ // but we need to let key deserializer handle it separately, nonetheless
+ Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyName, ctxt);
+ if (key == null) {
+ if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
+ return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyName,
+ "value not one of declared Enum instance names for %s",
+ _containerType.getKeyType());
+ }
+ // 24-Mar-2012, tatu: Null won't work as a key anyway, so let's
+ // just skip the entry then. But we must skip the value as well, if so.
+ p.nextToken();
+ p.skipChildren();
+ continue;
+ }
+ Object value;
+
+ try {
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ } else if (_valueTypeDeserializer == null) {
+ value = _valueDeserializer.deserialize(p, ctxt);
+ } else {
+ value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ }
+ } catch (Exception e) {
+ wrapAndThrow(e, _containerType.getRawClass(), keyName);
+ return null;
+ }
+ buffer.bufferMapProperty(key, value);
+ }
+ // end of JSON object?
+ // if so, can just construct and leave...
+ try {
+ return (EnumMap<?,?>)creator.build(ctxt, buffer);
+ } catch (Exception e) {
+ wrapAndThrow(e, _containerType.getRawClass(), keyName);
+ return null;
+ }
}
}
-
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 2136210..08ceee8 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
@@ -63,7 +63,7 @@
@SuppressWarnings("unchecked" )
protected EnumSetDeserializer(EnumSetDeserializer base,
JsonDeserializer<?> deser, Boolean unwrapSingle) {
- super(EnumSet.class);
+ super(base);
_enumType = base._enumType;
_enumClass = base._enumClass;
_enumDeserializer = (JsonDeserializer<Enum<?>>) deser;
@@ -96,7 +96,12 @@
}
return true;
}
-
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return Boolean.TRUE;
+ }
+
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
@@ -118,15 +123,32 @@
/**********************************************************
*/
- @SuppressWarnings("unchecked")
@Override
public EnumSet<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
+ EnumSet result = constructSet();
// Ok: must point to START_ARRAY (or equivalent)
if (!p.isExpectedStartArrayToken()) {
- return handleNonArray(p, ctxt);
+ return handleNonArray(p, ctxt, result);
}
- EnumSet result = constructSet();
+ return _deserialize(p, ctxt, result);
+ }
+
+ @Override
+ public EnumSet<?> deserialize(JsonParser p, DeserializationContext ctxt,
+ EnumSet<?> result) throws IOException
+ {
+ // Ok: must point to START_ARRAY (or equivalent)
+ if (!p.isExpectedStartArrayToken()) {
+ return handleNonArray(p, ctxt, result);
+ }
+ return _deserialize(p, ctxt, result);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected final EnumSet<?> _deserialize(JsonParser p, DeserializationContext ctxt,
+ EnumSet result) throws IOException
+ {
JsonToken t;
try {
@@ -164,12 +186,12 @@
@SuppressWarnings("unchecked")
private EnumSet constructSet()
{
- // superbly ugly... but apparently necessary
return EnumSet.noneOf(_enumClass);
}
@SuppressWarnings("unchecked")
- protected EnumSet<?> handleNonArray(JsonParser p, DeserializationContext ctxt)
+ protected EnumSet<?> handleNonArray(JsonParser p, DeserializationContext ctxt,
+ EnumSet result)
throws IOException
{
boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
@@ -179,8 +201,6 @@
if (!canWrap) {
return (EnumSet<?>) ctxt.handleUnexpectedToken(EnumSet.class, p);
}
-
- EnumSet result = constructSet();
// First: since `null`s not allowed, slightly simpler...
if (p.hasToken(JsonToken.VALUE_NULL)) {
return (EnumSet<?>) ctxt.handleUnexpectedToken(_enumClass, p);
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 a6839f2..86a68d9 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
@@ -1,17 +1,12 @@
package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
-import com.fasterxml.jackson.databind.BeanProperty;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonMappingException;
+
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
@@ -99,6 +94,15 @@
return this;
}
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return Boolean.FALSE;
+ }
+
+ // since 2.9.7: should have been the case earlier but
+ @Override
+ public boolean isCachable() { return true; }
+
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -113,7 +117,8 @@
value = p.getText();
} else if ((_creatorProps != null) && p.isExpectedStartObjectToken()) {
if (_propCreator == null) {
- _propCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps);
+ _propCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps,
+ ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
}
p.nextToken();
return deserializeEnumUsingPropertyBased(p, ctxt, _propCreator);
@@ -133,9 +138,9 @@
return _factory.callOnWith(_valueClass, value);
} catch (Exception e) {
Throwable t = ClassUtil.throwRootCauseIfIOE(e);
- // [databind#1642]
- if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
- && (t instanceof IllegalArgumentException)) {
+ // [databind#1642]:
+ if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL) &&
+ t instanceof IllegalArgumentException) {
return null;
}
return ctxt.handleInstantiationProblem(_valueClass, value, t);
@@ -176,43 +181,35 @@
// ************ Got the below methods from BeanDeserializer ********************//
protected final Object _deserializeWithErrorWrapping(JsonParser p, DeserializationContext ctxt,
- SettableBeanProperty prop) throws IOException {
- try {
- return prop.deserialize(p, ctxt);
- } catch (Exception e) {
- wrapAndThrow(e, _valueClass.getClass(), prop.getName(), ctxt);
- // never gets here, unless caller declines to throw an exception
- return null;
- }
+ SettableBeanProperty prop) throws IOException
+ {
+ try {
+ return prop.deserialize(p, ctxt);
+ } catch (Exception e) {
+ return wrapAndThrow(e, handledType(), prop.getName(), ctxt);
+ }
}
- public void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
- throws IOException
+ protected Object wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
+ throws IOException
{
throw JsonMappingException.wrapWithPath(throwOrReturnThrowable(t, ctxt), bean, fieldName);
}
private Throwable throwOrReturnThrowable(Throwable t, DeserializationContext ctxt) throws IOException
{
- while (t instanceof InvocationTargetException && t.getCause() != null) {
- t = t.getCause();
- }
+ t = ClassUtil.getRootCause(t);
// Errors to be passed as is
- if (t instanceof Error) {
- throw (Error) t;
- }
+ ClassUtil.throwIfError(t);
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
- // Ditto for IOExceptions; except we may want to wrap JSON
- // exceptions
- if (t instanceof IOException) {
- if (!wrap || !(t instanceof JsonProcessingException)) {
- throw (IOException) t;
- }
- } else if (!wrap) {
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
- }
- return t;
+ // Ditto for IOExceptions; except we may want to wrap JSON exceptions
+ if (t instanceof IOException) {
+ if (!wrap || !(t instanceof JsonProcessingException)) {
+ throw (IOException) t;
+ }
+ } else if (!wrap) {
+ ClassUtil.throwIfRTE(t);
+ }
+ return t;
}
-}
\ No newline at end of file
+}
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 0d01e78..68187c1 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
@@ -14,15 +14,41 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.util.VersionUtil;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
- * Base class for simple deserializers that only accept JSON String
- * values as the source.
+ * Base class for simple deserializers that serialize values from String
+ * representation: this includes JSON Strings and other Scalar values that
+ * can be coerced into text, like Numbers and Booleans).
+ * Simple JSON String values are trimmed using {@link java.lang.String#trim}.
+ * Partial deserializer implementation will try to first access current token as
+ * a String, calls {@link #_deserialize(String,DeserializationContext)} and
+ * returns return value.
+ * If this does not work (current token not a simple scalar type), attempts
+ * are made so that:
+ *<ul>
+ * <li>Embedded values ({@link JsonToken#VALUE_EMBEDDED_OBJECT}) are returned as-is
+ * if they are of compatible type
+ * </li>
+ * <li>Arrays may be "unwrapped" if (and only if) {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}
+ * is enabled, and array contains just a single scalar value that can be deserialized
+ * (for example, JSON Array with single JSON String element).
+ * </li>
+ * </ul>
+ *<p>
+ * Special handling includes:
+ * <ul>
+ * <li>Null values ({@link JsonToken#VALUE_NULL}) are handled by returning value
+ * returned by {@link JsonDeserializer#getNullValue(DeserializationContext)}: default
+ * implementation simply returns Java `null` but this may be overridden.
+ * </li>
+ * <li>Empty String (after trimming) will result in {@link #_deserializeFromEmptyString}
+ * getting called, and return value being returned as deserialization: default implementation
+ * simply returns `null`.
+ * </li>
+ * </ul>
*/
@SuppressWarnings("serial")
public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
@@ -99,16 +125,16 @@
/* Deserializer implementations
/**********************************************************
*/
-
+
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- // 22-Sep-2012, tatu: For 2.1, use this new method, may force coercion:
+ // 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) {
- // 04-Feb-2013, tatu: Usually should become null; but not always
+ // Usually should become null; but not always
return _deserializeFromEmptyString();
}
Exception cause = null;
@@ -117,23 +143,18 @@
// indicated error; but that seems wrong. Should be able to return
// `null` as value.
return _deserialize(text, ctxt);
- } catch (IllegalArgumentException iae) {
- cause = iae;
- } catch (MalformedURLException me) {
- cause = me;
+ } catch (IllegalArgumentException | MalformedURLException e) {
+ cause = e;
}
+ // note: `cause` can't be null
String msg = "not a valid textual representation";
- if (cause != null) {
- String m2 = cause.getMessage();
- if (m2 != null) {
- msg = msg + ", problem: "+m2;
- }
+ 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);
- if (cause != null) {
- e.initCause(cause);
- }
+ e.initCause(cause);
throw e;
// nothing to do here, yet? We'll fail anyway
}
@@ -160,7 +181,8 @@
protected T _deserializeEmbedded(Object ob, DeserializationContext ctxt) throws IOException {
// default impl: error out
- ctxt.reportMappingException("Don't know how to convert embedded Object of type %s into %s",
+ ctxt.reportInputMismatch(this,
+ "Don't know how to convert embedded Object of type %s into %s",
ob.getClass().getName(), _valueClass.getName());
return null;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java
index c4c0ed9..7743dea 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java
@@ -19,7 +19,8 @@
UUID.class,
AtomicBoolean.class,
StackTraceElement.class,
- ByteBuffer.class
+ ByteBuffer.class,
+ Void.class
};
for (Class<?> cls : types) { _classNames.add(cls.getName()); }
for (Class<?> cls : FromStringDeserializer.types()) { _classNames.add(cls.getName()); }
@@ -45,6 +46,9 @@
if (rawType == ByteBuffer.class) {
return new ByteBufferDeserializer();
}
+ if (rawType == Void.class) {
+ return NullifyingDeserializer.instance;
+ }
}
return null;
}
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 05b0562..01937fe 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
@@ -22,7 +22,12 @@
*/
private final static JsonNodeDeserializer instance = new JsonNodeDeserializer();
- protected JsonNodeDeserializer() { super(JsonNode.class); }
+ protected JsonNodeDeserializer() {
+ // `null` means that explicit "merge" is honored and may or may not work, but
+ // that per-type and global defaults do not enable merging. This because
+ // some node types (Object, Array) do support, others don't.
+ super(JsonNode.class, null);
+ }
/**
* Factory method for accessing deserializer for specific node type
@@ -50,12 +55,6 @@
return NullNode.getInstance();
}
- @Override
- @Deprecated // since 2.6, remove from 2.7
- public JsonNode getNullValue() {
- return NullNode.getInstance();
- }
-
/**
* Implementation that will produce types of any JSON nodes; not just one
* deserializer is registered to handle (in case of more specialized handler).
@@ -70,8 +69,8 @@
case JsonTokenId.ID_START_ARRAY:
return deserializeArray(p, ctxt, ctxt.getNodeFactory());
default:
- return deserializeAny(p, ctxt, ctxt.getNodeFactory());
}
+ return deserializeAny(p, ctxt, ctxt.getNodeFactory());
}
/*
@@ -87,16 +86,19 @@
protected final static ObjectDeserializer _instance = new ObjectDeserializer();
- protected ObjectDeserializer() { super(ObjectNode.class); }
+ protected ObjectDeserializer() { super(ObjectNode.class, true); }
public static ObjectDeserializer getInstance() { return _instance; }
-
+
@Override
public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (p.isExpectedStartObjectToken() || p.hasToken(JsonToken.FIELD_NAME)) {
+ if (p.isExpectedStartObjectToken()) {
return deserializeObject(p, ctxt, ctxt.getNodeFactory());
}
+ if (p.hasToken(JsonToken.FIELD_NAME)) {
+ return deserializeObjectAtName(p, ctxt, ctxt.getNodeFactory());
+ }
// 23-Sep-2015, tatu: Ugh. We may also be given END_OBJECT (similar to FIELD_NAME),
// if caller has advanced to the first token of Object, but for empty Object
if (p.hasToken(JsonToken.END_OBJECT)) {
@@ -104,8 +106,23 @@
}
return (ObjectNode) ctxt.handleUnexpectedToken(ObjectNode.class, p);
}
+
+ /**
+ * Variant needed to support both root-level `updateValue()` and merging.
+ *
+ * @since 2.9
+ */
+ @Override
+ public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt,
+ ObjectNode node) throws IOException
+ {
+ if (p.isExpectedStartObjectToken() || p.hasToken(JsonToken.FIELD_NAME)) {
+ return (ObjectNode) updateObject(p, ctxt, (ObjectNode) node);
+ }
+ return (ObjectNode) ctxt.handleUnexpectedToken(ObjectNode.class, p);
+ }
}
-
+
final static class ArrayDeserializer
extends BaseNodeDeserializer<ArrayNode>
{
@@ -113,10 +130,10 @@
protected final static ArrayDeserializer _instance = new ArrayDeserializer();
- protected ArrayDeserializer() { super(ArrayNode.class); }
+ protected ArrayDeserializer() { super(ArrayNode.class, true); }
public static ArrayDeserializer getInstance() { return _instance; }
-
+
@Override
public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -125,6 +142,21 @@
}
return (ArrayNode) ctxt.handleUnexpectedToken(ArrayNode.class, p);
}
+
+ /**
+ * Variant needed to support both root-level `updateValue()` and merging.
+ *
+ * @since 2.9
+ */
+ @Override
+ public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt,
+ ArrayNode node) throws IOException
+ {
+ if (p.isExpectedStartArrayToken()) {
+ return (ArrayNode) updateArray(p, ctxt, (ArrayNode) node);
+ }
+ return (ArrayNode) ctxt.handleUnexpectedToken(ArrayNode.class, p);
+ }
}
}
@@ -136,8 +168,11 @@
abstract class BaseNodeDeserializer<T extends JsonNode>
extends StdDeserializer<T>
{
- public BaseNodeDeserializer(Class<T> vc) {
+ protected final Boolean _supportsUpdates;
+
+ public BaseNodeDeserializer(Class<T> vc, Boolean supportsUpdates) {
super(vc);
+ _supportsUpdates = supportsUpdates;
}
@Override
@@ -145,9 +180,7 @@
TypeDeserializer typeDeserializer)
throws IOException
{
- /* Output can be as JSON Object, Array or scalar: no way to know
- * a priori. So:
- */
+ // Output can be as JSON Object, Array or scalar: no way to know a priori:
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
}
@@ -158,17 +191,17 @@
@Override
public boolean isCachable() { return true; }
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return _supportsUpdates;
+ }
+
/*
/**********************************************************
/* Overridable methods
/**********************************************************
*/
- @Deprecated // since 2.8
- protected void _reportProblem(JsonParser p, String msg) throws JsonMappingException {
- throw JsonMappingException.from(p, msg);
- }
-
/**
* Method called when there is a duplicate value for a field.
* By default we don't care, and the last value is used.
@@ -190,7 +223,8 @@
{
// [databind#237]: Report an error if asked to do so:
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)) {
- ctxt.reportMappingException("Duplicate field '%s' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled",
+ ctxt.reportInputMismatch(JsonNode.class,
+ "Duplicate field '%s' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled",
fieldName);
}
}
@@ -201,28 +235,20 @@
/**********************************************************
*/
+ /**
+ * Method called to deserialize Object node instance when there is no existing
+ * node to modify.
+ */
protected final ObjectNode deserializeObject(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
- ObjectNode node = nodeFactory.objectNode();
- String key;
- if (p.isExpectedStartObjectToken()) {
- key = p.nextFieldName();
- } else {
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.END_OBJECT) {
- return node;
- }
- if (t != JsonToken.FIELD_NAME) {
- return (ObjectNode) ctxt.handleUnexpectedToken(handledType(), p);
- }
- key = p.getCurrentName();
- }
+ final ObjectNode node = nodeFactory.objectNode();
+ String key = p.nextFieldName();
for (; key != null; key = p.nextFieldName()) {
JsonNode value;
JsonToken t = p.nextToken();
- if (t == null) {
- throw ctxt.mappingException("Unexpected end-of-input when binding data into ObjectNode");
+ if (t == null) { // can this ever occur?
+ t = JsonToken.NOT_AVAILABLE; // can this ever occur?
}
switch (t.id()) {
case JsonTokenId.ID_START_OBJECT:
@@ -261,6 +287,142 @@
return node;
}
+ /**
+ * Alternate deserialization method used when parser already points to first
+ * FIELD_NAME and not START_OBJECT.
+ *
+ * @since 2.9
+ */
+ protected final ObjectNode deserializeObjectAtName(JsonParser p, DeserializationContext ctxt,
+ final JsonNodeFactory nodeFactory) throws IOException
+ {
+ final ObjectNode node = nodeFactory.objectNode();
+ String key = p.getCurrentName();
+ for (; key != null; key = p.nextFieldName()) {
+ JsonNode value;
+ JsonToken t = p.nextToken();
+ if (t == null) { // can this ever occur?
+ t = JsonToken.NOT_AVAILABLE; // can this ever occur?
+ }
+ switch (t.id()) {
+ case JsonTokenId.ID_START_OBJECT:
+ value = deserializeObject(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ value = deserializeArray(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_EMBEDDED_OBJECT:
+ value = _fromEmbedded(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_STRING:
+ value = nodeFactory.textNode(p.getText());
+ break;
+ case JsonTokenId.ID_NUMBER_INT:
+ value = _fromInt(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_TRUE:
+ value = nodeFactory.booleanNode(true);
+ break;
+ case JsonTokenId.ID_FALSE:
+ value = nodeFactory.booleanNode(false);
+ break;
+ case JsonTokenId.ID_NULL:
+ value = nodeFactory.nullNode();
+ break;
+ default:
+ value = deserializeAny(p, ctxt, nodeFactory);
+ }
+ JsonNode old = node.replace(key, value);
+ if (old != null) {
+ _handleDuplicateField(p, ctxt, nodeFactory,
+ key, node, old, value);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * Alternate deserialization method that is to update existing {@link ObjectNode}
+ * if possible.
+ *
+ * @since 2.9
+ */
+ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
+ final ObjectNode node) throws IOException
+ {
+ String key;
+ if (p.isExpectedStartObjectToken()) {
+ key = p.nextFieldName();
+ } else {
+ if (!p.hasToken(JsonToken.FIELD_NAME)) {
+ return deserialize(p, ctxt);
+ }
+ key = p.getCurrentName();
+ }
+ for (; key != null; key = p.nextFieldName()) {
+ // If not, fall through to regular handling
+ JsonToken t = p.nextToken();
+
+ // First: see if we can merge things:
+ JsonNode old = node.get(key);
+ if (old != null) {
+ if (old instanceof ObjectNode) {
+ JsonNode newValue = updateObject(p, ctxt, (ObjectNode) old);
+ if (newValue != old) {
+ node.set(key, newValue);
+ }
+ continue;
+ }
+ if (old instanceof ArrayNode) {
+ JsonNode newValue = updateArray(p, ctxt, (ArrayNode) old);
+ if (newValue != old) {
+ node.set(key, newValue);
+ }
+ continue;
+ }
+ }
+ if (t == null) { // can this ever occur?
+ t = JsonToken.NOT_AVAILABLE;
+ }
+ JsonNode value;
+ JsonNodeFactory nodeFactory = ctxt.getNodeFactory();
+ switch (t.id()) {
+ case JsonTokenId.ID_START_OBJECT:
+ value = deserializeObject(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ value = deserializeArray(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_EMBEDDED_OBJECT:
+ value = _fromEmbedded(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_STRING:
+ value = nodeFactory.textNode(p.getText());
+ break;
+ case JsonTokenId.ID_NUMBER_INT:
+ value = _fromInt(p, ctxt, nodeFactory);
+ break;
+ case JsonTokenId.ID_TRUE:
+ value = nodeFactory.booleanNode(true);
+ break;
+ case JsonTokenId.ID_FALSE:
+ value = nodeFactory.booleanNode(false);
+ break;
+ case JsonTokenId.ID_NULL:
+ value = nodeFactory.nullNode();
+ break;
+ default:
+ value = deserializeAny(p, ctxt, nodeFactory);
+ }
+ if (old != null) {
+ _handleDuplicateField(p, ctxt, nodeFactory,
+ key, node, old, value);
+ }
+ node.set(key, value);
+ }
+ return node;
+ }
+
protected final ArrayNode deserializeArray(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
@@ -301,16 +463,60 @@
}
}
+ /**
+ * Alternate deserialization method that is to update existing {@link ObjectNode}
+ * if possible.
+ *
+ * @since 2.9
+ */
+ protected final JsonNode updateArray(JsonParser p, DeserializationContext ctxt,
+ final ArrayNode node) throws IOException
+ {
+ final JsonNodeFactory nodeFactory = ctxt.getNodeFactory();
+ while (true) {
+ JsonToken t = p.nextToken();
+ switch (t.id()) {
+ case JsonTokenId.ID_START_OBJECT:
+ node.add(deserializeObject(p, ctxt, nodeFactory));
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ node.add(deserializeArray(p, ctxt, nodeFactory));
+ break;
+ case JsonTokenId.ID_END_ARRAY:
+ return node;
+ case JsonTokenId.ID_EMBEDDED_OBJECT:
+ node.add(_fromEmbedded(p, ctxt, nodeFactory));
+ break;
+ case JsonTokenId.ID_STRING:
+ node.add(nodeFactory.textNode(p.getText()));
+ break;
+ case JsonTokenId.ID_NUMBER_INT:
+ node.add(_fromInt(p, ctxt, nodeFactory));
+ break;
+ case JsonTokenId.ID_TRUE:
+ node.add(nodeFactory.booleanNode(true));
+ break;
+ case JsonTokenId.ID_FALSE:
+ node.add(nodeFactory.booleanNode(false));
+ break;
+ case JsonTokenId.ID_NULL:
+ node.add(nodeFactory.nullNode());
+ break;
+ default:
+ node.add(deserializeAny(p, ctxt, nodeFactory));
+ break;
+ }
+ }
+ }
+
protected final JsonNode deserializeAny(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
switch (p.getCurrentTokenId()) {
- case JsonTokenId.ID_START_OBJECT:
- case JsonTokenId.ID_END_OBJECT: // for empty JSON Objects we may point to this
+ case JsonTokenId.ID_END_OBJECT: // for empty JSON Objects we may point to this?
+ return nodeFactory.objectNode();
case JsonTokenId.ID_FIELD_NAME:
- return deserializeObject(p, ctxt, nodeFactory);
- case JsonTokenId.ID_START_ARRAY:
- return deserializeArray(p, ctxt, nodeFactory);
+ return deserializeObjectAtName(p, ctxt, nodeFactory);
case JsonTokenId.ID_EMBEDDED_OBJECT:
return _fromEmbedded(p, ctxt, nodeFactory);
case JsonTokenId.ID_STRING:
@@ -325,8 +531,16 @@
return nodeFactory.booleanNode(false);
case JsonTokenId.ID_NULL:
return nodeFactory.nullNode();
+
+ /* Caller checks for these, should not get here ever
+ case JsonTokenId.ID_START_OBJECT:
+ return deserializeObject(p, ctxt, nodeFactory);
+ case JsonTokenId.ID_START_ARRAY:
+ return deserializeArray(p, ctxt, nodeFactory);
+ */
+
- // These states can not be mapped; input stream is
+ // These states cannot be mapped; input stream is
// off by an event or two
//case END_OBJECT:
@@ -371,12 +585,8 @@
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
// 20-May-2016, tatu: As per [databind#1028], need to be careful
// (note: JDK 1.8 would have `Double.isFinite()`)
- // 21-Aug-2016, tatu: Not optimal, really, because this may result in
- // value getting parsed twice. But has to do for now, to resolve
- // [databind#1315]
- double d = p.getDoubleValue();
- if (Double.isInfinite(d) || Double.isNaN(d)) {
- return nodeFactory.numberNode(d);
+ if (p.isNaN()) {
+ return nodeFactory.numberNode(p.getDoubleValue());
}
return nodeFactory.numberNode(p.getDecimalValue());
}
@@ -402,7 +612,7 @@
return nodeFactory.rawValueNode((RawValue) ob);
}
if (ob instanceof JsonNode) {
- // [Issue#433]: but could also be a JsonNode hiding in there!
+ // [databind#433]: but could also be a JsonNode hiding in there!
return (JsonNode) ob;
}
// any other special handling needed?
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 c1d8719..5e4bb35 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
@@ -4,7 +4,9 @@
import java.util.*;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.*;
@@ -33,8 +35,6 @@
// // Configuration: typing, deserializers
- protected final JavaType _mapType;
-
/**
* Key deserializer to use; either passed via constructor
* (when indicated by annotations), or resolved when
@@ -61,13 +61,11 @@
* is the type deserializer that can handle it
*/
protected final TypeDeserializer _valueTypeDeserializer;
-
+
// // Instance construction settings:
protected final ValueInstantiator _valueInstantiator;
- protected final boolean _hasDefaultCreator;
-
/**
* Deserializer that is used iff delegate-based creator is
* to be used for deserializing from JSON Object.
@@ -82,8 +80,10 @@
*/
protected PropertyBasedCreator _propertyBasedCreator;
+ protected final boolean _hasDefaultCreator;
+
// // Any properties to ignore if seen?
-
+
protected Set<String> _ignorableProperties;
/*
@@ -96,8 +96,7 @@
KeyDeserializer keyDeser, JsonDeserializer<Object> valueDeser,
TypeDeserializer valueTypeDeser)
{
- super(mapType);
- _mapType = mapType;
+ super(mapType, null, null);
_keyDeserializer = keyDeser;
_valueDeserializer = valueDeser;
_valueTypeDeserializer = valueTypeDeser;
@@ -114,8 +113,7 @@
*/
protected MapDeserializer(MapDeserializer src)
{
- super(src._mapType);
- _mapType = src._mapType;
+ super(src);
_keyDeserializer = src._keyDeserializer;
_valueDeserializer = src._valueDeserializer;
_valueTypeDeserializer = src._valueTypeDeserializer;
@@ -132,10 +130,10 @@
protected MapDeserializer(MapDeserializer src,
KeyDeserializer keyDeser, JsonDeserializer<Object> valueDeser,
TypeDeserializer valueTypeDeser,
+ NullValueProvider nuller,
Set<String> ignorable)
{
- super(src._mapType);
- _mapType = src._mapType;
+ super(src, nuller, src._unwrapSingle);
_keyDeserializer = keyDeser;
_valueDeserializer = valueDeser;
_valueTypeDeserializer = valueTypeDeser;
@@ -145,7 +143,7 @@
_hasDefaultCreator = src._hasDefaultCreator;
_ignorableProperties = ignorable;
- _standardStringKey = _isStdKeyDeser(_mapType, keyDeser);
+ _standardStringKey = _isStdKeyDeser(_containerType, keyDeser);
}
/**
@@ -155,15 +153,18 @@
@SuppressWarnings("unchecked")
protected MapDeserializer withResolved(KeyDeserializer keyDeser,
TypeDeserializer valueTypeDeser, JsonDeserializer<?> valueDeser,
+ NullValueProvider nuller,
Set<String> ignorable)
{
if ((_keyDeserializer == keyDeser) && (_valueDeserializer == valueDeser)
- && (_valueTypeDeserializer == valueTypeDeser) && (_ignorableProperties == ignorable)) {
+ && (_valueTypeDeserializer == valueTypeDeser) && (_nullProvider == nuller)
+ && (_ignorableProperties == ignorable)) {
return this;
}
return new MapDeserializer(this,
- keyDeser, (JsonDeserializer<Object>) valueDeser, valueTypeDeser, ignorable);
+ keyDeser, (JsonDeserializer<Object>) valueDeser, valueTypeDeser,
+ nuller, ignorable);
}
/**
@@ -204,34 +205,34 @@
public void resolve(DeserializationContext ctxt) throws JsonMappingException
{
// May need to resolve types for delegate- and/or property-based creators:
- if (_valueInstantiator != null) {
- if (_valueInstantiator.canCreateUsingDelegate()) {
- JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
- if (delegateType == null) {
- throw new IllegalArgumentException("Invalid delegate-creator definition for "+_mapType
- +": value instantiator ("+_valueInstantiator.getClass().getName()
- +") returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'");
- }
- /* Theoretically should be able to get CreatorProperty for delegate
- * parameter to pass; but things get tricky because DelegateCreator
- * may contain injectable values. So, for now, let's pass nothing.
- */
- _delegateDeserializer = findDeserializer(ctxt, delegateType, null);
- } else if (_valueInstantiator.canCreateUsingArrayDelegate()) {
- JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
- if (delegateType == null) {
- throw new IllegalArgumentException("Invalid delegate-creator definition for "+_mapType
- +": value instantiator ("+_valueInstantiator.getClass().getName()
- +") returned true for 'canCreateUsingDelegate()', but null for 'getArrayDelegateType()'");
- }
- _delegateDeserializer = findDeserializer(ctxt, delegateType, null);
+ if (_valueInstantiator.canCreateUsingDelegate()) {
+ JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
+ if (delegateType == null) {
+ ctxt.reportBadDefinition(_containerType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'",
+ _containerType,
+ _valueInstantiator.getClass().getName()));
}
+ // Theoretically should be able to get CreatorProperty for delegate
+ // parameter to pass; but things get tricky because DelegateCreator
+ // may contain injectable values. So, for now, let's pass nothing.
+ _delegateDeserializer = findDeserializer(ctxt, delegateType, null);
+ } else if (_valueInstantiator.canCreateUsingArrayDelegate()) {
+ JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
+ if (delegateType == null) {
+ ctxt.reportBadDefinition(_containerType, String.format(
+"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'",
+ _containerType,
+ _valueInstantiator.getClass().getName()));
+ }
+ _delegateDeserializer = findDeserializer(ctxt, delegateType, null);
}
if (_valueInstantiator.canCreateFromObjectWith()) {
SettableBeanProperty[] creatorProps = _valueInstantiator.getFromObjectArguments(ctxt.getConfig());
- _propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, creatorProps);
+ _propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, creatorProps,
+ ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
}
- _standardStringKey = _isStdKeyDeser(_mapType, _keyDeserializer);
+ _standardStringKey = _isStdKeyDeser(_containerType, _keyDeserializer);
}
/**
@@ -242,25 +243,25 @@
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
- KeyDeserializer kd = _keyDeserializer;
- if (kd == null) {
- kd = ctxt.findKeyDeserializer(_mapType.getKeyType(), property);
+ KeyDeserializer keyDeser = _keyDeserializer;
+ if (keyDeser == null) {
+ keyDeser = ctxt.findKeyDeserializer(_containerType.getKeyType(), property);
} else {
- if (kd instanceof ContextualKeyDeserializer) {
- kd = ((ContextualKeyDeserializer) kd).createContextual(ctxt, property);
+ if (keyDeser instanceof ContextualKeyDeserializer) {
+ keyDeser = ((ContextualKeyDeserializer) keyDeser).createContextual(ctxt, property);
}
}
- JsonDeserializer<?> vd = _valueDeserializer;
+ JsonDeserializer<?> valueDeser = _valueDeserializer;
// [databind#125]: May have a content converter
if (property != null) {
- vd = findConvertingContentDeserializer(ctxt, property, vd);
+ valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser);
}
- final JavaType vt = _mapType.getContentType();
- if (vd == null) {
- vd = ctxt.findContextualValueDeserializer(vt, property);
+ final JavaType vt = _containerType.getContentType();
+ if (valueDeser == null) {
+ valueDeser = ctxt.findContextualValueDeserializer(vt, property);
} else { // if directly assigned, probably not yet contextual, so:
- vd = ctxt.handleSecondaryContextualization(vd, property, vt);
+ valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt);
}
TypeDeserializer vtd = _valueTypeDeserializer;
if (vtd != null) {
@@ -268,7 +269,7 @@
}
Set<String> ignored = _ignorableProperties;
AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
- if (intr != null && property != null) {
+ if (_neitherNull(intr, property)) {
AnnotatedMember member = property.getMember();
if (member != null) {
JsonIgnoreProperties.Value ignorals = intr.findPropertyIgnorals(member);
@@ -283,7 +284,8 @@
}
}
}
- return withResolved(kd, vtd, vd, ignored);
+ return withResolved(keyDeser, vtd, valueDeser,
+ findContentNullProvider(ctxt, property, valueDeser), ignored);
}
/*
@@ -293,15 +295,15 @@
*/
@Override
- public JavaType getContentType() {
- return _mapType.getContentType();
- }
-
- @Override
public JsonDeserializer<Object> getContentDeserializer() {
return _valueDeserializer;
}
-
+
+ @Override
+ public ValueInstantiator getValueInstantiator() {
+ return _valueInstantiator;
+ }
+
/*
/**********************************************************
/* JsonDeserializer API
@@ -323,9 +325,8 @@
*/
@Override
public boolean isCachable() {
- /* As per [databind#735], existence of value or key deserializer (only passed
- * if annotated to use non-standard one) should also prevent caching.
- */
+ // As per [databind#735], existence of value or key deserializer (only passed
+ // if annotated to use non-standard one) should also prevent caching.
return (_valueDeserializer == null)
&& (_keyDeserializer == null)
&& (_valueTypeDeserializer == null)
@@ -344,17 +345,18 @@
_delegateDeserializer.deserialize(p, ctxt));
}
if (!_hasDefaultCreator) {
- return (Map<Object,Object> ) ctxt.handleMissingInstantiator(getMapClass(), p,
+ return (Map<Object,Object> ) ctxt.handleMissingInstantiator(getMapClass(),
+ 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:
+ // (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());
}
- // slightly redundant (since String was passed above), but
+ // slightly redundant (since String was passed above), but also handles empty array case:
return _deserializeFromEmpty(p, ctxt);
}
final Map<Object,Object> result = (Map<Object,Object>) _valueInstantiator.createUsingDefault(ctxt);
@@ -372,7 +374,7 @@
Map<Object,Object> result)
throws IOException
{
- // [databind#631]: Assign current value, to be accessible by custom serializers
+ // [databind#631]: Assign current value, to be accessible by custom deserializers
p.setCurrentValue(result);
// Ok: must point to START_OBJECT or FIELD_NAME
@@ -380,23 +382,24 @@
if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME) {
return (Map<Object,Object>) ctxt.handleUnexpectedToken(getMapClass(), p);
}
+ // 21-Apr-2017, tatu: Need separate methods to do proper merging
if (_standardStringKey) {
- _readAndBindStringKeyMap(p, ctxt, result);
+ _readAndUpdateStringKeyMap(p, ctxt, result);
return result;
}
- _readAndBind(p, ctxt, result);
+ _readAndUpdate(p, ctxt, result);
return result;
}
@Override
- public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
- throws IOException, JsonProcessingException
+ throws IOException
{
// In future could check current token... for now this should be enough:
- return typeDeserializer.deserializeTypedFromObject(jp, ctxt);
+ return typeDeserializer.deserializeTypedFromObject(p, ctxt);
}
-
+
/*
/**********************************************************
/* Other public accessors
@@ -404,13 +407,13 @@
*/
@SuppressWarnings("unchecked")
- public final Class<?> getMapClass() { return (Class<Map<Object,Object>>) _mapType.getRawClass(); }
+ public final Class<?> getMapClass() { return (Class<Map<Object,Object>>) _containerType.getRawClass(); }
- @Override public JavaType getValueType() { return _mapType; }
+ @Override public JavaType getValueType() { return _containerType; }
/*
/**********************************************************
- /* Internal methods
+ /* Internal methods, non-merging deserialization
/**********************************************************
*/
@@ -424,7 +427,8 @@
MapReferringAccumulator referringAccumulator = null;
boolean useObjectId = valueDes.getObjectIdReader() != null;
if (useObjectId) {
- referringAccumulator = new MapReferringAccumulator(_mapType.getContentType().getRawClass(), result);
+ referringAccumulator = new MapReferringAccumulator(_containerType.getContentType().getRawClass(),
+ result);
}
String keyStr;
@@ -432,11 +436,11 @@
keyStr = p.nextFieldName();
} else {
JsonToken t = p.getCurrentToken();
- if (t == JsonToken.END_OBJECT) {
- return;
- }
if (t != JsonToken.FIELD_NAME) {
- ctxt.reportWrongTokenException(p, JsonToken.FIELD_NAME, null);
+ if (t == JsonToken.END_OBJECT) {
+ return;
+ }
+ ctxt.reportWrongTokenException(this, JsonToken.FIELD_NAME, null);
}
keyStr = p.getCurrentName();
}
@@ -453,7 +457,10 @@
// Note: must handle null explicitly here; value deserializers won't
Object value;
if (t == JsonToken.VALUE_NULL) {
- value = valueDes.getNullValue(ctxt);
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
@@ -465,7 +472,7 @@
result.put(key, value);
}
} catch (UnresolvedForwardReference reference) {
- handleUnresolvedReference(p, referringAccumulator, key, reference);
+ handleUnresolvedReference(ctxt, referringAccumulator, key, reference);
} catch (Exception e) {
wrapAndThrow(e, result, keyStr);
}
@@ -485,7 +492,7 @@
MapReferringAccumulator referringAccumulator = null;
boolean useObjectId = (valueDes.getObjectIdReader() != null);
if (useObjectId) {
- referringAccumulator = new MapReferringAccumulator(_mapType.getContentType().getRawClass(), result);
+ referringAccumulator = new MapReferringAccumulator(_containerType.getContentType().getRawClass(), result);
}
String key;
@@ -497,7 +504,7 @@
return;
}
if (t != JsonToken.FIELD_NAME) {
- ctxt.reportWrongTokenException(p, JsonToken.FIELD_NAME, null);
+ ctxt.reportWrongTokenException(this, JsonToken.FIELD_NAME, null);
}
key = p.getCurrentName();
}
@@ -512,7 +519,10 @@
// Note: must handle null explicitly here; value deserializers won't
Object value;
if (t == JsonToken.VALUE_NULL) {
- value = valueDes.getNullValue(ctxt);
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
@@ -524,14 +534,14 @@
result.put(key, value);
}
} catch (UnresolvedForwardReference reference) {
- handleUnresolvedReference(p, referringAccumulator, key, reference);
+ handleUnresolvedReference(ctxt, referringAccumulator, key, reference);
} catch (Exception e) {
wrapAndThrow(e, result, key);
}
}
// 23-Mar-2015, tatu: TODO: verify we got END_OBJECT?
}
-
+
@SuppressWarnings("unchecked")
public Map<Object,Object> _deserializeUsingCreator(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -562,13 +572,12 @@
if (prop != null) {
// Last property to set?
if (buffer.assignParameter(prop, prop.deserialize(p, ctxt))) {
- p.nextToken();
+ p.nextToken(); // from value to END_OBJECT or FIELD_NAME
Map<Object,Object> result;
try {
result = (Map<Object,Object>)creator.build(ctxt, buffer);
} catch (Exception e) {
- wrapAndThrow(e, _mapType.getRawClass(), key);
- return null;
+ return wrapAndThrow(e, _containerType.getRawClass(), key);
}
_readAndBind(p, ctxt, result);
return result;
@@ -581,14 +590,17 @@
try {
if (t == JsonToken.VALUE_NULL) {
- value = valueDes.getNullValue(ctxt);
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
} catch (Exception e) {
- wrapAndThrow(e, _mapType.getRawClass(), key);
+ wrapAndThrow(e, _containerType.getRawClass(), key);
return null;
}
buffer.bufferMapProperty(actualKey, value);
@@ -598,22 +610,156 @@
try {
return (Map<Object,Object>)creator.build(ctxt, buffer);
} catch (Exception e) {
- wrapAndThrow(e, _mapType.getRawClass(), key);
+ wrapAndThrow(e, _containerType.getRawClass(), key);
return null;
}
}
- @Deprecated // since 2.5
- protected void wrapAndThrow(Throwable t, Object ref) throws IOException {
- wrapAndThrow(t, ref, null);
+ /*
+ /**********************************************************
+ /* Internal methods, non-merging deserialization
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt,
+ Map<Object,Object> result) throws IOException
+ {
+ final KeyDeserializer keyDes = _keyDeserializer;
+ final JsonDeserializer<Object> valueDes = _valueDeserializer;
+ final TypeDeserializer typeDeser = _valueTypeDeserializer;
+
+ // Note: assumption is that Object Id handling can't really work with merging
+ // and thereby we can (and should) just drop that part
+
+ String keyStr;
+ if (p.isExpectedStartObjectToken()) {
+ keyStr = p.nextFieldName();
+ } else {
+ JsonToken t = p.getCurrentToken();
+ if (t == JsonToken.END_OBJECT) {
+ return;
+ }
+ if (t != JsonToken.FIELD_NAME) {
+ ctxt.reportWrongTokenException(this, JsonToken.FIELD_NAME, null);
+ }
+ keyStr = p.getCurrentName();
+ }
+
+ for (; keyStr != null; keyStr = p.nextFieldName()) {
+ Object key = keyDes.deserializeKey(keyStr, ctxt);
+ // And then the value...
+ JsonToken t = p.nextToken();
+ if (_ignorableProperties != null && _ignorableProperties.contains(keyStr)) {
+ p.skipChildren();
+ continue;
+ }
+ try {
+ // Note: must handle null explicitly here, can't merge etc
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ result.put(key, _nullProvider.getNullValue(ctxt));
+ continue;
+ }
+ Object old = result.get(key);
+ Object value;
+ if (old != null) {
+ value = valueDes.deserialize(p, ctxt, old);
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(p, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(p, ctxt, typeDeser);
+ }
+ if (value != old) {
+ result.put(key, value);
+ }
+ } catch (Exception e) {
+ wrapAndThrow(e, result, keyStr);
+ }
+ }
}
- private void handleUnresolvedReference(JsonParser jp, MapReferringAccumulator accumulator,
+ /**
+ * Optimized method used when keys can be deserialized as plain old
+ * {@link java.lang.String}s, and there is no custom deserialized
+ * specified.
+ *
+ * @since 2.9
+ */
+ protected final void _readAndUpdateStringKeyMap(JsonParser p, DeserializationContext ctxt,
+ Map<Object,Object> result) throws IOException
+ {
+ final JsonDeserializer<Object> valueDes = _valueDeserializer;
+ final TypeDeserializer typeDeser = _valueTypeDeserializer;
+
+ // Note: assumption is that Object Id handling can't really work with merging
+ // and thereby we can (and should) just drop that part
+
+ String key;
+ if (p.isExpectedStartObjectToken()) {
+ key = p.nextFieldName();
+ } else {
+ JsonToken t = p.getCurrentToken();
+ if (t == JsonToken.END_OBJECT) {
+ return;
+ }
+ if (t != JsonToken.FIELD_NAME) {
+ ctxt.reportWrongTokenException(this, JsonToken.FIELD_NAME, null);
+ }
+ key = p.getCurrentName();
+ }
+
+ for (; key != null; key = p.nextFieldName()) {
+ JsonToken t = p.nextToken();
+ if (_ignorableProperties != null && _ignorableProperties.contains(key)) {
+ p.skipChildren();
+ continue;
+ }
+ try {
+ // Note: must handle null explicitly here, can't merge etc
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ result.put(key, _nullProvider.getNullValue(ctxt));
+ continue;
+ }
+ Object old = result.get(key);
+ Object value;
+ if (old != null) {
+ value = valueDes.deserialize(p, ctxt, old);
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(p, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(p, ctxt, typeDeser);
+ }
+ if (value != old) {
+ result.put(key, value);
+ }
+ } catch (Exception e) {
+ wrapAndThrow(e, result, key);
+ }
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods, other
+ /**********************************************************
+ */
+
+ private void handleUnresolvedReference(DeserializationContext ctxt,
+ MapReferringAccumulator accumulator,
Object key, UnresolvedForwardReference reference)
throws JsonMappingException
{
if (accumulator == null) {
- throw JsonMappingException.from(jp, "Unresolved forward reference but no identity info.", reference);
+ ctxt.reportInputMismatch(this,
+ "Unresolved forward reference but no identity info: "+reference);
}
Referring referring = accumulator.handleUnresolvedReference(reference, key);
reference.getRoid().appendReferring(referring);
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 7899712..2805c6b 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
@@ -27,8 +27,6 @@
// // Configuration: typing, deserializers
- protected final JavaType _type;
-
/**
* Key deserializer to use; either passed via constructor
* (when indicated by annotations), or resolved when
@@ -61,7 +59,6 @@
if (type.containedTypeCount() != 2) { // sanity check
throw new IllegalArgumentException("Missing generic type information for "+type);
}
- _type = type;
_keyDeserializer = keyDeser;
_valueDeserializer = valueDeser;
_valueTypeDeserializer = valueTypeDeser;
@@ -73,8 +70,7 @@
*/
protected MapEntryDeserializer(MapEntryDeserializer src)
{
- super(src._type);
- _type = src._type;
+ super(src);
_keyDeserializer = src._keyDeserializer;
_valueDeserializer = src._valueDeserializer;
_valueTypeDeserializer = src._valueTypeDeserializer;
@@ -84,8 +80,7 @@
KeyDeserializer keyDeser, JsonDeserializer<Object> valueDeser,
TypeDeserializer valueTypeDeser)
{
- super(src._type);
- _type = src._type;
+ super(src);
_keyDeserializer = keyDeser;
_valueDeserializer = valueDeser;
_valueTypeDeserializer = valueTypeDeser;
@@ -124,7 +119,7 @@
{
KeyDeserializer kd = _keyDeserializer;
if (kd == null) {
- kd = ctxt.findKeyDeserializer(_type.containedType(0), property);
+ kd = ctxt.findKeyDeserializer(_containerType.containedType(0), property);
} else {
if (kd instanceof ContextualKeyDeserializer) {
kd = ((ContextualKeyDeserializer) kd).createContextual(ctxt, property);
@@ -132,7 +127,7 @@
}
JsonDeserializer<?> vd = _valueDeserializer;
vd = findConvertingContentDeserializer(ctxt, property, vd);
- JavaType contentType = _type.containedType(1);
+ JavaType contentType = _containerType.containedType(1);
if (vd == null) {
vd = ctxt.findContextualValueDeserializer(contentType, property);
} else { // if directly assigned, probably not yet contextual, so:
@@ -153,7 +148,7 @@
@Override
public JavaType getContentType() {
- return _type.containedType(1);
+ return _containerType.containedType(1);
}
@Override
@@ -183,8 +178,8 @@
}
if (t != JsonToken.FIELD_NAME) {
if (t == JsonToken.END_OBJECT) {
- ctxt.reportMappingException("Can not deserialize a Map.Entry out of empty JSON Object");
- return null;
+ return ctxt.reportInputMismatch(this,
+ "Cannot deserialize a Map.Entry out of empty JSON Object");
}
return (Map.Entry<Object,Object>) ctxt.handleUnexpectedToken(handledType(), p);
}
@@ -215,10 +210,13 @@
t = p.nextToken();
if (t != JsonToken.END_OBJECT) {
if (t == JsonToken.FIELD_NAME) { // most likely
- ctxt.reportMappingException("Problem binding JSON into Map.Entry: more than one entry in JSON (second field: '"+p.getCurrentName()+"')");
+ ctxt.reportInputMismatch(this,
+ "Problem binding JSON into Map.Entry: more than one entry in JSON (second field: '%s')",
+ p.getCurrentName());
} else {
// how would this occur?
- ctxt.reportMappingException("Problem binding JSON into Map.Entry: unexpected content after JSON Object entry: "+t);
+ ctxt.reportInputMismatch(this,
+ "Problem binding JSON into Map.Entry: unexpected content after JSON Object entry: "+t);
}
return null;
}
@@ -229,23 +227,15 @@
public Map.Entry<Object,Object> deserialize(JsonParser p, DeserializationContext ctxt,
Map.Entry<Object,Object> result) throws IOException
{
- throw new IllegalStateException("Can not update Map.Entry values");
+ throw new IllegalStateException("Cannot update Map.Entry values");
}
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
- throws IOException, JsonProcessingException
+ throws IOException
{
// In future could check current token... for now this should be enough:
return typeDeserializer.deserializeTypedFromObject(p, ctxt);
}
-
- /*
- /**********************************************************
- /* Other public accessors
- /**********************************************************
- */
-
- @Override public JavaType getValueType() { return _type; }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/NullifyingDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/NullifyingDeserializer.java
index 0453595..bd58ecc 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/NullifyingDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/NullifyingDeserializer.java
@@ -9,7 +9,7 @@
/**
* Bogus deserializer that will simply skip all content there is to map
* and returns Java null reference.
- *
+ *
* @since 2.2
*/
public class NullifyingDeserializer
@@ -26,7 +26,12 @@
/* Deserializer API
/**********************************************************
*/
-
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return Boolean.FALSE;
+ }
+
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
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 e545d6b..35ec9d4 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
@@ -6,9 +6,11 @@
import java.util.HashSet;
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.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.util.AccessPattern;
/**
* Container class for deserializers that handle core JDK primitive
@@ -120,35 +122,47 @@
private static final long serialVersionUID = 1L;
protected final T _nullValue;
+
+ // @since 2.9
+ protected final T _emptyValue;
+
protected final boolean _primitive;
- protected PrimitiveOrWrapperDeserializer(Class<T> vc, T nvl) {
+ protected PrimitiveOrWrapperDeserializer(Class<T> vc, T nvl, T empty) {
super(vc);
_nullValue = nvl;
+ _emptyValue = empty;
_primitive = vc.isPrimitive();
}
@Override
- public final T getNullValue(DeserializationContext ctxt) throws JsonMappingException
- {
+ public AccessPattern getNullAccessPattern() {
+ // 02-Feb-2017, tatu: For primitives we must dynamically check (and possibly throw
+ // exception); for wrappers not.
+ if (_primitive) {
+ return AccessPattern.DYNAMIC;
+ }
+ if (_nullValue == null) {
+ return AccessPattern.ALWAYS_NULL;
+ }
+ return AccessPattern.CONSTANT;
+ }
+
+ @Override
+ public final T getNullValue(DeserializationContext ctxt) throws JsonMappingException {
+ // 01-Mar-2017, tatu: Alas, not all paths lead to `_coerceNull()`, as `SettableBeanProperty`
+ // short-circuits `null` handling. Hence need this check as well.
if (_primitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
- ctxt.reportMappingException(
- "Can not map JSON null into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
+ ctxt.reportInputMismatch(this,
+ "Cannot map `null` into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
handledType().toString());
}
return _nullValue;
}
@Override
- public T getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
- // [databind#1095]: Should not allow coercion from into null from Empty String
- // either, if `null` not allowed
- if (_primitive && ctxt.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
- ctxt.reportMappingException(
- "Can not map Empty String as null into type %s (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)",
- handledType().toString());
- }
- return _nullValue;
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return _emptyValue;
}
}
@@ -169,13 +183,20 @@
public BooleanDeserializer(Class<Boolean> cls, Boolean nvl)
{
- super(cls, nvl);
+ super(cls, nvl, Boolean.FALSE);
}
@Override
- public Boolean deserialize(JsonParser j, DeserializationContext ctxt) throws IOException
+ public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- return _parseBoolean(j, ctxt);
+ JsonToken t = p.getCurrentToken();
+ if (t == JsonToken.VALUE_TRUE) {
+ return Boolean.TRUE;
+ }
+ if (t == JsonToken.VALUE_FALSE) {
+ return Boolean.FALSE;
+ }
+ return _parseBoolean(p, ctxt);
}
// Since we can never have type info ("natural type"; String, Boolean, Integer, Double):
@@ -185,8 +206,61 @@
TypeDeserializer typeDeserializer)
throws IOException
{
+ JsonToken t = p.getCurrentToken();
+ 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;
+ }
+ // Otherwise, no can do:
+ return (Boolean) ctxt.handleUnexpectedToken(_valueClass, p);
+ }
}
@JacksonStdImpl
@@ -200,14 +274,65 @@
public ByteDeserializer(Class<Byte> cls, Byte nvl)
{
- super(cls, nvl);
+ super(cls, nvl, (byte) 0);
}
@Override
public Byte deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
+ if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ return p.getByteValue();
+ }
return _parseByte(p, ctxt);
}
+
+ 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");
+ }
+ // 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");
+ }
+ return p.getByteValue();
+ }
+ if (t == JsonToken.VALUE_NULL) {
+ return (Byte) _coerceNullToken(ctxt, _primitive);
+ }
+ // [databind#381]
+ if (t == JsonToken.START_ARRAY) {
+ return _deserializeFromArray(p, ctxt);
+ }
+ if (t == JsonToken.VALUE_NUMBER_INT) { // shouldn't usually be called with it but
+ return p.getByteValue();
+ }
+ return (Byte) ctxt.handleUnexpectedToken(_valueClass, p);
+ }
}
@JacksonStdImpl
@@ -221,14 +346,59 @@
public ShortDeserializer(Class<Short> cls, Short nvl)
{
- super(cls, nvl);
+ super(cls, nvl, (short)0);
}
@Override
- public Short deserialize(JsonParser jp, DeserializationContext ctxt)
+ public Short deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- return _parseShort(jp, ctxt);
+ return _parseShort(p, ctxt);
+ }
+
+ 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");
+ }
+ return Short.valueOf((short) value);
+ }
+ if (t == JsonToken.VALUE_NUMBER_FLOAT) {
+ if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
+ _failDoubleToIntCoercion(p, ctxt, "Short");
+ }
+ return p.getShortValue();
+ }
+ if (t == JsonToken.VALUE_NULL) {
+ return (Short) _coerceNullToken(ctxt, _primitive);
+ }
+ if (t == JsonToken.START_ARRAY) {
+ return _deserializeFromArray(p, ctxt);
+ }
+ return (Short) ctxt.handleUnexpectedToken(_valueClass, p);
}
}
@@ -243,7 +413,7 @@
public CharacterDeserializer(Class<Character> cls, Character nvl)
{
- super(cls, nvl);
+ super(cls, nvl, '\0');
}
@Override
@@ -252,6 +422,7 @@
{
switch (p.getCurrentTokenId()) {
case JsonTokenId.ID_NUMBER_INT: // ok iff ascii value
+ _verifyNumberForScalarCoercion(ctxt, p);
int value = p.getIntValue();
if (value >= 0 && value <= 0xFFFF) {
return Character.valueOf((char) value);
@@ -265,9 +436,11 @@
}
// actually, empty should become null?
if (text.length() == 0) {
- return (Character) getEmptyValue(ctxt);
- }
+ return (Character) _coerceEmptyString(ctxt, _primitive);
+ }
break;
+ case JsonTokenId.ID_NULL:
+ return (Character) _coerceNullToken(ctxt, _primitive);
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(p, ctxt);
default:
@@ -282,11 +455,11 @@
{
private static final long serialVersionUID = 1L;
- final static IntegerDeserializer primitiveInstance = new IntegerDeserializer(Integer.TYPE, Integer.valueOf(0));
+ final static IntegerDeserializer primitiveInstance = new IntegerDeserializer(Integer.TYPE, 0);
final static IntegerDeserializer wrapperInstance = new IntegerDeserializer(Integer.class, null);
public IntegerDeserializer(Class<Integer> cls, Integer nvl) {
- super(cls, nvl);
+ super(cls, nvl, 0);
}
// since 2.6, slightly faster lookups for this very common type
@@ -312,6 +485,51 @@
}
return _parseInteger(p, ctxt);
}
+
+ 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());
+ 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);
+ }
+ if (_hasTextualNull(text)) {
+ return (Integer) _coerceTextualNull(ctxt, _primitive);
+ }
+ _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);
+ case JsonTokenId.ID_START_ARRAY:
+ return _deserializeFromArray(p, ctxt);
+ }
+ // Otherwise, no can do:
+ return (Integer) ctxt.handleUnexpectedToken(_valueClass, p);
+ }
}
@JacksonStdImpl
@@ -320,11 +538,11 @@
{
private static final long serialVersionUID = 1L;
- final static LongDeserializer primitiveInstance = new LongDeserializer(Long.TYPE, Long.valueOf(0L));
+ final static LongDeserializer primitiveInstance = new LongDeserializer(Long.TYPE, 0L);
final static LongDeserializer wrapperInstance = new LongDeserializer(Long.class, null);
public LongDeserializer(Class<Long> cls, Long nvl) {
- super(cls, nvl);
+ super(cls, nvl, 0L);
}
// since 2.6, slightly faster lookups for this very common type
@@ -338,6 +556,42 @@
}
return _parseLong(p, ctxt);
}
+
+ 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();
+ case JsonTokenId.ID_STRING:
+ String text = p.getText().trim();
+ if (text.length() == 0) {
+ return (Long) _coerceEmptyString(ctxt, _primitive);
+ }
+ if (_hasTextualNull(text)) {
+ return (Long) _coerceTextualNull(ctxt, _primitive);
+ }
+ _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);
+ case JsonTokenId.ID_START_ARRAY:
+ return _deserializeFromArray(p, ctxt);
+ }
+ // Otherwise, no can do:
+ return (Long) ctxt.handleUnexpectedToken(_valueClass, p);
+ }
}
@JacksonStdImpl
@@ -350,7 +604,7 @@
final static FloatDeserializer wrapperInstance = new FloatDeserializer(Float.class, null);
public FloatDeserializer(Class<Float> cls, Float nvl) {
- super(cls, nvl);
+ super(cls, nvl, 0.f);
}
@Override
@@ -358,6 +612,58 @@
{
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
+ return p.getFloatValue();
+ }
+ // 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);
+ }
+ if (_hasTextualNull(text)) {
+ return (Float) _coerceTextualNull(ctxt, _primitive);
+ }
+ 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;
+ }
+ _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);
+ }
+ // Otherwise, no can do:
+ return (Float) ctxt.handleUnexpectedToken(_valueClass, p);
+ }
}
@JacksonStdImpl
@@ -370,21 +676,69 @@
final static DoubleDeserializer wrapperInstance = new DoubleDeserializer(Double.class, null);
public DoubleDeserializer(Class<Double> cls, Double nvl) {
- super(cls, nvl);
+ super(cls, nvl, 0.d);
}
@Override
- public Double deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
- return _parseDouble(jp, ctxt);
+ public Double deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ return _parseDouble(p, ctxt);
}
// Since we can never have type info ("natural type"; String, Boolean, Integer, Double):
// (is it an error to even call this version?)
@Override
- public Double deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ public Double deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) throws IOException
{
- return _parseDouble(jp, 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
+ return p.getDoubleValue();
+ }
+ if (t == JsonToken.VALUE_STRING) {
+ String text = p.getText().trim();
+ if ((text.length() == 0)) {
+ return (Double) _coerceEmptyString(ctxt, _primitive);
+ }
+ if (_hasTextualNull(text)) {
+ return (Double) _coerceTextualNull(ctxt, _primitive);
+ }
+ 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;
+ }
+ _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);
+ }
+ // Otherwise, no can do:
+ return (Double) ctxt.handleUnexpectedToken(_valueClass, p);
}
}
@@ -421,7 +775,10 @@
case JsonTokenId.ID_NUMBER_FLOAT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
- return p.getDecimalValue();
+ // 10-Mar-2017, tatu: NaN and BigDecimal won't mix...
+ if (!p.isNaN()) {
+ return p.getDecimalValue();
+ }
}
return p.getNumberValue();
@@ -430,10 +787,12 @@
* out 'minimal' type to use
*/
String text = p.getText().trim();
- if (text.length() == 0) {
- return getEmptyValue(ctxt);
+ 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)) {
@@ -445,12 +804,13 @@
if (_isNaN(text)) {
return Double.NaN;
}
+ _verifyStringForScalarCoercion(ctxt, text);
try {
if (!_isIntNumber(text)) {
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
return new BigDecimal(text);
}
- return new Double(text);
+ return Double.valueOf(text);
}
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
return new BigInteger(text);
@@ -480,18 +840,18 @@
* calling type deserializer.
*/
@Override
- public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
- TypeDeserializer typeDeserializer)
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
throws IOException
{
- switch (jp.getCurrentTokenId()) {
+ switch (p.getCurrentTokenId()) {
case JsonTokenId.ID_NUMBER_INT:
case JsonTokenId.ID_NUMBER_FLOAT:
case JsonTokenId.ID_STRING:
- // can not point to type information: hence must be non-typed (int/double)
- return deserialize(jp, ctxt);
+ // cannot point to type information: hence must be non-typed (int/double)
+ return deserialize(p, ctxt);
}
- return typeDeserializer.deserializeTypedFromScalar(jp, ctxt);
+ return typeDeserializer.deserializeTypedFromScalar(p, ctxt);
}
}
@@ -515,6 +875,11 @@
public BigIntegerDeserializer() { super(BigInteger.class); }
+ @Override
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ return BigInteger.ZERO;
+ }
+
@SuppressWarnings("incomplete-switch")
@Override
public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
@@ -537,15 +902,17 @@
return _deserializeFromArray(p, ctxt);
case JsonTokenId.ID_STRING: // let's do implicit re-parse
String text = p.getText().trim();
- if (text.length() == 0) {
- return null;
+ // 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");
- }
+ } catch (IllegalArgumentException iae) { }
+ return (BigInteger) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid representation");
}
// String is ok too, can easily convert; otherwise, no can do:
return (BigInteger) ctxt.handleUnexpectedToken(_valueClass, p);
@@ -562,6 +929,11 @@
public BigDecimalDeserializer() { super(BigDecimal.class); }
@Override
+ public Object getEmptyValue(DeserializationContext ctxt) {
+ return BigDecimal.ZERO;
+ }
+
+ @Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
@@ -571,15 +943,17 @@
return p.getDecimalValue();
case JsonTokenId.ID_STRING:
String text = p.getText().trim();
- if (text.length() == 0) {
- return null;
+ // 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");
- }
+ } catch (IllegalArgumentException iae) { }
+ return (BigDecimal) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid representation");
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(p, ctxt);
}
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 101217b..017317d 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
@@ -10,8 +10,9 @@
import com.fasterxml.jackson.databind.*;
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.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ObjectBuffer;
/**
@@ -24,14 +25,11 @@
{
private static final long serialVersionUID = 1L;
+ protected final static Object[] NO_OBJECTS = new Object[0];
+
// // Configuration
/**
- * Full generic type of the array being deserialized
- */
- protected final ArrayType _arrayType;
-
- /**
* Flag that indicates whether the component type is Object or not.
* Used for minor optimization when constructing result.
*/
@@ -54,54 +52,42 @@
*/
protected final TypeDeserializer _elementTypeDeserializer;
- /**
- * Specific override for this instance (from proper, or global per-type overrides)
- * to indicate whether single value may be taken to mean an unwrapped one-element array
- * or not. If null, left to global defaults.
- *
- * @since 2.7
- */
- protected final Boolean _unwrapSingle;
-
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
- public ObjectArrayDeserializer(ArrayType arrayType,
+ public ObjectArrayDeserializer(JavaType arrayType,
JsonDeserializer<Object> elemDeser, TypeDeserializer elemTypeDeser)
{
- super(arrayType);
- _arrayType = arrayType;
+ super(arrayType, null, null);
_elementClass = arrayType.getContentType().getRawClass();
_untyped = (_elementClass == Object.class);
_elementDeserializer = elemDeser;
_elementTypeDeserializer = elemTypeDeser;
- _unwrapSingle = null;
}
protected ObjectArrayDeserializer(ObjectArrayDeserializer base,
JsonDeserializer<Object> elemDeser, TypeDeserializer elemTypeDeser,
- Boolean unwrapSingle)
+ NullValueProvider nuller, Boolean unwrapSingle)
{
- super(base._arrayType);
- _arrayType = base._arrayType;
+ super(base, nuller, unwrapSingle);
_elementClass = base._elementClass;
_untyped = base._untyped;
_elementDeserializer = elemDeser;
_elementTypeDeserializer = elemTypeDeser;
- _unwrapSingle = unwrapSingle;
}
-
+
/**
* Overridable fluent-factory method used to create contextual instances
*/
public ObjectArrayDeserializer withDeserializer(TypeDeserializer elemTypeDeser,
JsonDeserializer<?> elemDeser)
{
- return withResolved(elemTypeDeser, elemDeser, _unwrapSingle);
+ return withResolved(elemTypeDeser, elemDeser,
+ _nullProvider, _unwrapSingle);
}
/**
@@ -109,43 +95,46 @@
*/
@SuppressWarnings("unchecked")
public ObjectArrayDeserializer withResolved(TypeDeserializer elemTypeDeser,
- JsonDeserializer<?> elemDeser, Boolean unwrapSingle)
+ JsonDeserializer<?> elemDeser, NullValueProvider nuller, Boolean unwrapSingle)
{
- if ((unwrapSingle == _unwrapSingle)
+ if ((unwrapSingle == _unwrapSingle) && (nuller == _nullProvider)
&& (elemDeser == _elementDeserializer)
&& (elemTypeDeser == _elementTypeDeserializer)) {
return this;
}
return new ObjectArrayDeserializer(this,
- (JsonDeserializer<Object>) elemDeser, elemTypeDeser, unwrapSingle);
+ (JsonDeserializer<Object>) elemDeser, elemTypeDeser,
+ nuller, unwrapSingle);
+ }
+
+ @Override // since 2.5
+ public boolean isCachable() {
+ // Important: do NOT cache if polymorphic values, or if there are annotation-based
+ // custom deserializers
+ return (_elementDeserializer == null) && (_elementTypeDeserializer == null);
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
- JsonDeserializer<?> deser = _elementDeserializer;
- Boolean unwrapSingle = findFormatFeature(ctxt, property, _arrayType.getRawClass(),
+ JsonDeserializer<?> valueDeser = _elementDeserializer;
+ Boolean unwrapSingle = findFormatFeature(ctxt, property, _containerType.getRawClass(),
JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
// May have a content converter
- deser = findConvertingContentDeserializer(ctxt, property, deser);
- final JavaType vt = _arrayType.getContentType();
- if (deser == null) {
- deser = ctxt.findContextualValueDeserializer(vt, property);
+ valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser);
+ final JavaType vt = _containerType.getContentType();
+ if (valueDeser == null) {
+ valueDeser = ctxt.findContextualValueDeserializer(vt, property);
} else { // if directly assigned, probably not yet contextual, so:
- deser = ctxt.handleSecondaryContextualization(deser, property, vt);
+ valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt);
}
TypeDeserializer elemTypeDeser = _elementTypeDeserializer;
if (elemTypeDeser != null) {
elemTypeDeser = elemTypeDeser.forProperty(property);
}
- return withResolved(elemTypeDeser, deser, unwrapSingle);
- }
-
- @Override // since 2.5
- public boolean isCachable() {
- // Important: do NOT cache if polymorphic values, or ones with custom deserializer
- return (_elementDeserializer == null) && (_elementTypeDeserializer == null);
+ NullValueProvider nuller = findContentNullProvider(ctxt, property, valueDeser);
+ return withResolved(elemTypeDeser, valueDeser, nuller, unwrapSingle);
}
/*
@@ -155,15 +144,22 @@
*/
@Override
- public JavaType getContentType() {
- return _arrayType.getContentType();
- }
-
- @Override
public JsonDeserializer<Object> getContentDeserializer() {
return _elementDeserializer;
}
-
+
+ @Override // since 2.9
+ public AccessPattern getEmptyAccessPattern() {
+ // immutable, shareable so:
+ return AccessPattern.CONSTANT;
+ }
+
+ // need to override as we can't expose ValueInstantiator
+ @Override // since 2.9
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return NO_OBJECTS;
+ }
+
/*
/**********************************************************
/* JsonDeserializer API
@@ -191,7 +187,10 @@
Object value;
if (t == JsonToken.VALUE_NULL) {
- value = _elementDeserializer.getNullValue(ctxt);
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = _elementDeserializer.deserialize(p, ctxt);
} else {
@@ -223,12 +222,68 @@
TypeDeserializer typeDeserializer)
throws IOException
{
- /* Should there be separate handling for base64 stuff?
- * for now this should be enough:
- */
+ // Should there be separate handling for base64 stuff?
+ // for now this should be enough:
return (Object[]) typeDeserializer.deserializeTypedFromArray(p, ctxt);
}
-
+
+ @Override // since 2.9
+ public Object[] deserialize(JsonParser p, DeserializationContext ctxt,
+ Object[] intoValue) throws IOException
+ {
+ if (!p.isExpectedStartArrayToken()) {
+ Object[] arr = handleNonArray(p, ctxt);
+ if (arr == null) {
+ return intoValue;
+ }
+ final int offset = intoValue.length;
+ Object[] result = new Object[offset + arr.length];
+ System.arraycopy(intoValue, 0, result, 0, offset);
+ System.arraycopy(arr, 0, result, offset, arr.length);
+ return result;
+ }
+
+ final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
+ int ix = intoValue.length;
+ Object[] chunk = buffer.resetAndStart(intoValue, ix);
+ JsonToken t;
+ final TypeDeserializer typeDeser = _elementTypeDeserializer;
+
+ try {
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
+ Object value;
+
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ value = _nullProvider.getNullValue(ctxt);
+ } else if (typeDeser == null) {
+ value = _elementDeserializer.deserialize(p, ctxt);
+ } else {
+ value = _elementDeserializer.deserializeWithType(p, ctxt, typeDeser);
+ }
+ if (ix >= chunk.length) {
+ chunk = buffer.appendCompletedChunk(chunk);
+ ix = 0;
+ }
+ chunk[ix++] = value;
+ }
+ } catch (Exception e) {
+ throw JsonMappingException.wrapWithPath(e, chunk, buffer.bufferedSize() + ix);
+ }
+
+ Object[] result;
+
+ if (_untyped) {
+ result = buffer.completeAndClearBuffer(chunk, ix);
+ } else {
+ result = buffer.completeAndClearBuffer(chunk, ix, _elementClass);
+ }
+ ctxt.returnObjectBuffer(buffer);
+ return result;
+ }
+
/*
/**********************************************************
/* Internal methods
@@ -259,7 +314,7 @@
return null;
}
}
-
+
// Can we do implicit coercion to a single-element array still?
boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
((_unwrapSingle == null) &&
@@ -272,13 +327,17 @@
&& _elementClass == Byte.class) {
return deserializeFromBase64(p, ctxt);
}
- return (Object[]) ctxt.handleUnexpectedToken(_arrayType.getRawClass(), p);
+ return (Object[]) ctxt.handleUnexpectedToken(_containerType.getRawClass(), p);
}
JsonToken t = p.getCurrentToken();
Object value;
if (t == JsonToken.VALUE_NULL) {
- value = _elementDeserializer.getNullValue(ctxt);
+ // 03-Feb-2017, tatu: Should this be skipped or not?
+ if (_skipNullValues) {
+ return NO_OBJECTS;
+ }
+ value = _nullProvider.getNullValue(ctxt);
} else if (_elementTypeDeserializer == null) {
value = _elementDeserializer.deserialize(p, ctxt);
} else {
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 4221b30..175db71 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
@@ -1,13 +1,21 @@
package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
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.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.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
/**
@@ -27,18 +35,35 @@
*/
protected final Boolean _unwrapSingle;
+ // since 2.9
+ private transient Object _emptyValue;
+
+ /**
+ * Flag that indicates need for special handling; either failing
+ * (throw exception) or skipping
+ */
+ protected final NullValueProvider _nuller;
+
+ /*
+ /********************************************************
+ /* Life-cycle
+ /********************************************************
+ */
+
protected PrimitiveArrayDeserializers(Class<T> cls) {
super(cls);
_unwrapSingle = null;
+ _nuller = null;
}
/**
* @since 2.7
*/
protected PrimitiveArrayDeserializers(PrimitiveArrayDeserializers<?> base,
- Boolean unwrapSingle) {
+ NullValueProvider nuller, Boolean unwrapSingle) {
super(base._valueClass);
_unwrapSingle = unwrapSingle;
+ _nuller = nuller;
}
public static JsonDeserializer<?> forType(Class<?> rawType)
@@ -72,37 +97,134 @@
throw new IllegalStateException();
}
- /**
- * @since 2.7
- */
- protected abstract PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle);
-
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
Boolean unwrapSingle = findFormatFeature(ctxt, property, _valueClass,
JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
- if (unwrapSingle == _unwrapSingle) {
+ NullValueProvider nuller = null;
+
+ Nulls nullStyle = findContentNullStyle(ctxt, property);
+ if (nullStyle == Nulls.SKIP) {
+ nuller = NullsConstantProvider.skipper();
+ } else if (nullStyle == Nulls.FAIL) {
+ if (property == null) {
+ nuller = NullsFailProvider.constructForRootValue(ctxt.constructType(_valueClass));
+ } else {
+ nuller = NullsFailProvider.constructForProperty(property);
+ }
+ }
+ if ((unwrapSingle == _unwrapSingle) && (nuller == _nuller)) {
return this;
}
- return withResolved(unwrapSingle);
+ return withResolved(nuller, unwrapSingle);
+ }
+
+ /*
+ /********************************************************
+ /* Abstract methods for sub-classes to implement
+ /********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ protected abstract T _concat(T oldValue, T newValue);
+
+ protected abstract T handleSingleElementUnwrapped(JsonParser p,
+ DeserializationContext ctxt) throws IOException;
+
+ /**
+ * @since 2.9
+ */
+ protected abstract PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle);
+
+ // since 2.9
+ protected abstract T _constructEmpty();
+
+ /*
+ /********************************************************
+ /* Default implementations
+ /********************************************************
+ */
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return Boolean.TRUE;
+ }
+
+ @Override
+ public AccessPattern getEmptyAccessPattern() {
+ // Empty values shareable freely
+ return AccessPattern.CONSTANT;
+ }
+
+ @Override // since 2.9
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ Object empty = _emptyValue;
+ if (empty == null) {
+ _emptyValue = empty = _constructEmpty();
+ }
+ return empty;
}
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) throws IOException
{
- /* Should there be separate handling for base64 stuff?
- * for now this should be enough:
- */
+ // Should there be separate handling for base64 stuff?
+ // for now this should be enough:
return typeDeserializer.deserializeTypedFromArray(p, ctxt);
}
+ @Override
+ public T deserialize(JsonParser p, DeserializationContext ctxt, T existing) throws IOException
+ {
+ T newValue = deserialize(p, ctxt);
+ if (existing == null) {
+ return newValue;
+ }
+ int len = Array.getLength(existing);
+ if (len == 0) {
+ return newValue;
+ }
+ return _concat(existing, newValue);
+ }
+
+ /*
+ /********************************************************
+ /* Helper methods for sub-classes
+ /********************************************************
+ */
+
+ /*
+ * Convenience method that constructs a concatenation of two arrays,
+ * with the type they have.
+ *
+ * @since 2.9
+ @SuppressWarnings("unchecked")
+ public static <T> T concatArrays(T array1, T array2)
+ {
+ int len1 = Array.getLength(array1);
+ if (len1 == 0) {
+ return array2;
+ }
+ int len2 = Array.getLength(array2);
+ if (len2 == 0) {
+ return array1;
+ }
+ Object result = Arrays.copyOf((Object[]) array1, len1 + len2);
+ System.arraycopy(array2, 0, result, len1, len2);
+ return (T) result;
+ }
+ */
+
@SuppressWarnings("unchecked")
protected T handleNonArray(JsonParser p, DeserializationContext ctxt) throws IOException
{
- // [JACKSON-620] Empty String can become null...
+ // 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) {
@@ -118,8 +240,10 @@
return (T) ctxt.handleUnexpectedToken(_valueClass, p);
}
- protected abstract T handleSingleElementUnwrapped(JsonParser p,
- DeserializationContext ctxt) throws IOException;
+ protected void _failOnNull(DeserializationContext ctxt) throws IOException
+ {
+ throw InvalidNullException.from(ctxt, null, ctxt.constructType(_valueClass));
+ }
/*
/********************************************************
@@ -134,16 +258,22 @@
private static final long serialVersionUID = 1L;
public CharDeser() { super(char[].class); }
- protected CharDeser(CharDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected CharDeser(CharDeser base, NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
// 11-Dec-2015, tatu: Not sure how re-wrapping would work; omit
return this;
}
-
+
+ @Override
+ protected char[] _constructEmpty() {
+ return new char[0];
+ }
+
@Override
public char[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -169,13 +299,20 @@
String str;
if (t == JsonToken.VALUE_STRING) {
str = p.getText();
+ } else if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ _verifyNullForPrimitive(ctxt);
+ str = "\0";
} else {
CharSequence cs = (CharSequence) ctxt.handleUnexpectedToken(Character.TYPE, p);
str = cs.toString();
}
if (str.length() != 1) {
- ctxt.reportMappingException("Can not convert a JSON String of length %d into a char element of char array",
- str.length());
+ ctxt.reportInputMismatch(this,
+"Cannot convert a JSON String of length %d into a char element of char array", str.length());
}
sb.append(str.charAt(0));
}
@@ -206,6 +343,15 @@
// not sure how this should work...
return (char[]) ctxt.handleUnexpectedToken(_valueClass, p);
}
+
+ @Override
+ protected char[] _concat(char[] oldValue, char[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ char[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
/*
@@ -221,18 +367,24 @@
private static final long serialVersionUID = 1L;
public BooleanDeser() { super(boolean[].class); }
- protected BooleanDeser(BooleanDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected BooleanDeser(BooleanDeser base, NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
- return new BooleanDeser(this, unwrapSingle);
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
+ return new BooleanDeser(this, nuller, unwrapSingle);
+ }
+
+ @Override
+ protected boolean[] _constructEmpty() {
+ return new boolean[0];
}
@Override
public boolean[] deserialize(JsonParser p, DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ throws IOException
{
if (!p.isExpectedStartArrayToken()) {
return handleNonArray(p, ctxt);
@@ -242,9 +394,23 @@
int ix = 0;
try {
- while (p.nextToken() != JsonToken.END_ARRAY) {
- // whether we should allow truncating conversions?
- boolean value = _parseBooleanPrimitive(p, ctxt);
+ JsonToken t;
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
+ boolean value;
+ if (t == JsonToken.VALUE_TRUE) {
+ value = true;
+ } else if (t == JsonToken.VALUE_FALSE) {
+ value = false;
+ } else if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ _verifyNullForPrimitive(ctxt);
+ value = false;
+ } else {
+ value = _parseBooleanPrimitive(p, ctxt);
+ }
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -262,6 +428,15 @@
DeserializationContext ctxt) throws IOException {
return new boolean[] { _parseBooleanPrimitive(p, ctxt) };
}
+
+ @Override
+ protected boolean[] _concat(boolean[] oldValue, boolean[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ boolean[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
/**
@@ -275,13 +450,19 @@
private static final long serialVersionUID = 1L;
public ByteDeser() { super(byte[].class); }
- protected ByteDeser(ByteDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected ByteDeser(ByteDeser base, NullValueProvider nuller,Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
- return new ByteDeser(this, unwrapSingle);
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
+ return new ByteDeser(this, nuller, unwrapSingle);
+ }
+
+ @Override
+ protected byte[] _constructEmpty() {
+ return new byte[0];
}
@Override
@@ -291,7 +472,19 @@
// Most likely case: base64 encoded String?
if (t == JsonToken.VALUE_STRING) {
- return p.getBinaryValue(ctxt.getBase64Variant());
+ try {
+ return p.getBinaryValue(ctxt.getBase64Variant());
+ } catch (JsonParseException e) {
+ // 25-Nov-2016, tatu: related to [databind#1425], try to convert
+ // to a more usable one, as it's not really a JSON-level parse
+ // exception, but rather binding from JSON String into base64 decoded
+ // binary data
+ String msg = e.getOriginalMessage();
+ if (msg.contains("base64")) {
+ return (byte[]) ctxt.handleWeirdStringValue(byte[].class,
+ p.getText(), msg);
+ }
+ }
}
// 31-Dec-2009, tatu: Also may be hidden as embedded Object
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
@@ -318,10 +511,14 @@
} else {
// should probably accept nulls as 0
if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ _verifyNullForPrimitive(ctxt);
value = (byte) 0;
} else {
- Number n = (Number) ctxt.handleUnexpectedToken(_valueClass.getComponentType(), p);
- value = n.byteValue();
+ value = _parseBytePrimitive(p, ctxt);
}
}
if (ix >= chunk.length) {
@@ -348,6 +545,11 @@
} else {
// should probably accept nulls as 'false'
if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ return (byte[]) getEmptyValue(ctxt);
+ }
+ _verifyNullForPrimitive(ctxt);
return null;
}
Number n = (Number) ctxt.handleUnexpectedToken(_valueClass.getComponentType(), p);
@@ -355,6 +557,15 @@
}
return new byte[] { value };
}
+
+ @Override
+ protected byte[] _concat(byte[] oldValue, byte[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ byte[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
@JacksonStdImpl
@@ -364,15 +575,21 @@
private static final long serialVersionUID = 1L;
public ShortDeser() { super(short[].class); }
- protected ShortDeser(ShortDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected ShortDeser(ShortDeser base, NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
- return new ShortDeser(this, unwrapSingle);
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
+ return new ShortDeser(this, nuller, unwrapSingle);
}
-
+
+ @Override
+ protected short[] _constructEmpty() {
+ return new short[0];
+ }
+
@Override
public short[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -384,8 +601,19 @@
int ix = 0;
try {
- while (p.nextToken() != JsonToken.END_ARRAY) {
- short value = _parseShortPrimitive(p, ctxt);
+ JsonToken t;
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
+ short value;
+ if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ _verifyNullForPrimitive(ctxt);
+ value = (short) 0;
+ } else {
+ value = _parseShortPrimitive(p, ctxt);
+ }
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -403,6 +631,15 @@
DeserializationContext ctxt) throws IOException {
return new short[] { _parseShortPrimitive(p, ctxt) };
}
+
+ @Override
+ protected short[] _concat(short[] oldValue, short[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ short[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
@JacksonStdImpl
@@ -414,13 +651,19 @@
public final static IntDeser instance = new IntDeser();
public IntDeser() { super(int[].class); }
- protected IntDeser(IntDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected IntDeser(IntDeser base, NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
- return new IntDeser(this, unwrapSingle);
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
+ return new IntDeser(this, nuller, unwrapSingle);
+ }
+
+ @Override
+ protected int[] _constructEmpty() {
+ return new int[0];
}
@Override
@@ -434,9 +677,21 @@
int ix = 0;
try {
- while (p.nextToken() != JsonToken.END_ARRAY) {
- // whether we should allow truncating conversions?
- int value = _parseIntPrimitive(p, ctxt);
+ JsonToken t;
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
+ int value;
+ if (t == JsonToken.VALUE_NUMBER_INT) {
+ value = p.getIntValue();
+ } else if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ _verifyNullForPrimitive(ctxt);
+ value = 0;
+ } else {
+ value = _parseIntPrimitive(p, ctxt);
+ }
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -454,6 +709,15 @@
DeserializationContext ctxt) throws IOException {
return new int[] { _parseIntPrimitive(p, ctxt) };
}
+
+ @Override
+ protected int[] _concat(int[] oldValue, int[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ int[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
@JacksonStdImpl
@@ -465,13 +729,19 @@
public final static LongDeser instance = new LongDeser();
public LongDeser() { super(long[].class); }
- protected LongDeser(LongDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected LongDeser(LongDeser base, NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
- return new LongDeser(this, unwrapSingle);
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
+ return new LongDeser(this, nuller, unwrapSingle);
+ }
+
+ @Override
+ protected long[] _constructEmpty() {
+ return new long[0];
}
@Override
@@ -485,8 +755,21 @@
int ix = 0;
try {
- while (p.nextToken() != JsonToken.END_ARRAY) {
- long value = _parseLongPrimitive(p, ctxt);
+ JsonToken t;
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
+ long value;
+ if (t == JsonToken.VALUE_NUMBER_INT) {
+ value = p.getLongValue();
+ } else if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ _verifyNullForPrimitive(ctxt);
+ value = 0L;
+ } else {
+ value = _parseLongPrimitive(p, ctxt);
+ }
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -504,6 +787,15 @@
DeserializationContext ctxt) throws IOException {
return new long[] { _parseLongPrimitive(p, ctxt) };
}
+
+ @Override
+ protected long[] _concat(long[] oldValue, long[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ long[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
@JacksonStdImpl
@@ -513,18 +805,23 @@
private static final long serialVersionUID = 1L;
public FloatDeser() { super(float[].class); }
- protected FloatDeser(FloatDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected FloatDeser(FloatDeser base, NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
- return new FloatDeser(this, unwrapSingle);
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
+ return new FloatDeser(this, nuller, unwrapSingle);
}
@Override
- public float[] deserialize(JsonParser p, DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ protected float[] _constructEmpty() {
+ return new float[0];
+ }
+
+ @Override
+ public float[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
if (!p.isExpectedStartArrayToken()) {
return handleNonArray(p, ctxt);
@@ -534,8 +831,15 @@
int ix = 0;
try {
- while (p.nextToken() != JsonToken.END_ARRAY) {
+ JsonToken t;
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
+ if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ }
float value = _parseFloatPrimitive(p, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
@@ -554,6 +858,15 @@
DeserializationContext ctxt) throws IOException {
return new float[] { _parseFloatPrimitive(p, ctxt) };
}
+
+ @Override
+ protected float[] _concat(float[] oldValue, float[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ float[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
@JacksonStdImpl
@@ -563,13 +876,19 @@
private static final long serialVersionUID = 1L;
public DoubleDeser() { super(double[].class); }
- protected DoubleDeser(DoubleDeser base, Boolean unwrapSingle) {
- super(base, unwrapSingle);
+ protected DoubleDeser(DoubleDeser base, NullValueProvider nuller, Boolean unwrapSingle) {
+ super(base, nuller, unwrapSingle);
}
@Override
- protected PrimitiveArrayDeserializers<?> withResolved(Boolean unwrapSingle) {
- return new DoubleDeser(this, unwrapSingle);
+ protected PrimitiveArrayDeserializers<?> withResolved(NullValueProvider nuller,
+ Boolean unwrapSingle) {
+ return new DoubleDeser(this, nuller, unwrapSingle);
+ }
+
+ @Override
+ protected double[] _constructEmpty() {
+ return new double[0];
}
@Override
@@ -583,7 +902,14 @@
int ix = 0;
try {
- while (p.nextToken() != JsonToken.END_ARRAY) {
+ JsonToken t;
+ while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
+ if (t == JsonToken.VALUE_NULL) {
+ if (_nuller != null) {
+ _nuller.getNullValue(ctxt);
+ continue;
+ }
+ }
double value = _parseDoublePrimitive(p, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
@@ -602,5 +928,14 @@
DeserializationContext ctxt) throws IOException {
return new double[] { _parseDoublePrimitive(p, ctxt) };
}
+
+ @Override
+ protected double[] _concat(double[] oldValue, double[] newValue) {
+ int len1 = oldValue.length;
+ int len2 = newValue.length;
+ double[] result = Arrays.copyOf(oldValue, len1+len2);
+ System.arraycopy(newValue, 0, result, len1, len2);
+ return result;
+ }
}
}
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 65876df..34e502d 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
@@ -10,6 +10,7 @@
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.ReferenceType;
+import com.fasterxml.jackson.databind.util.AccessPattern;
/**
* Base deserializer implementation for properties {@link ReferenceType} values.
@@ -22,17 +23,18 @@
extends StdDeserializer<T>
implements ContextualDeserializer
{
- private static final long serialVersionUID = 1L;
-
+ private static final long serialVersionUID = 2L; // 2.9
+
/**
* Full type of property (or root value) for which this deserializer
* has been constructed and contextualized.
*/
protected final JavaType _fullType;
-
+
+ protected final ValueInstantiator _valueInstantiator;
+
protected final TypeDeserializer _valueTypeDeserializer;
-
- protected final JsonDeserializer<?> _valueDeserializer;
+ protected final JsonDeserializer<Object> _valueDeserializer;
/*
/**********************************************************
@@ -40,23 +42,27 @@
/**********************************************************
*/
- public ReferenceTypeDeserializer(JavaType fullType,
+ @SuppressWarnings("unchecked")
+ public ReferenceTypeDeserializer(JavaType fullType, ValueInstantiator vi,
TypeDeserializer typeDeser, JsonDeserializer<?> deser)
{
super(fullType);
+ _valueInstantiator = vi;
_fullType = fullType;
- _valueDeserializer = deser;
+ _valueDeserializer = (JsonDeserializer<Object>) deser;
_valueTypeDeserializer = typeDeser;
}
- // NOTE: for forwards-compatibility; added in 2.8.5 since 2.9.0 has it
- public ReferenceTypeDeserializer(JavaType fullType, ValueInstantiator inst,
- TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
- this(fullType, typeDeser, deser);
+ @Deprecated // since 2.9
+ public ReferenceTypeDeserializer(JavaType fullType,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser)
+ {
+ this(fullType, null, typeDeser, deser);
}
@Override
- public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException
{
JsonDeserializer<?> deser = _valueDeserializer;
if (deser == null) {
@@ -68,6 +74,7 @@
if (typeDeser != null) {
typeDeser = typeDeser.forProperty(property);
}
+ // !!! 23-Oct-2016, tatu: TODO: full support for configurable ValueInstantiators?
if ((deser == _valueDeserializer) && (typeDeser == _valueTypeDeserializer)) {
return this;
}
@@ -76,16 +83,68 @@
/*
/**********************************************************
- /* Abstract methods for sub-classes to implement
+ /* Partial NullValueProvider impl
/**********************************************************
*/
- protected abstract ReferenceTypeDeserializer<T> withResolved(TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser);
+ /**
+ * Null value varies dynamically (unlike with scalar types),
+ * so let's indicate this.
+ */
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ return AccessPattern.DYNAMIC;
+ }
@Override
- public abstract T getNullValue(DeserializationContext ctxt);
+ public AccessPattern getEmptyAccessPattern() {
+ return AccessPattern.DYNAMIC;
+ }
+
+ /*
+ /**********************************************************
+ /* Abstract methods for sub-classes to implement
+ /**********************************************************
+ */
+
+ /**
+ * Mutant factory method called when changes are needed; should construct
+ * newly configured instance with new values as indicated.
+ *<p>
+ * NOTE: caller has verified that there are changes, so implementations
+ * need NOT check if a new instance is needed.
+ */
+ protected abstract ReferenceTypeDeserializer<T> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser);
+
+ @Override
+ public abstract T getNullValue(DeserializationContext ctxt) throws JsonMappingException;
+
+ @Override
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return getNullValue(ctxt);
+ }
public abstract T referenceValue(Object contents);
+
+ /**
+ * Method called in case of "merging update", in which we should try
+ * update reference instead of creating a new one. If this does not
+ * succeed, should just create a new instance.
+ *
+ * @since 2.9
+ */
+ public abstract T updateReference(T reference, Object contents);
+
+ /**
+ * Method that may be called to find contents of specified reference,
+ * if any; or `null` if none. Note that method should never fail, so
+ * for types that use concept of "absence" vs "presence", `null` is
+ * to be returned for both "absent" and "reference to `null`" cases.
+ *
+ * @since 2.9
+ */
+ public abstract Object getReferenced(T reference);
/*
/**********************************************************
@@ -96,14 +155,32 @@
@Override
public JavaType getValueType() { return _fullType; }
+ /**
+ * By default we assume that updateability mostly relies on value
+ * deserializer; if it supports updates, typically that's what
+ * matters. So let's just delegate.
+ */
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return (_valueDeserializer == null) ? null
+ : _valueDeserializer.supportsUpdate(config);
+ }
+
/*
/**********************************************************
/* Deserialization
/**********************************************************
*/
-
+
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ // 23-Oct-2016, tatu: ValueInstantiator only defined for non-vanilla instances,
+ // but do check... might work
+ if (_valueInstantiator != null) {
+ @SuppressWarnings("unchecked")
+ T value = (T) _valueInstantiator.createUsingDefault(ctxt);
+ return deserialize(p, ctxt, value);
+ }
Object contents = (_valueTypeDeserializer == null)
? _valueDeserializer.deserialize(p, ctxt)
: _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
@@ -111,6 +188,33 @@
}
@Override
+ public T deserialize(JsonParser p, DeserializationContext ctxt, T reference) throws IOException
+ {
+ Object contents;
+ // 26-Oct-2016, tatu: first things first; see if we should be able to merge:
+ Boolean B = _valueDeserializer.supportsUpdate(ctxt.getConfig());
+ // if explicitly stated that merge won't work...
+ if (B.equals(Boolean.FALSE) || (_valueTypeDeserializer != null)) {
+ contents = (_valueTypeDeserializer == null)
+ ? _valueDeserializer.deserialize(p, ctxt)
+ : _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ } else {
+ // Otherwise, see if we can merge the value
+ contents = getReferenced(reference);
+ // Whether to error or not... for now, just go back to default then
+ if (contents == null) {
+ contents = (_valueTypeDeserializer == null)
+ ? _valueDeserializer.deserialize(p, ctxt)
+ : _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ return referenceValue(contents);
+ } else {
+ contents = _valueDeserializer.deserialize(p, ctxt, contents);
+ }
+ }
+ return updateReference(reference, contents);
+ }
+
+ @Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) throws IOException
{
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 a5decbe..fcfba10 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
@@ -24,6 +24,7 @@
String className = "", methodName = "", fileName = "";
// Java 9 adds couple more things
String moduleName = null, moduleVersion = null;
+ String classLoaderName = null;
int lineNumber = -1;
while ((t = p.nextValue()) != JsonToken.END_OBJECT) {
@@ -31,6 +32,8 @@
// TODO: with Java 8, convert to switch
if ("className".equals(propName)) {
className = p.getText();
+ } else if ("classLoaderName".equals(propName)) {
+ classLoaderName = p.getText();
} else if ("fileName".equals(propName)) {
fileName = p.getText();
} else if ("lineNumber".equals(propName)) {
@@ -47,12 +50,17 @@
moduleName = p.getText();
} else if ("moduleVersion".equals(propName)) {
moduleVersion = p.getText();
+ } else if ("declaringClass".equals(propName)
+ || "format".equals(propName)) {
+ // 01-Nov-2017: [databind#1794] Not sure if we should but... let's prune it for now
+ ;
} else {
handleUnknownProperty(p, ctxt, _valueClass, propName);
}
+ p.skipChildren(); // just in case we might get structured values
}
return constructValue(ctxt, className, methodName, fileName, lineNumber,
- moduleName, moduleVersion);
+ moduleName, moduleVersion, classLoaderName);
} else if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken();
final StackTraceElement value = deserialize(p, ctxt);
@@ -64,6 +72,14 @@
return (StackTraceElement) ctxt.handleUnexpectedToken(_valueClass, p);
}
+ @Deprecated // since 2.9
+ protected StackTraceElement constructValue(DeserializationContext ctxt,
+ String className, String methodName, String fileName, int lineNumber,
+ String moduleName, String moduleVersion) {
+ return constructValue(ctxt, className, methodName, fileName, lineNumber,
+ moduleName, moduleVersion, null);
+ }
+
/**
* Overridable factory method used for constructing {@link StackTraceElement}s.
*
@@ -71,7 +87,7 @@
*/
protected StackTraceElement constructValue(DeserializationContext ctxt,
String className, String methodName, String fileName, int lineNumber,
- String moduleName, String moduleVersion)
+ String moduleName, String moduleVersion, String classLoaderName)
{
// 21-May-2016, tatu: With Java 9, need to use different constructor, probably
// via different module, and throw exception here if extra args passed
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 9876124..0b7e0ed 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.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
/**
@@ -37,6 +38,9 @@
{
private static final long serialVersionUID = 1L;
+ /**
+ * Converter that was used for creating {@link #_delegateDeserializer}.
+ */
protected final Converter<Object,T> _converter;
/**
@@ -92,9 +96,7 @@
protected StdDelegatingDeserializer<T> withDelegate(Converter<Object,T> converter,
JavaType delegateType, JsonDeserializer<?> delegateDeserializer)
{
- if (getClass() != StdDelegatingDeserializer.class) {
- throw new IllegalStateException("Sub-class "+getClass().getName()+" must override 'withDelegate'");
- }
+ ClassUtil.verifyMustOverride(StdDelegatingDeserializer.class, this, "withDelegate");
return new StdDelegatingDeserializer<T>(converter, delegateType, delegateDeserializer);
}
@@ -119,7 +121,7 @@
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException
{
- // First: if already got serializer to delegate to, contextualize it:
+ // First: if already got deserializer to delegate to, contextualize it:
if (_delegateDeserializer != null) {
JsonDeserializer<?> deser = ctxt.handleSecondaryContextualization(_delegateDeserializer,
property, _delegateType);
@@ -150,6 +152,11 @@
return _delegateDeserializer.handledType();
}
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return _delegateDeserializer.supportsUpdate(config);
+ }
+
/*
/**********************************************************
/* Serialization
@@ -213,7 +220,7 @@
throws IOException
{
throw new UnsupportedOperationException(String.format
- ("Can not update object of type %s (using deserializer for type %s)"
+ ("Cannot update object of type %s (using deserializer for type %s)"
+intoValue.getClass().getName(), _delegateType));
}
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 70d7417..9a6f482 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
@@ -4,12 +4,21 @@
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.io.NumberInput;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
+import com.fasterxml.jackson.databind.deser.NullValueProvider;
+import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
+import com.fasterxml.jackson.databind.deser.ValueInstantiator;
+import com.fasterxml.jackson.databind.deser.impl.NullsAsEmptyProvider;
+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.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
@@ -34,6 +43,12 @@
protected final static int F_MASK_INT_COERCIONS =
DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.getMask()
| DeserializationFeature.USE_LONG_FOR_INTS.getMask();
+
+ // @since 2.9
+ protected final static int F_MASK_ACCEPT_ARRAYS =
+ DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
+ DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
+
/**
* Type of values this deserializer handles: sometimes
@@ -48,7 +63,8 @@
}
protected StdDeserializer(JavaType valueType) {
- _valueClass = (valueType == null) ? null : valueType.getRawClass();
+ // 26-Sep-2017, tatu: [databind#1764] need to add null-check back until 3.x
+ _valueClass = (valueType == null) ? Object.class : valueType.getRawClass();
}
/**
@@ -83,7 +99,7 @@
public final Class<?> getValueClass() { return _valueClass; }
/**
- * Exact structured type deserializer handles, if known.
+ * Exact structured type this deserializer handles, if known.
*<p>
* Default implementation just returns null.
*/
@@ -119,7 +135,7 @@
TypeDeserializer typeDeserializer) throws IOException {
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
}
-
+
/*
/**********************************************************
/* Helper methods for sub-classes, parsing: while mostly
@@ -133,7 +149,10 @@
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_TRUE) return true;
if (t == JsonToken.VALUE_FALSE) return false;
- if (t == JsonToken.VALUE_NULL) return false;
+ if (t == JsonToken.VALUE_NULL) {
+ _verifyNullForPrimitive(ctxt);
+ return false;
+ }
// should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
@@ -146,80 +165,28 @@
if ("true".equals(text) || "True".equals(text)) {
return true;
}
- if ("false".equals(text) || "False".equals(text) || text.length() == 0) {
+ if ("false".equals(text) || "False".equals(text)) {
return false;
}
- if (_hasTextualNull(text)) {
+ if (_isEmptyOrTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
return false;
}
Boolean b = (Boolean) ctxt.handleWeirdStringValue(_valueClass, text,
"only \"true\" or \"false\" recognized");
- return (b == null) ? false : b.booleanValue();
+ 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(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
+ _verifyEndArrayForSingle(p, ctxt);
return parsed;
}
// Otherwise, no can do:
return ((Boolean) ctxt.handleUnexpectedToken(_valueClass, p)).booleanValue();
}
- protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt)
- throws IOException
- {
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_TRUE) {
- return Boolean.TRUE;
- }
- if (t == JsonToken.VALUE_FALSE) {
- return Boolean.FALSE;
- }
- // should accept ints too, (0 == false, otherwise true)
- if (t == JsonToken.VALUE_NUMBER_INT) {
- return Boolean.valueOf(_parseBooleanFromInt(p, ctxt));
- }
- if (t == JsonToken.VALUE_NULL) {
- return (Boolean) getNullValue(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)) {
- return Boolean.TRUE;
- }
- if ("false".equals(text) || "False".equals(text)) {
- return Boolean.FALSE;
- }
- if (text.length() == 0) {
- return (Boolean) getEmptyValue(ctxt);
- }
- if (_hasTextualNull(text)) {
- return (Boolean) getNullValue(ctxt);
- }
- return (Boolean) ctxt.handleWeirdStringValue(_valueClass, text,
- "only \"true\" or \"false\" recognized");
- }
- // [databind#381]
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Boolean parsed = _parseBoolean(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- // Otherwise, no can do:
- return (Boolean) ctxt.handleUnexpectedToken(_valueClass, p);
- }
-
protected boolean _parseBooleanFromInt(JsonParser p, DeserializationContext ctxt)
throws IOException
{
@@ -227,123 +194,23 @@
// 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.8.4
- protected boolean _parseBooleanFromOther(JsonParser p, DeserializationContext ctxt)
+ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- return _parseBooleanFromInt(p, ctxt);
- }
-
- protected Byte _parseByte(JsonParser p, DeserializationContext ctxt)
- throws IOException
- {
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_NUMBER_INT) {
- return p.getByteValue();
+ int value = _parseIntPrimitive(p, ctxt);
+ // So far so good: but does it fit?
+ if (_byteOverflow(value)) {
+ Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, String.valueOf(value),
+ "overflow, value cannot be represented as 8-bit value");
+ return _nonNullNumber(v).byteValue();
}
- if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
- String text = p.getText().trim();
- if (_hasTextualNull(text)) {
- return (Byte) getNullValue(ctxt);
- }
- int value;
- try {
- int len = text.length();
- if (len == 0) {
- return (Byte) getEmptyValue(ctxt);
- }
- 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?
- // as per [JACKSON-804], allow range up to 255, inclusive
- if (value < Byte.MIN_VALUE || value > 255) {
- return (Byte) ctxt.handleWeirdStringValue(_valueClass, text,
- "overflow, value can not 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");
- }
- return p.getByteValue();
- }
- if (t == JsonToken.VALUE_NULL) {
- return (Byte) getNullValue(ctxt);
- }
- // [databind#381]
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Byte parsed = _parseByte(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- return (Byte) ctxt.handleUnexpectedToken(_valueClass, p);
- }
-
- 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 value;
- try {
- int len = text.length();
- if (len == 0) {
- return (Short) getEmptyValue(ctxt);
- }
- if (_hasTextualNull(text)) {
- return (Short) getNullValue(ctxt);
- }
- 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 (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
- return (Short) ctxt.handleWeirdStringValue(_valueClass, text,
- "overflow, value can not be represented as 16-bit value");
- }
- return Short.valueOf((short) value);
- }
- if (t == JsonToken.VALUE_NUMBER_FLOAT) {
- if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
- _failDoubleToIntCoercion(p, ctxt, "Short");
- }
- return p.getShortValue();
- }
- if (t == JsonToken.VALUE_NULL) {
- return (Short) getNullValue(ctxt);
- }
- // [databind#381]
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Short parsed = _parseShort(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- return (Short) ctxt.handleUnexpectedToken(_valueClass, p);
+ return (byte) value;
}
protected final short _parseShortPrimitive(JsonParser p, DeserializationContext ctxt)
@@ -351,10 +218,10 @@
{
int value = _parseIntPrimitive(p, ctxt);
// So far so good: but does it fit?
- if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+ if (_shortOverflow(value)) {
Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, String.valueOf(value),
- "overflow, value can not be represented as 16-bit value");
- return (v == null) ? (short) 0 : v.shortValue();
+ "overflow, value cannot be represented as 16-bit value");
+ return _nonNullNumber(v).shortValue();
}
return (short) value;
}
@@ -365,189 +232,87 @@
if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
return p.getIntValue();
}
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
+ switch (p.getCurrentTokenId()) {
+ case JsonTokenId.ID_STRING:
String text = p.getText().trim();
- if (_hasTextualNull(text)) {
+ if (_isEmptyOrTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
return 0;
}
- try {
- int len = text.length();
- if (len > 9) {
- long l = Long.parseLong(text);
- if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "Overflow: numeric value (%s) out of range of int (%d -%d)",
- text, Integer.MIN_VALUE, Integer.MAX_VALUE);
- return (v == null) ? 0 : v.intValue();
- }
- return (int) l;
- }
- if (len == 0) {
- return 0;
- }
- return NumberInput.parseInt(text);
- } catch (IllegalArgumentException iae) {
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid int value");
- return (v == null) ? 0 : v.intValue();
- }
- }
- if (t == JsonToken.VALUE_NUMBER_FLOAT) {
+ return _parseIntPrimitive(ctxt, text);
+ case JsonTokenId.ID_NUMBER_FLOAT:
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
_failDoubleToIntCoercion(p, ctxt, "int");
}
return p.getValueAsInt();
- }
- if (t == JsonToken.VALUE_NULL) {
+ case JsonTokenId.ID_NULL:
+ _verifyNullForPrimitive(ctxt);
return 0;
- }
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final int parsed = _parseIntPrimitive(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
+ case JsonTokenId.ID_START_ARRAY:
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ p.nextToken();
+ final int parsed = _parseIntPrimitive(p, ctxt);
+ _verifyEndArrayForSingle(p, ctxt);
+ return parsed;
+ }
+ break;
+ default:
}
// Otherwise, no can do:
return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).intValue();
}
- protected final Integer _parseInteger(JsonParser p, DeserializationContext ctxt)
- throws IOException
+ /**
+ * @since 2.9
+ */
+ protected final int _parseIntPrimitive(DeserializationContext ctxt, String text) 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());
- 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();
- try {
- int len = text.length();
- if (_hasTextualNull(text)) {
- return (Integer) getNullValue(ctxt);
+ try {
+ if (text.length() > 9) {
+ long l = Long.parseLong(text);
+ if (_intOverflow(l)) {
+ Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
+ "Overflow: numeric value (%s) out of range of int (%d -%d)",
+ text, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ return _nonNullNumber(v).intValue();
}
- if (len > 9) {
- long l = Long.parseLong(text);
- if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
- return (Integer) ctxt.handleWeirdStringValue(_valueClass, text,
- "Overflow: numeric value ("+text+") out of range of Integer ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")");
- // fall-through
- }
- return Integer.valueOf((int) l);
- }
- if (len == 0) {
- return (Integer) getEmptyValue(ctxt);
- }
- return Integer.valueOf(NumberInput.parseInt(text));
- } catch (IllegalArgumentException iae) {
- return (Integer) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Integer value");
+ return (int) l;
}
- // fall-through
- case JsonTokenId.ID_NULL:
- return (Integer) getNullValue(ctxt);
- case JsonTokenId.ID_START_ARRAY:
- if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Integer parsed = _parseInteger(p, ctxt);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- break;
+ return NumberInput.parseInt(text);
+ } catch (IllegalArgumentException iae) {
+ Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid int value");
+ return _nonNullNumber(v).intValue();
}
- // Otherwise, no can do:
- return (Integer) ctxt.handleUnexpectedToken(_valueClass, p);
}
-
- 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();
- case JsonTokenId.ID_STRING:
- // let's allow Strings to be converted too
- // !!! 05-Jan-2009, tatu: Should we try to limit value space, JDK is too lenient?
- String text = p.getText().trim();
- if (text.length() == 0) {
- return (Long) getEmptyValue(ctxt);
- }
- if (_hasTextualNull(text)) {
- return (Long) getNullValue(ctxt);
- }
- 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) getNullValue(ctxt);
- case JsonTokenId.ID_START_ARRAY:
- if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Long parsed = _parseLong(p, ctxt);
- JsonToken t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- break;
- }
- // Otherwise, no can do:
- return (Long) ctxt.handleUnexpectedToken(_valueClass, p);
- }
-
+
protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- switch (p.getCurrentTokenId()) {
- case JsonTokenId.ID_NUMBER_INT:
+ if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
return p.getLongValue();
+ }
+ switch (p.getCurrentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ String text = p.getText().trim();
+ if (_isEmptyOrTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
+ return 0L;
+ }
+ return _parseLongPrimitive(ctxt, text);
case JsonTokenId.ID_NUMBER_FLOAT:
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)) {
_failDoubleToIntCoercion(p, ctxt, "long");
}
return p.getValueAsLong();
- case JsonTokenId.ID_STRING:
- String text = p.getText().trim();
- if (text.length() == 0 || _hasTextualNull(text)) {
- return 0L;
- }
- try {
- return NumberInput.parseLong(text);
- } catch (IllegalArgumentException iae) { }
- {
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid long value");
- return (v == null) ? 0 : v.longValue();
- }
case JsonTokenId.ID_NULL:
+ _verifyNullForPrimitive(ctxt);
return 0L;
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken();
final long parsed = _parseLongPrimitive(p, ctxt);
- JsonToken t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
+ _verifyEndArrayForSingle(p, ctxt);
return parsed;
}
break;
@@ -555,249 +320,192 @@
return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).longValue();
}
- protected final Float _parseFloat(JsonParser p, DeserializationContext ctxt)
- throws IOException
+ /**
+ * @since 2.9
+ */
+ protected final long _parseLongPrimitive(DeserializationContext ctxt, String text) throws IOException
{
- // We accept couple of different types; obvious ones first:
- JsonToken t = p.getCurrentToken();
-
- if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
- return p.getFloatValue();
+ try {
+ return NumberInput.parseLong(text);
+ } catch (IllegalArgumentException iae) { }
+ {
+ Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid long value");
+ return _nonNullNumber(v).longValue();
}
- // 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) getEmptyValue(ctxt);
- }
- if (_hasTextualNull(text)) {
- return (Float) getNullValue(ctxt);
- }
- 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;
- }
- 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) getNullValue(ctxt);
- }
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Float parsed = _parseFloat(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- // Otherwise, no can do:
- return (Float) ctxt.handleUnexpectedToken(_valueClass, p);
}
protected final float _parseFloatPrimitive(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
+ if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
return p.getFloatValue();
}
- if (t == JsonToken.VALUE_STRING) {
+ switch (p.getCurrentTokenId()) {
+ case JsonTokenId.ID_STRING:
String text = p.getText().trim();
- if (text.length() == 0 || _hasTextualNull(text)) {
+ if (_isEmptyOrTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
return 0.0f;
}
- 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;
- }
- try {
- return Float.parseFloat(text);
- } catch (IllegalArgumentException iae) { }
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid float value");
- return (v == null) ? 0 : v.floatValue();
- }
- if (t == JsonToken.VALUE_NULL) {
+ return _parseFloatPrimitive(ctxt, text);
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getFloatValue();
+ case JsonTokenId.ID_NULL:
+ _verifyNullForPrimitive(ctxt);
return 0.0f;
- }
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final float parsed = _parseFloatPrimitive(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
+ case JsonTokenId.ID_START_ARRAY:
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ p.nextToken();
+ final float parsed = _parseFloatPrimitive(p, ctxt);
+ _verifyEndArrayForSingle(p, ctxt);
+ return parsed;
+ }
+ break;
}
// Otherwise, no can do:
return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).floatValue();
}
- protected final Double _parseDouble(JsonParser p, DeserializationContext ctxt)
+ /**
+ * @since 2.9
+ */
+ protected final float _parseFloatPrimitive(DeserializationContext ctxt, String text)
throws IOException
{
- JsonToken t = p.getCurrentToken();
-
- if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
- return p.getDoubleValue();
- }
- if (t == JsonToken.VALUE_STRING) {
- String text = p.getText().trim();
- if (text.length() == 0) {
- return (Double) getEmptyValue(ctxt);
+ switch (text.charAt(0)) {
+ case 'I':
+ if (_isPosInf(text)) {
+ return Float.POSITIVE_INFINITY;
}
- if (_hasTextualNull(text)) {
- return (Double) getNullValue(ctxt);
+ break;
+ case 'N':
+ if (_isNaN(text)) { return Float.NaN; }
+ break;
+ case '-':
+ if (_isNegInf(text)) {
+ return Float.NEGATIVE_INFINITY;
}
- 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;
- }
- try {
- return parseDouble(text);
- } catch (IllegalArgumentException iae) { }
- return (Double) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid Double value");
+ break;
}
- if (t == JsonToken.VALUE_NULL) {
- return (Double) getNullValue(ctxt);
- }
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Double parsed = _parseDouble(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- // Otherwise, no can do:
- return (Double) ctxt.handleUnexpectedToken(_valueClass, p);
+ try {
+ return Float.parseFloat(text);
+ } catch (IllegalArgumentException iae) { }
+ Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid float value");
+ return _nonNullNumber(v).floatValue();
}
protected final double _parseDoublePrimitive(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- // We accept couple of different types; obvious ones first:
- JsonToken t = p.getCurrentToken();
-
- if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
+ if (p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
return p.getDoubleValue();
}
- // And finally, let's allow Strings to be converted too
- if (t == JsonToken.VALUE_STRING) {
+ switch (p.getCurrentTokenId()) {
+ case JsonTokenId.ID_STRING:
String text = p.getText().trim();
- if (text.length() == 0 || _hasTextualNull(text)) {
+ if (_isEmptyOrTextualNull(text)) {
+ _verifyNullForPrimitiveCoercion(ctxt, text);
return 0.0;
}
- 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;
- }
- try {
- return parseDouble(text);
- } catch (IllegalArgumentException iae) { }
- Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
- "not a valid double value");
- return (v == null) ? 0 : v.doubleValue();
- }
- if (t == JsonToken.VALUE_NULL) {
+ return _parseDoublePrimitive(ctxt, text);
+ case JsonTokenId.ID_NUMBER_INT:
+ return p.getDoubleValue();
+ case JsonTokenId.ID_NULL:
+ _verifyNullForPrimitive(ctxt);
return 0.0;
- }
- // [databind#381]
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final double parsed = _parseDoublePrimitive(p, ctxt);
- t = p.nextToken();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
+ case JsonTokenId.ID_START_ARRAY:
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ p.nextToken();
+ final double parsed = _parseDoublePrimitive(p, ctxt);
+ _verifyEndArrayForSingle(p, ctxt);
+ return parsed;
+ }
+ break;
}
// Otherwise, no can do:
return ((Number) ctxt.handleUnexpectedToken(_valueClass, p)).doubleValue();
}
+ /**
+ * @since 2.9
+ */
+ protected final double _parseDoublePrimitive(DeserializationContext ctxt, String text)
+ throws IOException
+ {
+ 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;
+ }
+ try {
+ return parseDouble(text);
+ } catch (IllegalArgumentException iae) { }
+ Number v = (Number) ctxt.handleWeirdStringValue(_valueClass, text,
+ "not a valid double value (as String to convert)");
+ return _nonNullNumber(v).doubleValue();
+ }
+
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- JsonToken t = p.getCurrentToken();
- if (t == JsonToken.VALUE_NUMBER_INT) {
- return new java.util.Date(p.getLongValue());
- }
- if (t == JsonToken.VALUE_NULL) {
- return (java.util.Date) getNullValue(ctxt);
- }
- if (t == JsonToken.VALUE_STRING) {
+ switch (p.getCurrentTokenId()) {
+ case JsonTokenId.ID_STRING:
return _parseDate(p.getText().trim(), ctxt);
+ case JsonTokenId.ID_NUMBER_INT:
+ {
+ long ts;
+ try {
+ ts = p.getLongValue();
+ } catch (JsonParseException e) {
+ Number v = (Number) ctxt.handleWeirdNumberValue(_valueClass, p.getNumberValue(),
+ "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);
+ case JsonTokenId.ID_START_ARRAY:
+ return _parseDateFromArray(p, ctxt);
}
- // [databind#381]
- if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- p.nextToken();
- final Date parsed = _parseDate(p, ctxt);
+ return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p);
+ }
+
+ // @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();
- if (t != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
+ if (t == JsonToken.END_ARRAY) {
+ if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
+ return (java.util.Date) getNullValue(ctxt);
+ }
+ }
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ final Date parsed = _parseDate(p, ctxt);
+ _verifyEndArrayForSingle(p, ctxt);
+ return parsed;
+ }
+ } else {
+ t = p.getCurrentToken();
}
- return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p);
+ return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
}
/**
@@ -808,16 +516,14 @@
{
try {
// Take empty Strings to mean 'empty' Value, usually 'null':
- if (value.length() == 0) {
- return (Date) getEmptyValue(ctxt);
- }
- if (_hasTextualNull(value)) {
+ if (_isEmptyOrTextualNull(value)) {
return (java.util.Date) getNullValue(ctxt);
}
return ctxt.parseDate(value);
} catch (IllegalArgumentException iae) {
return (java.util.Date) ctxt.handleWeirdStringValue(_valueClass, value,
- "not a valid representation (error: %s)", iae.getMessage());
+ "not a valid representation (error: %s)",
+ ClassUtil.exceptionMessage(iae));
}
}
@@ -846,15 +552,17 @@
if (t == JsonToken.VALUE_STRING) {
return p.getText();
}
+ // 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);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
+ _verifyEndArrayForSingle(p, ctxt);
return parsed;
}
+ */
String value = p.getValueAsString();
if (value != null) {
return value;
@@ -903,6 +611,13 @@
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);
}
@@ -914,11 +629,91 @@
protected final boolean _isNaN(String text) { return "NaN".equals(text); }
/*
+ /**********************************************************
+ /* Helper methods for sub-classes regarding decoding from
+ /* alternate representations
+ /**********************************************************
+ */
+
+ /**
+ * 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
+ */
+ protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) 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;
+ }
+ } else {
+ t = p.getCurrentToken();
+ }
+ @SuppressWarnings("unchecked")
+ T result = (T) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
+ return result;
+ }
+
+ /**
+ * 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(_valueClass, p.getCurrentToken(), p, msg);
+ return result;
+ }
+ return (T) deserialize(p, ctxt);
+ }
+
+ /*
/****************************************************
/* Helper methods for sub-classes, coercions
/****************************************************
*/
+ 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);
+ }
+
/**
* Helper method called in case where an integral number is encountered, but
* config settings suggest that a coercion may be needed to "upgrade"
@@ -941,7 +736,168 @@
}
return p.getBigIntegerValue(); // 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
+ *
+ * @since 2.9
+ */
+ protected Object _coerceNullToken(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
+ {
+ if (isPrimitive) {
+ _verifyNullForPrimitive(ctxt);
+ }
+ return getNullValue(ctxt);
+ }
+
+ /**
+ * Method called when JSON String with value "null" is encountered.
+ *
+ * @since 2.9
+ */
+ protected Object _coerceTextualNull(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
+ {
+ Enum<?> feat;
+ boolean enable;
+
+ 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;
+ } else {
+ return getNullValue(ctxt);
+ }
+ _reportFailedNullCoerce(ctxt, enable, feat, "String \"null\"");
+ return null;
+ }
+
+ /**
+ * Method called when JSON String with value "" (that is, zero length) is encountered.
+ *
+ * @since 2.9
+ */
+ protected Object _coerceEmptyString(DeserializationContext ctxt, boolean isPrimitive) throws JsonMappingException
+ {
+ Enum<?> feat;
+ boolean enable;
+
+ 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;
+ } else {
+ return getNullValue(ctxt);
+ }
+ _reportFailedNullCoerce(ctxt, enable, feat, "empty String (\"\")");
+ return null;
+ }
+
+ // @since 2.9
+ protected final void _verifyNullForPrimitive(DeserializationContext ctxt) throws JsonMappingException
+ {
+ 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());
+ }
+ }
+
+ // NOTE: only for primitive Scalars
+ // @since 2.9
+ protected final void _verifyNullForPrimitiveCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
+ {
+ Enum<?> feat;
+ boolean enable;
+
+ 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)) {
+ feat = DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES;
+ enable = false;
+ } else {
+ return;
+ }
+ String strDesc = str.isEmpty() ? "empty String (\"\")" : String.format("String \"%s\"", str);
+ _reportFailedNullCoerce(ctxt, enable, feat, strDesc);
+ }
+
+ // NOTE: for non-primitive Scalars
+ // @since 2.9
+ protected final void _verifyNullForScalarCoercion(DeserializationContext ctxt, String str) throws JsonMappingException
+ {
+ if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
+ String strDesc = str.isEmpty() ? "empty String (\"\")" : String.format("String \"%s\"", str);
+ _reportFailedNullCoerce(ctxt, true, MapperFeature.ALLOW_COERCION_OF_SCALARS, strDesc);
+ }
+ }
+
+ // @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
+ protected void _verifyNumberForScalarCoercion(DeserializationContext ctxt, JsonParser p) throws IOException
+ {
+ MapperFeature feat = MapperFeature.ALLOW_COERCION_OF_SCALARS;
+ if (!ctxt.isEnabled(feat)) {
+ // 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)",
+ valueDesc, _coercedTypeDesc(), feat.getClass().getSimpleName(), feat.name());
+ }
+ }
+ 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 %s (%s `%s.%s` to allow)",
+ inputDesc, _coercedTypeDesc(), enableDesc, feature.getClass().getSimpleName(), feature.name());
+ }
+
+ /**
+ * 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;
+ }
+ return "for type "+typeDesc;
+ }
+
/*
/****************************************************
/* Helper methods for sub-classes, resolving dependencies
@@ -988,10 +944,10 @@
/*
/**********************************************************
- /* Helper methods for sub-classes, deserializer construction
+ /* Helper methods for: deserializer construction
/**********************************************************
*/
-
+
/**
* Helper method that can be used to see if specified property has annotation
* indicating that a converter is to be used for contained values (contents
@@ -1007,7 +963,7 @@
throws JsonMappingException
{
final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
- if (intr != null && prop != null) {
+ if (_neitherNull(intr, prop)) {
AnnotatedMember member = prop.getMember();
if (member != null) {
Object convDef = intr.findDeserializationContentConverter(member);
@@ -1024,6 +980,12 @@
return existingDeserializer;
}
+ /*
+ /**********************************************************
+ /* Helper methods for: accessing contextual config settings
+ /**********************************************************
+ */
+
/**
* Helper method that may be used to find if this deserializer has specific
* {@link JsonFormat} settings, either via property, or through type-specific
@@ -1063,6 +1025,103 @@
return null;
}
+ /**
+ * Method called to find {@link NullValueProvider} for a primary property, using
+ * "value nulls" setting. If no provider found (not defined, or is "skip"),
+ * will return `null`.
+ *
+ * @since 2.9
+ */
+ protected final NullValueProvider findValueNullProvider(DeserializationContext ctxt,
+ SettableBeanProperty prop, PropertyMetadata propMetadata)
+ throws JsonMappingException
+ {
+ if (prop != null) {
+ return _findNullProvider(ctxt, prop, propMetadata.getValueNulls(),
+ prop.getValueDeserializer());
+ }
+ return null;
+ }
+
+ /**
+ * Method called to find {@link NullValueProvider} for a contents of a structured
+ * primary property (Collection, Map, array), using
+ * "content nulls" setting. If no provider found (not defined),
+ * will return given value deserializer (which is a null value provider itself).
+ *
+ * @since 2.9
+ */
+ protected NullValueProvider findContentNullProvider(DeserializationContext ctxt,
+ BeanProperty prop, JsonDeserializer<?> valueDeser)
+ throws JsonMappingException
+ {
+ final Nulls nulls = findContentNullStyle(ctxt, prop);
+ if (nulls == Nulls.SKIP) {
+ return NullsConstantProvider.skipper();
+ }
+ NullValueProvider prov = _findNullProvider(ctxt, prop, nulls, valueDeser);
+ if (prov != null) {
+ return prov;
+ }
+ return valueDeser;
+ }
+
+ protected Nulls findContentNullStyle(DeserializationContext ctxt, BeanProperty prop)
+ throws JsonMappingException
+ {
+ if (prop != null) {
+ return prop.getMetadata().getContentNulls();
+ }
+ return null;
+ }
+
+ // @since 2.9
+ protected final NullValueProvider _findNullProvider(DeserializationContext ctxt,
+ BeanProperty prop, Nulls nulls, JsonDeserializer<?> valueDeser)
+ throws JsonMappingException
+ {
+ if (nulls == Nulls.FAIL) {
+ if (prop == null) {
+ return NullsFailProvider.constructForRootValue(ctxt.constructType(valueDeser.handledType()));
+ }
+ return NullsFailProvider.constructForProperty(prop);
+ }
+ if (nulls == Nulls.AS_EMPTY) {
+ // cannot deal with empty values if there is no value deserializer that
+ // can indicate what "empty value" is:
+ if (valueDeser == null) {
+ return null;
+ }
+
+ // Let's first do some sanity checking...
+ // NOTE: although we could use `ValueInstantiator.Gettable` in general,
+ // let's not since that would prevent being able to use custom impls:
+ if (valueDeser instanceof BeanDeserializerBase) {
+ ValueInstantiator vi = ((BeanDeserializerBase) valueDeser).getValueInstantiator();
+ if (!vi.canCreateUsingDefault()) {
+ final JavaType type = prop.getType();
+ ctxt.reportBadDefinition(type,
+ String.format("Cannot create empty instance of %s, no default Creator", type));
+ }
+ }
+ // Second: can with pre-fetch value?
+ {
+ AccessPattern access = valueDeser.getEmptyAccessPattern();
+ if (access == AccessPattern.ALWAYS_NULL) {
+ return NullsConstantProvider.nuller();
+ }
+ if (access == AccessPattern.CONSTANT) {
+ return NullsConstantProvider.forValue(valueDeser.getEmptyValue(ctxt));
+ }
+ }
+ return new NullsAsEmptyProvider(valueDeser);
+ }
+ if (nulls == Nulls.SKIP) {
+ return NullsConstantProvider.skipper();
+ }
+ return null;
+ }
+
/*
/**********************************************************
/* Helper methods for sub-classes, problem reporting
@@ -1081,9 +1140,10 @@
* @param instanceOrClass Instance that is being populated by this
* deserializer, or if not known, Class that would be instantiated.
* If null, will assume type is what {@link #getValueClass} returns.
- * @param propName Name of the property that can not be mapped
+ * @param propName Name of the property that cannot be mapped
*/
- protected void handleUnknownProperty(JsonParser p, DeserializationContext ctxt, Object instanceOrClass, String propName)
+ protected void handleUnknownProperty(JsonParser p, DeserializationContext ctxt,
+ Object instanceOrClass, String propName)
throws IOException
{
if (instanceOrClass == null) {
@@ -1102,17 +1162,64 @@
protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- ctxt.reportWrongTokenException(p, JsonToken.END_ARRAY,
-"Attempted to unwrap single value array for single '%s' value but there was more than a single value in the array",
+ ctxt.reportWrongTokenException(this, JsonToken.END_ARRAY,
+"Attempted to unwrap '%s' value from an array (with `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS`) but it contains more than one value",
handledType().getName());
// 05-May-2016, tatu: Should recover somehow (maybe skip until END_ARRAY);
// but for now just fall through
}
- protected void _failDoubleToIntCoercion(JsonParser p, DeserializationContext ctxt,
- String type) throws IOException
+ protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
{
- ctxt.reportMappingException("Can not coerce a floating-point value ('%s') into %s; enable `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to allow",
- p.getValueAsString(), type);
+ JsonToken t = p.nextToken();
+ if (t != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods, other
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ protected final static boolean _neitherNull(Object a, Object b) {
+ return (a != null) && (b != null);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected final boolean _byteOverflow(int value) {
+ // 07-nov-2016, tatu: We support "unsigned byte" as well
+ // as Java signed range since that's relatively common usage
+ return (value < Byte.MIN_VALUE || value > 255);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected final boolean _shortOverflow(int value) {
+ return (value < Short.MIN_VALUE || value > Short.MAX_VALUE);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected final boolean _intOverflow(long value) {
+ return (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected Number _nonNullNumber(Number n) {
+ if (n == null) {
+ n = Integer.valueOf(0);
+ }
+ return n;
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java
index de6b23b..862545b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java
@@ -47,6 +47,7 @@
public final static int TYPE_URL = 14;
public final static int TYPE_CLASS = 15;
public final static int TYPE_CURRENCY = 16;
+ public final static int TYPE_BYTE_ARRAY = 17; // since 2.9
final protected int _kind;
final protected Class<?> _keyClass;
@@ -108,6 +109,8 @@
} else if (raw == Currency.class) {
FromStringDeserializer<?> deser = FromStringDeserializer.findDeserializer(Currency.class);
return new StdKeyDeserializer(TYPE_CURRENCY, raw, deser);
+ } else if (raw == byte[].class) {
+ kind = TYPE_BYTE_ARRAY;
} else {
return null;
}
@@ -128,7 +131,8 @@
}
} catch (Exception re) {
return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation, problem: (%s) %s",
- re.getClass().getName(), re.getMessage());
+ re.getClass().getName(),
+ ClassUtil.exceptionMessage(re));
}
if (_keyClass.isEnum() && ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return null;
@@ -154,7 +158,7 @@
int value = _parseInt(key);
// allow range up to 255, inclusive (to support "unsigned" byte)
if (value < Byte.MIN_VALUE || value > 255) {
- return ctxt.handleWeirdKey(_keyClass, key, "overflow, value can not be represented as 8-bit value");
+ return ctxt.handleWeirdKey(_keyClass, key, "overflow, value cannot be represented as 8-bit value");
}
return Byte.valueOf((byte) value);
}
@@ -162,7 +166,7 @@
{
int value = _parseInt(key);
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
- return ctxt.handleWeirdKey(_keyClass, key, "overflow, value can not be represented as 16-bit value");
+ return ctxt.handleWeirdKey(_keyClass, key, "overflow, value cannot be represented as 16-bit value");
// fall-through and truncate if need be
}
return Short.valueOf((short) value);
@@ -186,37 +190,36 @@
case TYPE_LOCALE:
try {
return _deser._deserialize(key, ctxt);
- } catch (IOException e) {
- return ctxt.handleWeirdKey(_keyClass, key, "unable to parse key as locale");
+ } catch (IllegalArgumentException e) {
+ return _weirdKey(ctxt, key, e);
}
case TYPE_CURRENCY:
try {
return _deser._deserialize(key, ctxt);
- } catch (IOException e) {
- return ctxt.handleWeirdKey(_keyClass, key, "unable to parse key as currency");
+ } catch (IllegalArgumentException e) {
+ return _weirdKey(ctxt, key, e);
}
case TYPE_DATE:
return ctxt.parseDate(key);
case TYPE_CALENDAR:
- java.util.Date date = ctxt.parseDate(key);
- return (date == null) ? null : ctxt.constructCalendar(date);
+ return ctxt.constructCalendar(ctxt.parseDate(key));
case TYPE_UUID:
try {
return UUID.fromString(key);
} catch (Exception e) {
- return ctxt.handleWeirdKey(_keyClass, key, "problem: %s", e.getMessage());
+ return _weirdKey(ctxt, key, e);
}
case TYPE_URI:
try {
return URI.create(key);
} catch (Exception e) {
- return ctxt.handleWeirdKey(_keyClass, key, "problem: %s", e.getMessage());
+ return _weirdKey(ctxt, key, e);
}
case TYPE_URL:
try {
return new URL(key);
} catch (MalformedURLException e) {
- return ctxt.handleWeirdKey(_keyClass, key, "problem: %s", e.getMessage());
+ return _weirdKey(ctxt, key, e);
}
case TYPE_CLASS:
try {
@@ -224,6 +227,12 @@
} catch (Exception e) {
return ctxt.handleWeirdKey(_keyClass, key, "unable to parse key as Class");
}
+ case TYPE_BYTE_ARRAY:
+ try {
+ return ctxt.getConfig().getBase64Variant().decode(key);
+ } catch (IllegalArgumentException e) {
+ return _weirdKey(ctxt, key, e);
+ }
default:
throw new IllegalStateException("Internal error: unknown key type "+_keyClass);
}
@@ -247,6 +256,12 @@
return NumberInput.parseDouble(key);
}
+ // @since 2.9
+ protected Object _weirdKey(DeserializationContext ctxt, String key, Exception e) throws IOException {
+ return ctxt.handleWeirdKey(_keyClass, key, "problem: %s",
+ ClassUtil.exceptionMessage(e));
+ }
+
/*
/**********************************************************
/* First: the standard "String as String" deserializer
@@ -331,7 +346,7 @@
public Class<?> getKeyClass() { return _keyClass; }
}
-
+
@JacksonStdImpl
final static class EnumKD extends StdKeyDeserializer
{
@@ -348,11 +363,14 @@
* @since 2.7.3
*/
protected EnumResolver _byToStringResolver;
+
+ protected final Enum<?> _enumDefaultValue;
protected EnumKD(EnumResolver er, AnnotatedMethod factory) {
super(-1, er.getEnumClass());
_byNameResolver = er;
_factory = factory;
+ _enumDefaultValue = er.getDefaultValue();
}
@Override
@@ -368,9 +386,14 @@
EnumResolver res = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
? _getToStringResolver(ctxt) : _byNameResolver;
Enum<?> e = res.findEnum(key);
- if ((e == null) && !ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
- return ctxt.handleWeirdKey(_keyClass, key, "not one of values excepted for Enum class: %s",
+ if (e == null) {
+ if ((_enumDefaultValue != null)
+ && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
+ e = _enumDefaultValue;
+ } else if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
+ return ctxt.handleWeirdKey(_keyClass, key, "not one of values excepted for Enum class: %s",
res.getEnumIds());
+ }
// fall-through if problems are collected, not immediately thrown
}
return e;
@@ -383,6 +406,7 @@
synchronized (this) {
res = EnumResolver.constructUnsafeUsingToString(_byNameResolver.getEnumClass(),
ctxt.getAnnotationIntrospector());
+ _byToStringResolver = res;
}
}
return res;
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 f75d101..1e719e4 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
@@ -3,10 +3,9 @@
import java.io.IOException;
import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.util.AccessPattern;
/**
* Base class for deserializers that handle types that are serialized
@@ -14,11 +13,6 @@
*/
public abstract class StdScalarDeserializer<T> extends StdDeserializer<T>
{
- // @since 2.8.8
- protected final static int FEATURES_ACCEPT_ARRAYS =
- DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
- DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
-
private static final long serialVersionUID = 1L;
protected StdScalarDeserializer(Class<?> vc) { super(vc); }
@@ -32,28 +26,37 @@
return typeDeserializer.deserializeTypedFromScalar(p, ctxt);
}
- protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
- {
- JsonToken t;
- if (ctxt.hasSomeOfFeatures(FEATURES_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;
- }
- } else {
- t = p.getCurrentToken();
- }
- @SuppressWarnings("unchecked")
- T result = (T) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
- return result;
+ /**
+ * 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.reportBadMerge(this);
+ // except that it is possible to suppress this; and if so...
+ return deserialize(p, ctxt);
+ }
+
+ /**
+ * By default assumption is that scalar types cannot be updated: many are immutable
+ * values (such as primitives and wrappers)
+ */
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return Boolean.FALSE;
+ }
+
+ // Typically Scalar values have default setting of "nulls as nulls"
+ @Override
+ public AccessPattern getNullAccessPattern() {
+ return AccessPattern.ALWAYS_NULL;
+ }
+
+ // While some scalar types have non-null empty values (hence can't say "ALWAYS_NULL")
+ // they are mostly immutable, shareable and so constant.
+ @Override // since 2.9
+ public AccessPattern getEmptyAccessPattern() {
+ return AccessPattern.CONSTANT;
}
}
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 9705770..919c963 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
@@ -8,6 +8,7 @@
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;
/**
* Default {@link ValueInstantiator} implementation, which supports
@@ -79,7 +80,7 @@
*/
@Deprecated
public StdValueInstantiator(DeserializationConfig config, Class<?> valueType) {
- _valueTypeDesc = (valueType == null) ? "UNKNOWN TYPE" : valueType.getName();
+ _valueTypeDesc = ClassUtil.nameOf(valueType);
_valueClass = (valueType == null) ? Object.class : valueType;
}
@@ -267,9 +268,8 @@
}
try {
return _defaultCreator.call();
- } catch (Throwable t) {
- return ctxt.handleInstantiationProblem(_defaultCreator.getDeclaringClass(),
- null, rewrapCtorProblem(ctxt, t));
+ } catch (Exception e) { // 19-Apr-2017, tatu: Let's not catch Errors, just Exceptions
+ return ctxt.handleInstantiationProblem(_valueClass, null, rewrapCtorProblem(ctxt, e));
}
}
@@ -281,9 +281,8 @@
}
try {
return _withArgsCreator.call(args);
- } catch (Throwable t) {
- return ctxt.handleInstantiationProblem(_withArgsCreator.getDeclaringClass(),
- args, rewrapCtorProblem(ctxt, t));
+ } catch (Exception e) { // 19-Apr-2017, tatu: Let's not catch Errors, just Exceptions
+ return ctxt.handleInstantiationProblem(_valueClass, args, rewrapCtorProblem(ctxt, e));
}
}
@@ -454,7 +453,7 @@
}
}
return new JsonMappingException(null,
- "Instantiation of "+getValueTypeDesc()+" value failed: "+t.getMessage(), t);
+ "Instantiation of "+getValueTypeDesc()+" value failed: "+ClassUtil.exceptionMessage(t), t);
}
/**
@@ -510,8 +509,7 @@
/**********************************************************
*/
- private Object _createUsingDelegate(
- AnnotatedWithParams delegateCreator,
+ private Object _createUsingDelegate(AnnotatedWithParams delegateCreator,
SettableBeanProperty[] delegateArguments,
DeserializationContext ctxt,
Object delegate)
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 38f319f..a348a40 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
@@ -7,7 +7,10 @@
import com.fasterxml.jackson.databind.*;
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.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ObjectBuffer;
/**
@@ -17,11 +20,16 @@
*/
@JacksonStdImpl
public final class StringArrayDeserializer
+// 28-Oct-2016, tatu: Should do this:
+// extends ContainerDeserializerBase<String[]>
+// but for now won't:
extends StdDeserializer<String[]>
implements ContextualDeserializer
{
private static final long serialVersionUID = 2L;
+ private final static String[] NO_STRINGS = new String[0];
+
public final static StringArrayDeserializer instance = new StringArrayDeserializer();
/**
@@ -30,6 +38,13 @@
protected JsonDeserializer<String> _elementDeserializer;
/**
+ * Handler we need for dealing with nulls.
+ *
+ * @since 2.9
+ */
+ protected final NullValueProvider _nullProvider;
+
+ /**
* Specific override for this instance (from proper, or global per-type overrides)
* to indicate whether single value may be taken to mean an unwrapped one-element array
* or not. If null, left to global defaults.
@@ -38,15 +53,42 @@
*/
protected final Boolean _unwrapSingle;
+ /**
+ * Marker flag set if the <code>_nullProvider</code> indicates that all null
+ * content values should be skipped (instead of being possibly converted).
+ *
+ * @since 2.9
+ */
+ protected final boolean _skipNullValues;
+
public StringArrayDeserializer() {
- this(null, null);
+ this(null, null, null);
}
@SuppressWarnings("unchecked")
- protected StringArrayDeserializer(JsonDeserializer<?> deser, Boolean unwrapSingle) {
+ protected StringArrayDeserializer(JsonDeserializer<?> deser,
+ NullValueProvider nuller, Boolean unwrapSingle) {
super(String[].class);
_elementDeserializer = (JsonDeserializer<String>) deser;
+ _nullProvider = nuller;
_unwrapSingle = unwrapSingle;
+ _skipNullValues = NullsConstantProvider.isSkipper(nuller);
+ }
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ return Boolean.TRUE;
+ }
+
+ @Override // since 2.9
+ public AccessPattern getEmptyAccessPattern() {
+ // immutable, shareable so:
+ return AccessPattern.CONSTANT;
+ }
+
+ @Override // since 2.9
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return NO_STRINGS;
}
/**
@@ -54,7 +96,8 @@
* of String values, or if we have to use separate value deserializer.
*/
@Override
- public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException
{
JsonDeserializer<?> deser = _elementDeserializer;
// May have a content converter
@@ -68,14 +111,17 @@
// One more thing: allow unwrapping?
Boolean unwrapSingle = findFormatFeature(ctxt, property, String[].class,
JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ NullValueProvider nuller = findContentNullProvider(ctxt, property, deser);
// Ok ok: if all we got is the default String deserializer, can just forget about it
if ((deser != null) && isDefaultDeserializer(deser)) {
deser = null;
}
- if ((_elementDeserializer == deser) && (_unwrapSingle == unwrapSingle)) {
+ if ((_elementDeserializer == deser)
+ && (_unwrapSingle == unwrapSingle)
+ && (_nullProvider == nuller)) {
return this;
}
- return new StringArrayDeserializer(deser, unwrapSingle);
+ return new StringArrayDeserializer(deser, nuller, unwrapSingle);
}
@Override
@@ -86,7 +132,7 @@
return handleNonArray(p, ctxt);
}
if (_elementDeserializer != null) {
- return _deserializeCustom(p, ctxt);
+ return _deserializeCustom(p, ctxt, null);
}
final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
@@ -102,7 +148,12 @@
if (t == JsonToken.END_ARRAY) {
break;
}
- if (t != JsonToken.VALUE_NULL) {
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ value = (String) _nullProvider.getNullValue(ctxt);
+ } else {
value = _parseString(p, ctxt);
}
}
@@ -123,13 +174,22 @@
/**
* Offlined version used when we do not use the default deserialization method.
*/
- protected final String[] _deserializeCustom(JsonParser p, DeserializationContext ctxt) throws IOException
+ protected final String[] _deserializeCustom(JsonParser p, DeserializationContext ctxt,
+ String[] old) throws IOException
{
final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
- Object[] chunk = buffer.resetAndStart();
- final JsonDeserializer<String> deser = _elementDeserializer;
+ int ix;
+ Object[] chunk;
+
+ if (old == null) {
+ ix = 0;
+ chunk = buffer.resetAndStart();
+ } else {
+ ix = old.length;
+ chunk = buffer.resetAndStart(old, ix);
+ }
- int ix = 0;
+ final JsonDeserializer<String> deser = _elementDeserializer;
try {
while (true) {
@@ -145,7 +205,14 @@
break;
}
// Ok: no need to convert Strings, but must recognize nulls
- value = (t == JsonToken.VALUE_NULL) ? deser.getNullValue(ctxt) : deser.deserialize(p, ctxt);
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ value = (String) _nullProvider.getNullValue(ctxt);
+ } else {
+ value = deser.deserialize(p, ctxt);
+ }
} else {
value = deser.deserialize(p, ctxt);
}
@@ -163,12 +230,68 @@
ctxt.returnObjectBuffer(buffer);
return result;
}
-
+
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
return typeDeserializer.deserializeTypedFromArray(p, ctxt);
}
+ @Override
+ public String[] deserialize(JsonParser p, DeserializationContext ctxt,
+ String[] intoValue) throws IOException
+ {
+ // Ok: must point to START_ARRAY (or equivalent)
+ if (!p.isExpectedStartArrayToken()) {
+ String[] arr = handleNonArray(p, ctxt);
+ if (arr == null) {
+ return intoValue;
+ }
+ final int offset = intoValue.length;
+ String[] result = new String[offset + arr.length];
+ System.arraycopy(intoValue, 0, result, 0, offset);
+ System.arraycopy(arr, 0, result, offset, arr.length);
+ return result;
+ }
+
+ if (_elementDeserializer != null) {
+ return _deserializeCustom(p, ctxt, intoValue);
+ }
+ final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
+ int ix = intoValue.length;
+ Object[] chunk = buffer.resetAndStart(intoValue, ix);
+
+ try {
+ while (true) {
+ String value = p.nextTextValue();
+ if (value == null) {
+ JsonToken t = p.getCurrentToken();
+ if (t == JsonToken.END_ARRAY) {
+ break;
+ }
+ if (t == JsonToken.VALUE_NULL) {
+ // 03-Feb-2017, tatu: Should we skip null here or not?
+ if (_skipNullValues) {
+ return NO_STRINGS;
+ }
+ value = (String) _nullProvider.getNullValue(ctxt);
+ } else {
+ value = _parseString(p, ctxt);
+ }
+ }
+ if (ix >= chunk.length) {
+ chunk = buffer.appendCompletedChunk(chunk);
+ ix = 0;
+ }
+ chunk[ix++] = value;
+ }
+ } catch (Exception e) {
+ throw JsonMappingException.wrapWithPath(e, chunk, buffer.bufferedSize() + ix);
+ }
+ String[] result = buffer.completeAndClearBuffer(chunk, ix, String.class);
+ ctxt.returnObjectBuffer(buffer);
+ return result;
+ }
+
private final String[] handleNonArray(JsonParser p, DeserializationContext ctxt) throws IOException
{
// implicit arrays from single values?
@@ -176,8 +299,12 @@
((_unwrapSingle == null) &&
ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
if (canWrap) {
- return new String[] { p.hasToken(JsonToken.VALUE_NULL) ? null : _parseString(p, ctxt) };
- } else if (p.hasToken(JsonToken.VALUE_STRING)
+ String value = p.hasToken(JsonToken.VALUE_NULL)
+ ? (String) _nullProvider.getNullValue(ctxt)
+ : _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) {
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 0ef9714..0bf2e12 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
@@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.*;
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.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
@@ -26,8 +27,6 @@
// // Configuration
- protected final JavaType _collectionType;
-
/**
* Value deserializer to use, if NOT the standard one
* (if it is, will be null).
@@ -47,15 +46,6 @@
*/
protected final JsonDeserializer<Object> _delegateDeserializer;
- /**
- * Specific override for this instance (from proper, or global per-type overrides)
- * to indicate whether single value may be taken to mean an unwrapped one-element array
- * or not. If null, left to global defaults.
- *
- * @since 2.7
- */
- protected final Boolean _unwrapSingle;
-
// NOTE: no PropertyBasedCreator, as JSON Arrays have no properties
/*
@@ -67,36 +57,37 @@
public StringCollectionDeserializer(JavaType collectionType,
JsonDeserializer<?> valueDeser, ValueInstantiator valueInstantiator)
{
- this(collectionType, valueInstantiator, null, valueDeser, null);
+ this(collectionType, valueInstantiator, null, valueDeser, valueDeser, null);
}
@SuppressWarnings("unchecked")
protected StringCollectionDeserializer(JavaType collectionType,
ValueInstantiator valueInstantiator, JsonDeserializer<?> delegateDeser,
- JsonDeserializer<?> valueDeser, Boolean unwrapSingle)
+ JsonDeserializer<?> valueDeser,
+ NullValueProvider nuller, Boolean unwrapSingle)
{
- super(collectionType);
- _collectionType = collectionType;
+ super(collectionType, nuller, unwrapSingle);
_valueDeserializer = (JsonDeserializer<String>) valueDeser;
_valueInstantiator = valueInstantiator;
_delegateDeserializer = (JsonDeserializer<Object>) delegateDeser;
- _unwrapSingle = unwrapSingle;
}
protected StringCollectionDeserializer withResolved(JsonDeserializer<?> delegateDeser,
- JsonDeserializer<?> valueDeser, Boolean unwrapSingle)
+ JsonDeserializer<?> valueDeser,
+ NullValueProvider nuller, Boolean unwrapSingle)
{
- if ((_unwrapSingle == unwrapSingle)
+ if ((_unwrapSingle == unwrapSingle) && (_nullProvider == nuller)
&& (_valueDeserializer == valueDeser) && (_delegateDeserializer == delegateDeser)) {
return this;
}
- return new StringCollectionDeserializer(_collectionType,
- _valueInstantiator, delegateDeser, valueDeser, unwrapSingle);
+ return new StringCollectionDeserializer(_containerType, _valueInstantiator,
+ delegateDeser, valueDeser, nuller, unwrapSingle);
}
@Override // since 2.5
public boolean isCachable() {
- // 26-Mar-2015, tatu: Important: prevent caching if custom deserializers are involved
+ // 26-Mar-2015, tatu: Important: prevent caching if custom deserializers via annotations
+ // are involved
return (_valueDeserializer == null) && (_delegateDeserializer == null);
}
@@ -112,14 +103,18 @@
// May need to resolve types for delegate-based creators:
JsonDeserializer<Object> delegate = null;
if (_valueInstantiator != null) {
- AnnotatedWithParams delegateCreator = _valueInstantiator.getDelegateCreator();
+ // [databind#2324]: check both array-delegating and delegating
+ AnnotatedWithParams delegateCreator = _valueInstantiator.getArrayDelegateCreator();
if (delegateCreator != null) {
+ JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
+ delegate = findDeserializer(ctxt, delegateType, property);
+ } else if ((delegateCreator = _valueInstantiator.getDelegateCreator()) != null) {
JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
delegate = findDeserializer(ctxt, delegateType, property);
}
}
JsonDeserializer<?> valueDeser = _valueDeserializer;
- final JavaType valueType = _collectionType.getContentType();
+ final JavaType valueType = _containerType.getContentType();
if (valueDeser == null) {
// [databind#125]: May have a content converter
valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser);
@@ -134,10 +129,11 @@
// comes down to "List vs Collection" I suppose... for now, pass Collection
Boolean unwrapSingle = findFormatFeature(ctxt, property, Collection.class,
JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ NullValueProvider nuller = findContentNullProvider(ctxt, property, valueDeser);
if (isDefaultDeserializer(valueDeser)) {
valueDeser = null;
}
- return withResolved(delegate, valueDeser, unwrapSingle);
+ return withResolved(delegate, valueDeser, nuller, unwrapSingle);
}
/*
@@ -146,18 +142,18 @@
/**********************************************************
*/
- @Override
- public JavaType getContentType() {
- return _collectionType.getContentType();
- }
-
@SuppressWarnings("unchecked")
@Override
public JsonDeserializer<Object> getContentDeserializer() {
JsonDeserializer<?> deser = _valueDeserializer;
return (JsonDeserializer<Object>) deser;
}
-
+
+ @Override
+ public ValueInstantiator getValueInstantiator() {
+ return _valueInstantiator;
+ }
+
/*
/**********************************************************
/* JsonDeserializer API
@@ -202,7 +198,12 @@
if (t == JsonToken.END_ARRAY) {
break;
}
- if (t != JsonToken.VALUE_NULL) {
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ value = (String) _nullProvider.getNullValue(ctxt);
+ } else {
value = _parseString(p, ctxt);
}
result.add(value);
@@ -229,7 +230,14 @@
break;
}
// Ok: no need to convert Strings, but must recognize nulls
- value = (t == JsonToken.VALUE_NULL) ? deser.getNullValue(ctxt) : deser.deserialize(p, ctxt);
+ if (t == JsonToken.VALUE_NULL) {
+ if (_skipNullValues) {
+ continue;
+ }
+ value = (String) _nullProvider.getNullValue(ctxt);
+ } else {
+ value = deser.deserialize(p, ctxt);
+ }
} else {
value = deser.deserialize(p, ctxt);
}
@@ -239,7 +247,8 @@
}
@Override
- public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
+ 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);
}
@@ -250,14 +259,15 @@
* array, depending on configuration.
*/
@SuppressWarnings("unchecked")
- private final Collection<String> handleNonArray(JsonParser p, DeserializationContext ctxt, Collection<String> result) throws IOException
+ private final Collection<String> handleNonArray(JsonParser p, DeserializationContext ctxt,
+ Collection<String> result) throws IOException
{
// implicit arrays from single values?
boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
((_unwrapSingle == null) &&
ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
if (!canWrap) {
- return (Collection<String>) ctxt.handleUnexpectedToken(_collectionType.getRawClass(), p);
+ return (Collection<String>) ctxt.handleUnexpectedToken(_containerType.getRawClass(), p);
}
// Strings are one of "native" (intrinsic) types, so there's never type deserializer involved
JsonDeserializer<String> valueDes = _valueDeserializer;
@@ -266,7 +276,11 @@
String value;
if (t == JsonToken.VALUE_NULL) {
- value = (valueDes == null) ? null : valueDes.getNullValue(ctxt);
+ // 03-Feb-2017, tatu: Does this work?
+ if (_skipNullValues) {
+ return result;
+ }
+ value = (String) _nullProvider.getNullValue(ctxt);
} else {
value = (valueDes == null) ? _parseString(p, ctxt) : valueDes.deserialize(p, ctxt);
}
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 5082b74..c563f4b 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
@@ -3,32 +3,31 @@
import java.io.IOException;
import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
@JacksonStdImpl
-public final class StringDeserializer extends StdScalarDeserializer<String>
+public class StringDeserializer extends StdScalarDeserializer<String> // non-final since 2.9
{
private static final long serialVersionUID = 1L;
- // @since 2.8.8
- protected final static int FEATURES_ACCEPT_ARRAYS =
- DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
- DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
-
/**
* @since 2.2
*/
public final static StringDeserializer instance = new StringDeserializer();
-
+
public StringDeserializer() { super(String.class); }
// since 2.6, slightly faster lookups for this very common type
@Override
public boolean isCachable() { return true; }
+ @Override // since 2.9
+ public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
+ return "";
+ }
+
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -53,9 +52,13 @@
return ob.toString();
}
// allow coercions for other scalar types
- String text = p.getValueAsString();
- if (text != null) {
- return text;
+ // 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
+ // "real" scalar
+ if (t.isScalarValue()) {
+ String text = p.getValueAsString();
+ if (text != null) {
+ return text;
+ }
}
return (String) ctxt.handleUnexpectedToken(_valueClass, p);
}
@@ -63,31 +66,8 @@
// Since we can never have type info ("natural type"; String, Boolean, Integer, Double):
// (is it an error to even call this version?)
@Override
- public String deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
+ public String deserializeWithType(JsonParser p, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer) throws IOException {
return deserialize(p, ctxt);
}
-
- // @since 2.8.8
- protected String _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
- {
- JsonToken t;
- if (ctxt.hasSomeOfFeatures(FEATURES_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 String parsed = _parseString(p, ctxt);
- if (p.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(p, ctxt);
- }
- return parsed;
- }
- } else {
- t = p.getCurrentToken();
- }
- return (String) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
- }
}
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 0906ad5..d2e8c67 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
@@ -69,14 +69,14 @@
_delegateDeserializer.deserialize(p, ctxt));
}
if (_beanType.isAbstract()) { // for good measure, check this too
- return ctxt.handleMissingInstantiator(handledType(), p,
+ return ctxt.handleMissingInstantiator(handledType(), getValueInstantiator(), p,
"abstract type (need to add/enable type information?)");
}
boolean hasStringCreator = _valueInstantiator.canCreateFromString();
boolean hasDefaultCtor = _valueInstantiator.canCreateUsingDefault();
// and finally, verify we do have single-String arg constructor (if no @JsonCreator)
if (!hasStringCreator && !hasDefaultCtor) {
- return ctxt.handleMissingInstantiator(handledType(), p,
+ return ctxt.handleMissingInstantiator(handledType(), getValueInstantiator(), p,
"Throwable needs a default contructor, a single-String-arg constructor; or explicit @JsonCreator");
}
@@ -105,9 +105,10 @@
}
// Maybe it's "message"?
- if (PROP_NAME_MESSAGE.equals(propName)) {
+ final boolean isMessage = PROP_NAME_MESSAGE.equals(propName);
+ if (isMessage) {
if (hasStringCreator) {
- throwable = _valueInstantiator.createFromString(ctxt, p.getText());
+ throwable = _valueInstantiator.createFromString(ctxt, p.getValueAsString());
// any pending values?
if (pending != null) {
for (int i = 0, len = pendingIx; i < len; i += 2) {
@@ -128,6 +129,9 @@
_anySetter.deserializeAndSet(p, ctxt, throwable, propName);
continue;
}
+ // 23-Jan-2018, tatu: One concern would be `message`, but without any-setter or single-String-ctor
+ // (or explicit constructor). We could just ignore it but for now, let it fail
+
// Unknown: let's call handler method
handleUnknownProperty(p, ctxt, throwable, propName);
}
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 7f4e220..67be238 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
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
@@ -37,12 +38,6 @@
protected final static Object[] NO_OBJECTS = new Object[0];
- /**
- * @deprecated Since 2.3, construct a new instance, needs to be resolved
- */
- @Deprecated
- public final static UntypedObjectDeserializer instance = new UntypedObjectDeserializer(null, null);
-
/*
/**********************************************************
/* Possible custom deserializer overrides we need to use
@@ -74,6 +69,11 @@
protected JavaType _mapType;
/**
+ * @since 2.9
+ */
+ protected final boolean _nonMerging;
+
+ /**
* @deprecated Since 2.6 use variant takes type arguments
*/
@Deprecated
@@ -85,6 +85,7 @@
super(Object.class);
_listType = listType;
_mapType = mapType;
+ _nonMerging = false;
}
@SuppressWarnings("unchecked")
@@ -99,6 +100,23 @@
_numberDeserializer = (JsonDeserializer<Object>) numberDeser;
_listType = base._listType;
_mapType = base._mapType;
+ _nonMerging = base._nonMerging;
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected UntypedObjectDeserializer(UntypedObjectDeserializer base,
+ boolean nonMerging)
+ {
+ super(Object.class);
+ _mapDeserializer = base._mapDeserializer;
+ _listDeserializer = base._listDeserializer;
+ _stringDeserializer = base._stringDeserializer;
+ _numberDeserializer = base._numberDeserializer;
+ _listType = base._listType;
+ _mapType = base._mapType;
+ _nonMerging = nonMerging;
}
/*
@@ -109,7 +127,7 @@
/**
* We need to implement this method to properly find things to delegate
- * to: it can not be done earlier since delegated deserializers almost
+ * to: it cannot be done earlier since delegated deserializers almost
* certainly require access to this instance (at least "List" and "Map" ones)
*/
@SuppressWarnings("unchecked")
@@ -174,23 +192,22 @@
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
+ // 14-Jun-2017, tatu: [databind#1625]: may want to block merging, for root value
+ boolean preventMerge = (property == null)
+ && Boolean.FALSE.equals(ctxt.getConfig().getDefaultMergeable(Object.class));
// 20-Apr-2014, tatu: If nothing custom, let's use "vanilla" instance,
// simpler and can avoid some of delegation
if ((_stringDeserializer == null) && (_numberDeserializer == null)
&& (_mapDeserializer == null) && (_listDeserializer == null)
&& getClass() == UntypedObjectDeserializer.class) {
- return Vanilla.std;
+ return Vanilla.instance(preventMerge);
+ }
+ if (preventMerge != _nonMerging) {
+ return new UntypedObjectDeserializer(this, preventMerge);
}
return this;
}
- protected JsonDeserializer<?> _withResolved(JsonDeserializer<?> mapDeser,
- JsonDeserializer<?> listDeser,
- JsonDeserializer<?> stringDeser, JsonDeserializer<?> numberDeser) {
- return new UntypedObjectDeserializer(this,
- mapDeser, listDeser, stringDeser, numberDeser);
- }
-
/*
/**********************************************************
/* Deserializer API
@@ -210,6 +227,12 @@
return true;
}
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ // 21-Apr-2017, tatu: Bit tricky... some values, yes. So let's say "dunno"
+ return null;
+ }
+
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
@@ -267,7 +290,7 @@
case JsonTokenId.ID_FALSE:
return Boolean.FALSE;
- case JsonTokenId.ID_NULL: // should not get this but...
+ case JsonTokenId.ID_NULL: // 08-Nov-2016, tatu: yes, occurs
return null;
// case JsonTokenId.ID_END_ARRAY: // invalid
@@ -277,16 +300,15 @@
}
@Override
- public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException
+ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer) throws IOException
{
switch (p.getCurrentTokenId()) {
// First: does it look like we had type id wrapping of some kind?
case JsonTokenId.ID_START_ARRAY:
case JsonTokenId.ID_START_OBJECT:
case JsonTokenId.ID_FIELD_NAME:
- /* Output can be as JSON Object, Array or scalar: no way to know
- * a this point:
- */
+ // Output can be as JSON Object, Array or scalar: no way to know at this point:
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
case JsonTokenId.ID_EMBEDDED_OBJECT:
@@ -332,6 +354,78 @@
return ctxt.handleUnexpectedToken(Object.class, p);
}
+ @SuppressWarnings("unchecked")
+ @Override // since 2.9 (to support deep merge)
+ public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue)
+ throws IOException
+ {
+ if (_nonMerging) {
+ return deserialize(p, ctxt);
+ }
+
+ switch (p.getCurrentTokenId()) {
+ 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),
+ // if caller has advanced to the first token of Object, but for empty Object
+ case JsonTokenId.ID_END_OBJECT:
+ if (_mapDeserializer != null) {
+ return _mapDeserializer.deserialize(p, ctxt, intoValue);
+ }
+ if (intoValue instanceof Map<?,?>) {
+ return mapObject(p, ctxt, (Map<Object,Object>) intoValue);
+ }
+ return mapObject(p, ctxt);
+ case JsonTokenId.ID_START_ARRAY:
+ if (_listDeserializer != null) {
+ return _listDeserializer.deserialize(p, ctxt, intoValue);
+ }
+ if (intoValue instanceof Collection<?>) {
+ return mapArray(p, ctxt, (Collection<Object>) intoValue);
+ }
+ if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
+ return mapArrayToArray(p, ctxt);
+ }
+ return mapArray(p, ctxt);
+ case JsonTokenId.ID_EMBEDDED_OBJECT:
+ return p.getEmbeddedObject();
+ case JsonTokenId.ID_STRING:
+ if (_stringDeserializer != null) {
+ return _stringDeserializer.deserialize(p, ctxt, intoValue);
+ }
+ return p.getText();
+
+ case JsonTokenId.ID_NUMBER_INT:
+ if (_numberDeserializer != null) {
+ return _numberDeserializer.deserialize(p, ctxt, intoValue);
+ }
+ if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
+ return _coerceIntegral(p, ctxt);
+ }
+ return p.getNumberValue();
+
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ if (_numberDeserializer != null) {
+ return _numberDeserializer.deserialize(p, ctxt, intoValue);
+ }
+ if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
+ return p.getDecimalValue();
+ }
+ return p.getNumberValue();
+ case JsonTokenId.ID_TRUE:
+ return Boolean.TRUE;
+ case JsonTokenId.ID_FALSE:
+ return Boolean.FALSE;
+
+ case JsonTokenId.ID_NULL:
+ // 21-Apr-2017, tatu: May need to consider "skip nulls" at some point but...
+ return null;
+ default:
+ }
+ // easiest to just delegate to "dumb" version for the rest?
+ return deserialize(p, ctxt);
+ }
+
/*
/**********************************************************
/* Internal methods
@@ -381,6 +475,17 @@
return result;
}
+ protected Object mapArray(JsonParser p, DeserializationContext ctxt,
+ Collection<Object> result) throws IOException
+ {
+ // we start by pointing to START_ARRAY. Also, no real merging; array/Collection
+ // just appends always
+ while (p.nextToken() != JsonToken.END_ARRAY) {
+ result.add(deserialize(p, ctxt));
+ }
+ return result;
+ }
+
/**
* Method called to map a JSON Object into a Java value.
*/
@@ -463,6 +568,36 @@
return buffer.completeAndClearBuffer(values, ptr);
}
+ protected Object mapObject(JsonParser p, DeserializationContext ctxt,
+ Map<Object,Object> m) throws IOException
+ {
+ JsonToken t = p.getCurrentToken();
+ if (t == JsonToken.START_OBJECT) {
+ t = p.nextToken();
+ }
+ if (t == JsonToken.END_OBJECT) {
+ return m;
+ }
+ // NOTE: we are guaranteed to point to FIELD_NAME
+ String key = p.getCurrentName();
+ do {
+ p.nextToken();
+ // and possibly recursive merge here
+ Object old = m.get(key);
+ Object newV;
+
+ if (old != null) {
+ newV = deserialize(p, ctxt, old);
+ } else {
+ newV = deserialize(p, ctxt);
+ }
+ if (newV != old) {
+ m.put(key, newV);
+ }
+ } while ((key = p.nextFieldName()) != null);
+ return m;
+ }
+
/*
/**********************************************************
/* Separate "vanilla" implementation for common case of
@@ -478,7 +613,31 @@
public final static Vanilla std = new Vanilla();
- public Vanilla() { super(Object.class); }
+ /**
+ * @since 2.9
+ */
+ protected final boolean _nonMerging;
+
+ public Vanilla() { this(false); }
+
+ protected Vanilla(boolean nonMerging) {
+ super(Object.class);
+ _nonMerging = nonMerging;
+ }
+
+ public static Vanilla instance(boolean nonMerging) {
+ if (nonMerging) {
+ return new Vanilla(true);
+ }
+ return std;
+ }
+
+ @Override // since 2.9
+ public Boolean supportsUpdate(DeserializationConfig config) {
+ // 21-Apr-2017, tatu: Bit tricky... some values, yes. So let's say "dunno"
+ // 14-Jun-2017, tatu: Well, if merging blocked, can say no, as well.
+ return _nonMerging ? Boolean.FALSE : null;
+ }
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
@@ -529,14 +688,14 @@
case JsonTokenId.ID_FALSE:
return Boolean.FALSE;
- case JsonTokenId.ID_NULL: // should not get this but...
- return null;
-
case JsonTokenId.ID_END_OBJECT:
// 28-Oct-2015, tatu: [databind#989] We may also be given END_OBJECT (similar to FIELD_NAME),
// if caller has advanced to the first token of Object, but for empty Object
return new LinkedHashMap<String,Object>(2);
+ case JsonTokenId.ID_NULL: // 08-Nov-2016, tatu: yes, occurs
+ return null;
+
//case JsonTokenId.ID_END_ARRAY: // invalid
default:
}
@@ -581,6 +740,72 @@
return ctxt.handleUnexpectedToken(Object.class, p);
}
+ @SuppressWarnings("unchecked")
+ @Override // since 2.9 (to support deep merge)
+ public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue)
+ throws IOException
+ {
+ if (_nonMerging) {
+ return deserialize(p, ctxt);
+ }
+
+ switch (p.getCurrentTokenId()) {
+ case JsonTokenId.ID_END_OBJECT:
+ case JsonTokenId.ID_END_ARRAY:
+ return intoValue;
+ case JsonTokenId.ID_START_OBJECT:
+ {
+ JsonToken t = p.nextToken(); // to get to FIELD_NAME or END_OBJECT
+ if (t == JsonToken.END_OBJECT) {
+ return intoValue;
+ }
+ }
+ case JsonTokenId.ID_FIELD_NAME:
+ if (intoValue instanceof Map<?,?>) {
+ Map<Object,Object> m = (Map<Object,Object>) intoValue;
+ // NOTE: we are guaranteed to point to FIELD_NAME
+ String key = p.getCurrentName();
+ do {
+ p.nextToken();
+ // and possibly recursive merge here
+ Object old = m.get(key);
+ Object newV;
+ if (old != null) {
+ newV = deserialize(p, ctxt, old);
+ } else {
+ newV = deserialize(p, ctxt);
+ }
+ if (newV != old) {
+ m.put(key, newV);
+ }
+ } while ((key = p.nextFieldName()) != null);
+ return intoValue;
+ }
+ break;
+ case JsonTokenId.ID_START_ARRAY:
+ {
+ JsonToken t = p.nextToken(); // to get to FIELD_NAME or END_OBJECT
+ if (t == JsonToken.END_ARRAY) {
+ return intoValue;
+ }
+ }
+
+ if (intoValue instanceof Collection<?>) {
+ Collection<Object> c = (Collection<Object>) intoValue;
+ // NOTE: merge for arrays/Collections means append, can't merge contents
+ do {
+ c.add(deserialize(p, ctxt));
+ } while (p.nextToken() != JsonToken.END_ARRAY);
+ return intoValue;
+ }
+ // 21-Apr-2017, tatu: Should we try to support merging of Object[] values too?
+ // ... maybe future improvement
+ break;
+ }
+ // Easiest handling for the rest, delegate. Only (?) question: how about nulls?
+ return deserialize(p, ctxt);
+ }
+
protected Object mapArray(JsonParser p, DeserializationContext ctxt) throws IOException
{
Object value = deserialize(p, ctxt);
@@ -618,6 +843,24 @@
}
/**
+ * Method called to map a JSON Array into a Java Object array (Object[]).
+ */
+ protected Object[] mapArrayToArray(JsonParser p, DeserializationContext ctxt) throws IOException {
+ ObjectBuffer buffer = ctxt.leaseObjectBuffer();
+ Object[] values = buffer.resetAndStart();
+ int ptr = 0;
+ do {
+ Object value = deserialize(p, ctxt);
+ if (ptr >= values.length) {
+ values = buffer.appendCompletedChunk(values);
+ ptr = 0;
+ }
+ values[ptr++] = value;
+ } while (p.nextToken() != JsonToken.END_ARRAY);
+ return buffer.completeAndClearBuffer(values, ptr);
+ }
+
+ /**
* Method called to map a JSON Object into a Java value.
*/
protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOException
@@ -653,23 +896,5 @@
} while ((key = p.nextFieldName()) != null);
return result;
}
-
- /**
- * Method called to map a JSON Array into a Java Object array (Object[]).
- */
- protected Object[] mapArrayToArray(JsonParser p, DeserializationContext ctxt) throws IOException {
- ObjectBuffer buffer = ctxt.leaseObjectBuffer();
- Object[] values = buffer.resetAndStart();
- int ptr = 0;
- do {
- Object value = deserialize(p, ctxt);
- if (ptr >= values.length) {
- values = buffer.appendCompletedChunk(values);
- ptr = 0;
- }
- values[ptr++] = value;
- } while (p.nextToken() != JsonToken.END_ARRAY);
- return buffer.completeAndClearBuffer(values, ptr);
- }
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/IgnoredPropertyException.java b/src/main/java/com/fasterxml/jackson/databind/exc/IgnoredPropertyException.java
index c26d951..e78ad52 100644
--- a/src/main/java/com/fasterxml/jackson/databind/exc/IgnoredPropertyException.java
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/IgnoredPropertyException.java
@@ -39,7 +39,6 @@
super(msg, loc, referringClass, propName, propertyIds);
}
-
/**
* Factory method used for constructing instances of this exception type.
*
@@ -48,23 +47,20 @@
* if available), or if not, type itself
* @param propertyName Name of unrecognized property
* @param propertyIds (optional, null if not available) Set of properties that
- * type would recognize, if completely known: null if set can not be determined.
+ * type would recognize, if completely known: null if set cannot be determined.
*/
public static IgnoredPropertyException from(JsonParser p,
Object fromObjectOrClass, String propertyName,
Collection<Object> propertyIds)
{
- if (fromObjectOrClass == null) {
- throw new IllegalArgumentException();
- }
Class<?> ref;
if (fromObjectOrClass instanceof Class<?>) {
ref = (Class<?>) fromObjectOrClass;
- } else {
+ } else { // also acts as null check:
ref = fromObjectOrClass.getClass();
}
- String msg = "Ignored field \""+propertyName+"\" (class "+ref.getName()
- +") encountered; mapper configured not to allow this";
+ String msg = String.format("Ignored field \"%s\" (class %s) encountered; mapper configured not to allow this",
+ propertyName, ref.getName());
IgnoredPropertyException e = new IgnoredPropertyException(p, msg,
p.getCurrentLocation(), ref, propertyName, propertyIds);
// but let's also ensure path includes this last (missing) segment
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidDefinitionException.java b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidDefinitionException.java
new file mode 100644
index 0000000..444e6d9
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidDefinitionException.java
@@ -0,0 +1,104 @@
+package com.fasterxml.jackson.databind.exc;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
+
+/**
+ * Intermediate exception type used as the base class for all {@link JsonMappingException}s
+ * that are due to problems with target type definition; usually a problem with
+ * annotations used on a class or its properties.
+ * This is in contrast to {@link MismatchedInputException} which
+ * signals a problem with input to map.
+ *
+ * @since 2.9
+ */
+@SuppressWarnings("serial")
+public class InvalidDefinitionException
+ extends JsonMappingException
+{
+ protected final JavaType _type;
+
+ protected transient BeanDescription _beanDesc;
+ protected transient BeanPropertyDefinition _property;
+
+ protected InvalidDefinitionException(JsonParser p, String msg,
+ JavaType type) {
+ super(p, msg);
+ _type = type;
+ _beanDesc = null;
+ _property = null;
+ }
+
+ protected InvalidDefinitionException(JsonGenerator g, String msg,
+ JavaType type) {
+ super(g, msg);
+ _type = type;
+ _beanDesc = null;
+ _property = null;
+ }
+
+ protected InvalidDefinitionException(JsonParser p, String msg,
+ BeanDescription bean, BeanPropertyDefinition prop) {
+ super(p, msg);
+ _type = (bean == null) ? null : bean.getType();
+ _beanDesc = bean;
+ _property = prop;
+ }
+
+ protected InvalidDefinitionException(JsonGenerator g, String msg,
+ BeanDescription bean, BeanPropertyDefinition prop) {
+ super(g, msg);
+ _type = (bean == null) ? null : bean.getType();
+ _beanDesc = bean;
+ _property = prop;
+ }
+
+ public static InvalidDefinitionException from(JsonParser p, String msg,
+ BeanDescription bean, BeanPropertyDefinition prop) {
+ return new InvalidDefinitionException(p, msg, bean, prop);
+ }
+
+ public static InvalidDefinitionException from(JsonParser p, String msg,
+ JavaType type) {
+ return new InvalidDefinitionException(p, msg, type);
+ }
+
+ public static InvalidDefinitionException from(JsonGenerator g, String msg,
+ BeanDescription bean, BeanPropertyDefinition prop) {
+ return new InvalidDefinitionException(g, msg, bean, prop);
+ }
+
+ public static InvalidDefinitionException from(JsonGenerator g, String msg,
+ JavaType type) {
+ return new InvalidDefinitionException(g, msg, type);
+ }
+
+ /**
+ * Accessor for type fully resolved type that had the problem; this should always
+ * known and available, never <code>null</code>
+ */
+ public JavaType getType() {
+ return _type;
+ }
+
+ /**
+ * Accessor for type definition (class) that had the definition problem, if any; may sometimes
+ * be undefined or unknown; if so, returns <code>null</code>.
+ */
+ public BeanDescription getBeanDescription() {
+ return _beanDesc;
+ }
+
+ /**
+ * Accessor for property that had the definition problem if any
+ * (none, for example if the problem relates to type in general),
+ * if known. If not known (or relevant), returns <code>null</code>.
+ */
+ public BeanPropertyDefinition getProperty() {
+ return _property;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java
index e7593d1..b1b8b10 100644
--- a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java
@@ -2,16 +2,16 @@
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.JsonMappingException;
/**
- * Specialized sub-class of {@link JsonMappingException}
+ * Specialized sub-class of {@link MismatchedInputException}
* that is used when the underlying problem appears to be that
* of bad formatting of a value to deserialize.
*
* @since 2.1
*/
-public class InvalidFormatException extends JsonMappingException
+public class InvalidFormatException
+ extends MismatchedInputException // since 2.9
{
private static final long serialVersionUID = 1L; // silly Eclipse, warnings
@@ -21,12 +21,6 @@
*/
protected final Object _value;
- /**
- * Intended target type (type-erased class) that value could not
- * be deserialized into, if known.
- */
- protected final Class<?> _targetType;
-
/*
/**********************************************************
/* Life-cycle
@@ -63,9 +57,8 @@
public InvalidFormatException(JsonParser p,
String msg, Object value, Class<?> targetType)
{
- super(p, msg);
+ super(p, msg, targetType);
_value = value;
- _targetType = targetType;
}
public static InvalidFormatException from(JsonParser p, String msg,
@@ -89,14 +82,4 @@
public Object getValue() {
return _value;
}
-
- /**
- * Accessor for checking target type of value ({@link #getValue} that failed
- * to deserialize.
- * Note that type may not be available, depending on who throws the exception
- * and when.
- */
- public Class<?> getTargetType() {
- return _targetType;
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidNullException.java b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidNullException.java
new file mode 100644
index 0000000..daa0eeb
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidNullException.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.databind.exc;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.PropertyName;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
+/**
+ * Exception thrown if a `null` value is being encountered for a property
+ * designed as "fail on null" property (see {@link com.fasterxml.jackson.annotation.JsonSetter}).
+ *
+ * @since 2.9
+ */
+public class InvalidNullException
+ extends MismatchedInputException // since 2.9
+{
+ private static final long serialVersionUID = 1L; // silly Eclipse, warnings
+
+ /**
+ * Name of property, if known, for which null was encountered.
+ */
+ protected final PropertyName _propertyName;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ protected InvalidNullException(DeserializationContext ctxt, String msg,
+ PropertyName pname)
+ {
+ super(ctxt.getParser(), msg);
+ _propertyName = pname;
+ }
+
+ public static InvalidNullException from(DeserializationContext ctxt,
+ PropertyName name, JavaType type)
+ {
+ String msg = String.format("Invalid `null` value encountered for property %s",
+ ClassUtil.quotedOr(name, "<UNKNOWN>"));
+ InvalidNullException exc = new InvalidNullException(ctxt, msg, name);
+ if (type != null) {
+ exc.setTargetType(type);
+ }
+ return exc;
+ }
+
+ public PropertyName getPropertyName() {
+ return _propertyName;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidTypeIdException.java b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidTypeIdException.java
index 8b05215..d405a43 100644
--- a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidTypeIdException.java
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidTypeIdException.java
@@ -2,14 +2,14 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.JsonMappingException;
/**
* Exception thrown when resolution of a type id fails.
*
* @since 2.8
*/
-public class InvalidTypeIdException extends JsonMappingException
+public class InvalidTypeIdException
+ extends MismatchedInputException // since 2.9
{
private static final long serialVersionUID = 1L; // silly Eclipse, warnings
@@ -19,7 +19,8 @@
protected final JavaType _baseType;
/**
- * Type id that failed to be resolved to a subtype
+ * Type id that failed to be resolved to a subtype; `null` in cases
+ * where no type id was located (since 2.9).
*/
protected final String _typeId;
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/MismatchedInputException.java b/src/main/java/com/fasterxml/jackson/databind/exc/MismatchedInputException.java
new file mode 100644
index 0000000..183831a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/MismatchedInputException.java
@@ -0,0 +1,78 @@
+package com.fasterxml.jackson.databind.exc;
+
+import com.fasterxml.jackson.core.JsonLocation;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
+/**
+ * General exception type used as the base class for all {@link JsonMappingException}s
+ * that are due to input not mapping to target definition; these are typically
+ * considered "client errors" since target type definition itself is not the root cause
+ * but mismatching input. This is in contrast to {@link InvalidDefinitionException} which
+ * signals a problem with target type definition and not input.
+ *<p>
+ * This type is used as-is for some input problems, but in most cases there should be
+ * more explicit subtypes to use.
+ *<p>
+ * NOTE: name chosen to differ from `java.util.InputMismatchException` since while that
+ * would have been better name, use of same overlapping name causes nasty issues
+ * with IDE auto-completion, so slightly less optimal chosen.
+ *
+ * @since 2.9
+ */
+@SuppressWarnings("serial")
+public class MismatchedInputException
+ extends JsonMappingException
+{
+ /**
+ * Type of value that was to be deserialized
+ */
+ protected Class<?> _targetType;
+
+ protected MismatchedInputException(JsonParser p, String msg) {
+ this(p, msg, (JavaType) null);
+ }
+
+ protected MismatchedInputException(JsonParser p, String msg, JsonLocation loc) {
+ super(p, msg, loc);
+ }
+
+ protected MismatchedInputException(JsonParser p, String msg, Class<?> targetType) {
+ super(p, msg);
+ _targetType = targetType;
+ }
+
+ protected MismatchedInputException(JsonParser p, String msg, JavaType targetType) {
+ super(p, msg);
+ _targetType = ClassUtil.rawClass(targetType);
+ }
+
+ // Only to prevent super-class static method from getting called
+ @Deprecated // as of 2.9
+ public static MismatchedInputException from(JsonParser p, String msg) {
+ return from(p, (Class<?>) null, msg);
+ }
+
+ public static MismatchedInputException from(JsonParser p, JavaType targetType, String msg) {
+ return new MismatchedInputException(p, msg, targetType);
+ }
+
+ public static MismatchedInputException from(JsonParser p, Class<?> targetType, String msg) {
+ return new MismatchedInputException(p, msg, targetType);
+ }
+
+ public MismatchedInputException setTargetType(JavaType t) {
+ _targetType = t.getRawClass();
+ return this;
+ }
+
+ /**
+ * Accessor for getting intended target type, with which input did not match,
+ * if known; `null` if not known for some reason.
+ */
+ public Class<?> getTargetType() {
+ return _targetType;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/PropertyBindingException.java b/src/main/java/com/fasterxml/jackson/databind/exc/PropertyBindingException.java
index 2dcfe8a..7e5c630 100644
--- a/src/main/java/com/fasterxml/jackson/databind/exc/PropertyBindingException.java
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/PropertyBindingException.java
@@ -16,7 +16,7 @@
*/
@SuppressWarnings("serial")
public abstract class PropertyBindingException
- extends JsonMappingException
+ extends MismatchedInputException // since 2.9
{
/**
* Class that does not contain mapping for the unrecognized property.
diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/UnrecognizedPropertyException.java b/src/main/java/com/fasterxml/jackson/databind/exc/UnrecognizedPropertyException.java
index d913079..fda5674 100644
--- a/src/main/java/com/fasterxml/jackson/databind/exc/UnrecognizedPropertyException.java
+++ b/src/main/java/com/fasterxml/jackson/databind/exc/UnrecognizedPropertyException.java
@@ -43,22 +43,20 @@
* if available), or if not, type itself
* @param propertyName Name of unrecognized property
* @param propertyIds (optional, null if not available) Set of properties that
- * type would recognize, if completely known: null if set can not be determined.
+ * type would recognize, if completely known: null if set cannot be determined.
*/
public static UnrecognizedPropertyException from(JsonParser p,
Object fromObjectOrClass, String propertyName,
Collection<Object> propertyIds)
{
- if (fromObjectOrClass == null) {
- throw new IllegalArgumentException();
- }
Class<?> ref;
if (fromObjectOrClass instanceof Class<?>) {
ref = (Class<?>) fromObjectOrClass;
} else {
ref = fromObjectOrClass.getClass();
}
- String msg = "Unrecognized field \""+propertyName+"\" (class "+ref.getName()+"), not marked as ignorable";
+ String msg = String.format("Unrecognized field \"%s\" (class %s), not marked as ignorable",
+ propertyName, ref.getName());
UnrecognizedPropertyException e = new UnrecognizedPropertyException(p, msg,
p.getCurrentLocation(), ref, propertyName, propertyIds);
// but let's also ensure path includes this last (missing) segment
diff --git a/src/main/java/com/fasterxml/jackson/databind/ext/Java7Support.java b/src/main/java/com/fasterxml/jackson/databind/ext/Java7Support.java
index 5edf8a0..051f570 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ext/Java7Support.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ext/Java7Support.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* To support Java7-incomplete platforms, we will offer support for JDK 7
@@ -21,7 +22,7 @@
Java7Support impl = null;
try {
Class<?> cls = Class.forName("com.fasterxml.jackson.databind.ext.Java7SupportImpl");
- impl = (Java7Support) cls.newInstance();
+ impl = (Java7Support) ClassUtil.createInstance(cls, false);
} catch (Throwable t) {
// 24-Nov-2015, tatu: Should we log or not?
java.util.logging.Logger.getLogger(Java7Support.class.getName())
diff --git a/src/main/java/com/fasterxml/jackson/databind/ext/NioPathDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/ext/NioPathDeserializer.java
index 48a39eb..20e9d5c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ext/NioPathDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ext/NioPathDeserializer.java
@@ -1,16 +1,22 @@
package com.fasterxml.jackson.databind.ext;
+import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.ServiceLoader;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
+import static java.lang.Character.isLetter;
+
/**
* @since 2.8
*/
@@ -18,6 +24,19 @@
{
private static final long serialVersionUID = 1;
+ private static final boolean areWindowsFilePathsSupported;
+ static {
+ boolean isWindowsRootFound = false;
+ for (File file : File.listRoots()) {
+ String path = file.getPath();
+ if (path.length() >= 2 && isLetter(path.charAt(0)) && path.charAt(1) == ':') {
+ isWindowsRootFound = true;
+ break;
+ }
+ }
+ areWindowsFilePathsSupported = isWindowsRootFound;
+ }
+
public NioPathDeserializer() { super(Path.class); }
@Override
@@ -25,17 +44,45 @@
if (!p.hasToken(JsonToken.VALUE_STRING)) {
return (Path) ctxt.handleUnexpectedToken(Path.class, p);
}
+
final String value = p.getText();
+
// If someone gives us an input with no : at all, treat as local path, instead of failing
// with invalid URI.
if (value.indexOf(':') < 0) {
return Paths.get(value);
}
+
+ if (areWindowsFilePathsSupported) {
+ if (value.length() >= 2 && isLetter(value.charAt(0)) && value.charAt(1) == ':') {
+ return Paths.get(value);
+ }
+ }
+
+ final URI uri;
try {
- URI uri = new URI(value);
- return Paths.get(uri);
+ uri = new URI(value);
} catch (URISyntaxException e) {
return (Path) ctxt.handleInstantiationProblem(handledType(), value, e);
}
+ try {
+ return Paths.get(uri);
+ } catch (FileSystemNotFoundException cause) {
+ try {
+ final String scheme = uri.getScheme();
+ // We want to use the current thread's context class loader, not system class loader that is used in Paths.get():
+ for (FileSystemProvider provider : ServiceLoader.load(FileSystemProvider.class)) {
+ if (provider.getScheme().equalsIgnoreCase(scheme)) {
+ return provider.getPath(uri);
+ }
+ }
+ return (Path) ctxt.handleInstantiationProblem(handledType(), value, cause);
+ } catch (Throwable e) {
+ e.addSuppressed(cause);
+ return (Path) ctxt.handleInstantiationProblem(handledType(), value, e);
+ }
+ } catch (Throwable e) {
+ return (Path) ctxt.handleInstantiationProblem(handledType(), value, e);
+ }
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ext/NioPathSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ext/NioPathSerializer.java
index 1abfab5..b8c23a5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ext/NioPathSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ext/NioPathSerializer.java
@@ -5,7 +5,11 @@
import java.nio.file.Path;
import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
+
import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
/**
@@ -22,4 +26,17 @@
// write the Path as a URI, always.
gen.writeString(value.toUri().toString());
}
+
+ // [databind#1688]: Not sure this is 100% ok, considering there are legitimately different
+ // impls... but has to do
+ @Override
+ public void serializeWithType(Path value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException
+ {
+ // Better ensure we don't use specific sub-classes:
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, Path.class, JsonToken.VALUE_STRING));
+ serialize(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ext/OptionalHandlerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ext/OptionalHandlerFactory.java
index 0f49a22..f22e8d5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ext/OptionalHandlerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ext/OptionalHandlerFactory.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.Deserializers;
import com.fasterxml.jackson.databind.ser.Serializers;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Helper class used for isolating details of handling optional+external types
@@ -50,7 +51,7 @@
try {
node = org.w3c.dom.Node.class;
doc = org.w3c.dom.Document.class;
- } catch (Exception e) {
+ } catch (Throwable e) {
// not optimal but will do
Logger.getLogger(OptionalHandlerFactory.class.getName())
.log(Level.INFO, "Could not load DOM `Node` and/or `Document` classes: no DOM support");
@@ -153,7 +154,7 @@
private Object instantiate(String className)
{
try {
- return Class.forName(className).newInstance();
+ return ClassUtil.createInstance(Class.forName(className), false);
} catch (LinkageError e) { }
// too many different kinds to enumerate here:
catch (Exception e) { }
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/Annotated.java b/src/main/java/com/fasterxml/jackson/databind/introspect/Annotated.java
index 2d7c064..fce7b9e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/Annotated.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/Annotated.java
@@ -24,20 +24,6 @@
* @since 2.7
*/
public abstract boolean hasOneOf(Class<? extends Annotation>[] annoClasses);
-
- /**
- * Fluent factory method that will construct a new instance that uses specified
- * instance annotations instead of currently configured ones.
- */
- public abstract Annotated withAnnotations(AnnotationMap fallback);
-
- /**
- * Fluent factory method that will construct a new instance that uses
- * annotations from specified {@link Annotated} as fallback annotations
- */
- public final Annotated withFallBackAnnotationsFrom(Annotated annotated) {
- return withAnnotations(AnnotationMap.merge(getAllAnnotations(), annotated.getAllAnnotations()));
- }
/**
* Method that can be used to find actual JDK element that this instance
@@ -48,7 +34,7 @@
protected abstract int getModifiers();
- public final boolean isPublic() {
+ public boolean isPublic() {
return Modifier.isPublic(getModifiers());
}
@@ -73,7 +59,7 @@
/**
* JDK declared generic type of the annotated element; definition
* of what exactly this means depends on sub-class. Note that such type
- * can not be reliably resolved without {@link TypeResolutionContext}, and
+ * cannot be reliably resolved without {@link TypeResolutionContext}, and
* as a result use of this method was deprecated in Jackson 2.7: see
* {@link #getType} for replacement.
*
@@ -93,17 +79,13 @@
/**
* Accessor that can be used to iterate over all the annotations
* associated with annotated component.
- *
+ *
* @since 2.3
+ * @deprecated Since 2.9 should instead use {@link #getAnnotated()}
*/
+ @Deprecated
public abstract Iterable<Annotation> annotations();
- /**
- * Internal helper method used to access annotation information;
- * not exposed to developers since instances are mutable.
- */
- protected abstract AnnotationMap getAllAnnotations();
-
// Also: ensure we can use #equals, #hashCode
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java
index 109c4c7..2c8b733 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java
@@ -1,8 +1,6 @@
package com.fasterxml.jackson.databind.introspect;
import java.lang.annotation.Annotation;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.*;
@@ -19,7 +17,9 @@
extends Annotated
implements TypeResolutionContext
{
- private final static AnnotationMap[] NO_ANNOTATION_MAPS = new AnnotationMap[0];
+ private final static Creators NO_CREATORS = new Creators(null,
+ Collections.<AnnotatedConstructor>emptyList(),
+ Collections.<AnnotatedMethod>emptyList());
/*
/**********************************************************
@@ -48,8 +48,6 @@
/**
* Ordered set of super classes and interfaces of the
* class itself: included in order of precedence
- *<p>
- * NOTE: changed in 2.7 from List of <code>Class</code>es to List of {@link JavaType}s.
*/
final protected List<JavaType> _superTypes;
@@ -88,29 +86,12 @@
* Combined list of Jackson annotations that the class has,
* including inheritable ones from super classes and interfaces
*/
- final protected AnnotationMap _classAnnotations;
+ final protected Annotations _classAnnotations;
/**
- * Flag to indicate whether creator information has been resolved
- * or not.
+ * @since 2.9
*/
- protected boolean _creatorsResolved = false;
-
- /**
- * Default constructor of the annotated class, if it has one.
- */
- protected AnnotatedConstructor _defaultConstructor;
-
- /**
- * Single argument constructors the class has, if any.
- */
- protected List<AnnotatedConstructor> _constructors;
-
- /**
- * Single argument static methods that might be usable
- * as factory methods
- */
- protected List<AnnotatedMethod> _creatorMethods;
+ protected Creators _creators;
/**
* Member methods of interest; for now ones with 0 or 1 arguments
@@ -141,98 +122,83 @@
/**
* Constructor will not do any initializations, to allow for
* configuring instances differently depending on use cases
+ *
+ * @param type Fully resolved type; may be `null`, but ONLY if no member fields or
+ * methods are to be accessed
+ * @param rawType Type-erased class; pass if no `type` needed or available
*/
- private AnnotatedClass(JavaType type, Class<?> rawType, TypeBindings bindings,
- List<JavaType> superTypes,
+ AnnotatedClass(JavaType type, Class<?> rawType, List<JavaType> superTypes,
+ Class<?> primaryMixIn, Annotations classAnnotations, TypeBindings bindings,
AnnotationIntrospector aintr, MixInResolver mir, TypeFactory tf)
{
_type = type;
_class = rawType;
- _bindings = bindings;
_superTypes = superTypes;
+ _primaryMixIn = primaryMixIn;
+ _classAnnotations = classAnnotations;
+ _bindings = bindings;
_annotationIntrospector = aintr;
- _typeFactory = tf;
_mixInResolver = mir;
- _primaryMixIn = (_mixInResolver == null) ? null
- : _mixInResolver.findMixInClassFor(_class);
- _classAnnotations = _resolveClassAnnotations();
- }
-
- private AnnotatedClass(AnnotatedClass base, AnnotationMap clsAnn) {
- _type = base._type;
- _class = base._class;
- _bindings = base._bindings;
- _superTypes = base._superTypes;
- _annotationIntrospector = base._annotationIntrospector;
- _typeFactory = base._typeFactory;
- _mixInResolver = base._mixInResolver;
- _primaryMixIn = base._primaryMixIn;
- _classAnnotations = clsAnn;
- }
-
- @Override
- public AnnotatedClass withAnnotations(AnnotationMap ann) {
- return new AnnotatedClass(this, ann);
+ _typeFactory = tf;
}
/**
- * Factory method that instantiates an instance. Returned instance
- * will only be initialized with class annotations, but not with
- * any method information.
- *
- * @since 2.7
+ * Constructor (only) used for creating primordial simple types (during bootstrapping)
+ * and array type placeholders where no fields or methods are needed.
+ *
+ * @since 2.9
*/
+ AnnotatedClass(Class<?> rawType) {
+ _type = null;
+ _class = rawType;
+ _superTypes = Collections.emptyList();
+ _primaryMixIn = null;
+ _classAnnotations = AnnotationCollector.emptyAnnotations();
+ _bindings = TypeBindings.emptyBindings();
+ _annotationIntrospector = null;
+ _mixInResolver = null;
+ _typeFactory = null;
+ }
+
+ /**
+ * @deprecated Since 2.9, use methods in {@link AnnotatedClassResolver} instead.
+ */
+ @Deprecated
public static AnnotatedClass construct(JavaType type, MapperConfig<?> config) {
- AnnotationIntrospector intr = config.isAnnotationProcessingEnabled()
- ? config.getAnnotationIntrospector() : null;
- Class<?> raw = type.getRawClass();
- return new AnnotatedClass(type, raw, type.getBindings(),
- ClassUtil.findSuperTypes(type, null, false), intr,
- (MixInResolver) config, config.getTypeFactory());
+ return construct(type, config, (MixInResolver) config);
}
/**
- * @since 2.7
+ * @deprecated Since 2.9, use methods in {@link AnnotatedClassResolver} instead.
*/
+ @Deprecated
public static AnnotatedClass construct(JavaType type, MapperConfig<?> config,
MixInResolver mir)
{
- AnnotationIntrospector intr = config.isAnnotationProcessingEnabled()
- ? config.getAnnotationIntrospector() : null;
- Class<?> raw = type.getRawClass();
- return new AnnotatedClass(type, raw, type.getBindings(),
- ClassUtil.findSuperTypes(type, null, false),
- intr, mir, config.getTypeFactory());
+ return AnnotatedClassResolver.resolve(config, type, mir);
}
-
+
/**
* Method similar to {@link #construct}, but that will NOT include
* information from supertypes; only class itself and any direct
* mix-ins it may have.
*/
- public static AnnotatedClass constructWithoutSuperTypes(Class<?> cls, MapperConfig<?> config)
- {
- if (config == null) {
- return new AnnotatedClass(null, cls, TypeBindings.emptyBindings(),
- Collections.<JavaType>emptyList(), null, null, null);
- }
- AnnotationIntrospector intr = config.isAnnotationProcessingEnabled()
- ? config.getAnnotationIntrospector() : null;
- return new AnnotatedClass(null, cls, TypeBindings.emptyBindings(),
- Collections.<JavaType>emptyList(), intr, (MixInResolver) config, config.getTypeFactory());
+ /**
+ * @deprecated Since 2.9, use methods in {@link AnnotatedClassResolver} instead.
+ */
+ @Deprecated
+ public static AnnotatedClass constructWithoutSuperTypes(Class<?> raw, MapperConfig<?> config) {
+ return constructWithoutSuperTypes(raw, config, config);
}
- public static AnnotatedClass constructWithoutSuperTypes(Class<?> cls, MapperConfig<?> config,
+ /**
+ * @deprecated Since 2.9, use methods in {@link AnnotatedClassResolver} instead.
+ */
+ @Deprecated
+ public static AnnotatedClass constructWithoutSuperTypes(Class<?> raw, MapperConfig<?> config,
MixInResolver mir)
{
- if (config == null) {
- return new AnnotatedClass(null, cls, TypeBindings.emptyBindings(),
- Collections.<JavaType>emptyList(), null, null, null);
- }
- AnnotationIntrospector intr = config.isAnnotationProcessingEnabled()
- ? config.getAnnotationIntrospector() : null;
- return new AnnotatedClass(null, cls, TypeBindings.emptyBindings(),
- Collections.<JavaType>emptyList(), intr, mir, config.getTypeFactory());
+ return AnnotatedClassResolver.resolveWithoutSuperTypes(config, raw, mir);
}
/*
@@ -282,13 +248,15 @@
}
@Override
+ @Deprecated
public Iterable<Annotation> annotations() {
- return _classAnnotations.annotations();
- }
-
- @Override
- protected AnnotationMap getAllAnnotations() {
- return _classAnnotations;
+ if (_classAnnotations instanceof AnnotationMap) {
+ return ((AnnotationMap) _classAnnotations).annotations();
+ } else if (_classAnnotations instanceof AnnotationCollector.OneAnnotation ||
+ _classAnnotations instanceof AnnotationCollector.TwoAnnotations) {
+ throw new UnsupportedOperationException("please use getAnnotations/ hasAnnotation to check for Annotations");
+ }
+ return Collections.emptyList();
}
@Override
@@ -310,67 +278,47 @@
return _classAnnotations.size() > 0;
}
- public AnnotatedConstructor getDefaultConstructor()
- {
- if (!_creatorsResolved) {
- resolveCreators();
- }
- return _defaultConstructor;
+ public AnnotatedConstructor getDefaultConstructor() {
+ return _creators().defaultConstructor;
}
- public List<AnnotatedConstructor> getConstructors()
- {
- if (!_creatorsResolved) {
- resolveCreators();
- }
- return _constructors;
+ public List<AnnotatedConstructor> getConstructors() {
+ return _creators().constructors;
}
- public List<AnnotatedMethod> getStaticMethods()
- {
- if (!_creatorsResolved) {
- resolveCreators();
- }
- return _creatorMethods;
+ /**
+ * @since 2.9
+ */
+ public List<AnnotatedMethod> getFactoryMethods() {
+ return _creators().creatorMethods;
}
- public Iterable<AnnotatedMethod> memberMethods()
- {
- if (_memberMethods == null) {
- resolveMemberMethods();
- }
- return _memberMethods;
+ /**
+ * @deprecated Since 2.9; use {@link #getFactoryMethods} instead.
+ */
+ @Deprecated
+ public List<AnnotatedMethod> getStaticMethods() {
+ return getFactoryMethods();
}
- public int getMemberMethodCount()
- {
- if (_memberMethods == null) {
- resolveMemberMethods();
- }
- return _memberMethods.size();
+ public Iterable<AnnotatedMethod> memberMethods() {
+ return _methods();
}
- public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes)
- {
- if (_memberMethods == null) {
- resolveMemberMethods();
- }
- return _memberMethods.find(name, paramTypes);
+ public int getMemberMethodCount() {
+ return _methods().size();
+ }
+
+ public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes) {
+ return _methods().find(name, paramTypes);
}
public int getFieldCount() {
- if (_fields == null) {
- resolveFields();
- }
- return _fields.size();
+ return _fields().size();
}
- public Iterable<AnnotatedField> fields()
- {
- if (_fields == null) {
- resolveFields();
- }
- return _fields;
+ public Iterable<AnnotatedField> fields() {
+ return _fields();
}
/**
@@ -387,836 +335,60 @@
/*
/**********************************************************
- /* Public API, main-level resolution methods
+ /* Lazily-operating accessors
/**********************************************************
*/
- /**
- * Initialization method that will recursively collect Jackson
- * annotations for this class and all super classes and
- * interfaces.
- */
- private AnnotationMap _resolveClassAnnotations()
- {
- AnnotationMap ca = new AnnotationMap();
- // Should skip processing if annotation processing disabled
- if (_annotationIntrospector != null) {
- // add mix-in annotations first (overrides)
- if (_primaryMixIn != null) {
- _addClassMixIns(ca, _class, _primaryMixIn);
- }
- // first, annotations from the class itself:
- _addAnnotationsIfNotPresent(ca,
- ClassUtil.findClassAnnotations(_class));
-
- // and then from super types
- for (JavaType type : _superTypes) {
- // and mix mix-in annotations in-between
- _addClassMixIns(ca, type);
- _addAnnotationsIfNotPresent(ca,
- ClassUtil.findClassAnnotations(type.getRawClass()));
- }
- /* and finally... any annotations there might be for plain
- * old Object.class: separate because for all other purposes
- * it is just ignored (not included in super types)
- */
- /* 12-Jul-2009, tatu: Should this be done for interfaces too?
- * For now, yes, seems useful for some cases, and not harmful for any?
- */
- _addClassMixIns(ca, Object.class);
- }
- return ca;
- }
-
- /**
- * Initialization method that will find out all constructors
- * and potential static factory methods the class has.
- */
- private void resolveCreators()
- {
- // Constructor also always members of this class
- TypeResolutionContext typeContext = this;
-
- // 30-Apr-2016, tatu: [databind#1215]: Actually, while true, this does
- // NOT apply to context since sub-class may have type bindings
-// TypeResolutionContext typeContext = new TypeResolutionContext.Basic(_typeFactory, _type.getBindings());
-
- // Then see which constructors we have
- List<AnnotatedConstructor> constructors = null;
-
- // 18-Jun-2016, tatu: Enum constructors will never be useful (unlike
- // possibly static factory methods); but they can be royal PITA
- // due to some oddities by JVM; see:
- // [https://github.com/FasterXML/jackson-module-parameter-names/issues/35]
- // for more. So, let's just skip them.
- if (!_type.isEnumType()) {
- ClassUtil.Ctor[] declaredCtors = ClassUtil.getConstructors(_class);
- for (ClassUtil.Ctor ctor : declaredCtors) {
- if (_isIncludableConstructor(ctor.getConstructor())) {
- if (ctor.getParamCount() == 0) {
- _defaultConstructor = _constructDefaultConstructor(ctor, typeContext);
- } else {
- if (constructors == null) {
- constructors = new ArrayList<AnnotatedConstructor>(Math.max(10, declaredCtors.length));
- }
- constructors.add(_constructNonDefaultConstructor(ctor, typeContext));
- }
- }
- }
- }
- if (constructors == null) {
- _constructors = Collections.emptyList();
- } else {
- _constructors = constructors;
- }
- // and if need be, augment with mix-ins
- if (_primaryMixIn != null) {
- if (_defaultConstructor != null || !_constructors.isEmpty()) {
- _addConstructorMixIns(_primaryMixIn);
- }
- }
-
- /* And then... let's remove all constructors that are deemed
- * ignorable after all annotations have been properly collapsed.
- */
- // AnnotationIntrospector is null if annotations not enabled; if so, can skip:
- if (_annotationIntrospector != null) {
- if (_defaultConstructor != null) {
- if (_annotationIntrospector.hasIgnoreMarker(_defaultConstructor)) {
- _defaultConstructor = null;
- }
- }
- if (_constructors != null) {
- // count down to allow safe removal
- for (int i = _constructors.size(); --i >= 0; ) {
- if (_annotationIntrospector.hasIgnoreMarker(_constructors.get(i))) {
- _constructors.remove(i);
- }
- }
- }
- }
- List<AnnotatedMethod> creatorMethods = null;
-
- // Then static methods which are potential factory methods
- for (Method m : _findClassMethods(_class)) {
- if (!Modifier.isStatic(m.getModifiers())) {
- continue;
- }
- // all factory methods are fine:
- //int argCount = m.getParameterTypes().length;
- if (creatorMethods == null) {
- creatorMethods = new ArrayList<AnnotatedMethod>(8);
- }
- creatorMethods.add(_constructCreatorMethod(m, typeContext));
- }
- if (creatorMethods == null) {
- _creatorMethods = Collections.emptyList();
- } else {
- _creatorMethods = creatorMethods;
- // mix-ins to mix in?
- if (_primaryMixIn != null) {
- _addFactoryMixIns(_primaryMixIn);
- }
- // anything to ignore at this point?
- if (_annotationIntrospector != null) {
- // count down to allow safe removal
- for (int i = _creatorMethods.size(); --i >= 0; ) {
- if (_annotationIntrospector.hasIgnoreMarker(_creatorMethods.get(i))) {
- _creatorMethods.remove(i);
- }
- }
- }
- }
- _creatorsResolved = true;
- }
-
- /**
- * Method for resolving member method information: aggregating all non-static methods
- * and combining annotations (to implement method-annotation inheritance)
- *
- * @param methodFilter Filter used to determine which methods to include
- */
- private void resolveMemberMethods()
- {
- _memberMethods = _resolveMemberMethods();
- }
-
- private AnnotatedMethodMap _resolveMemberMethods()
- {
- AnnotatedMethodMap memberMethods = new AnnotatedMethodMap();
- AnnotatedMethodMap mixins = new AnnotatedMethodMap();
- // first: methods from the class itself
- _addMemberMethods(_class, this, memberMethods, _primaryMixIn, mixins);
-
- // and then augment these with annotations from super-types:
- for (JavaType type : _superTypes) {
- Class<?> mixin = (_mixInResolver == null) ? null : _mixInResolver.findMixInClassFor(type.getRawClass());
- _addMemberMethods(type.getRawClass(),
- new TypeResolutionContext.Basic(_typeFactory, type.getBindings()),
- memberMethods, mixin, mixins);
- }
- // Special case: mix-ins for Object.class? (to apply to ALL classes)
- if (_mixInResolver != null) {
- Class<?> mixin = _mixInResolver.findMixInClassFor(Object.class);
- if (mixin != null) {
- _addMethodMixIns(_class, memberMethods, mixin, mixins);
- }
- }
-
- /* Any unmatched mix-ins? Most likely error cases (not matching
- * any method); but there is one possible real use case:
- * exposing Object#hashCode (alas, Object#getClass can NOT be
- * exposed)
- */
- // 14-Feb-2011, tatu: AnnotationIntrospector is null if annotations not enabled; if so, can skip:
- if (_annotationIntrospector != null) {
- if (!mixins.isEmpty()) {
- Iterator<AnnotatedMethod> it = mixins.iterator();
- while (it.hasNext()) {
- AnnotatedMethod mixIn = it.next();
- try {
- Method m = Object.class.getDeclaredMethod(mixIn.getName(), mixIn.getRawParameterTypes());
- if (m != null) {
- // Since it's from java.lang.Object, no generics, no need for real type context:
- AnnotatedMethod am = _constructMethod(m, this);
- _addMixOvers(mixIn.getAnnotated(), am, false);
- memberMethods.add(am);
- }
- } catch (Exception e) { }
- }
- }
- }
- return memberMethods;
- }
-
- /**
- * Method that will collect all member (non-static) fields
- * that are either public, or have at least a single annotation
- * associated with them.
- */
- private void resolveFields()
- {
- Map<String,AnnotatedField> foundFields = _findFields(_type, this, null);
- List<AnnotatedField> f;
- if (foundFields == null || foundFields.size() == 0) {
- f = Collections.emptyList();
- } else {
- f = new ArrayList<AnnotatedField>(foundFields.size());
- f.addAll(foundFields.values());
- }
- _fields = f;
- }
-
- /*
- /**********************************************************
- /* Helper methods for resolving class annotations
- /* (resolution consisting of inheritance, overrides,
- /* and injection of mix-ins as necessary)
- /**********************************************************
- */
-
- /**
- * Helper method for adding any mix-in annotations specified
- * class might have.
- */
- protected void _addClassMixIns(AnnotationMap annotations, JavaType target)
- {
- if (_mixInResolver != null) {
- final Class<?> toMask = target.getRawClass();
- _addClassMixIns(annotations, toMask, _mixInResolver.findMixInClassFor(toMask));
- }
- }
-
- protected void _addClassMixIns(AnnotationMap annotations, Class<?> target)
- {
- if (_mixInResolver != null) {
- _addClassMixIns(annotations, target, _mixInResolver.findMixInClassFor(target));
- }
- }
-
- protected void _addClassMixIns(AnnotationMap annotations, Class<?> toMask,
- Class<?> mixin)
- {
- if (mixin == null) {
- return;
- }
- // Ok, first: annotations from mix-in class itself:
- _addAnnotationsIfNotPresent(annotations, ClassUtil.findClassAnnotations(mixin));
-
- /* And then from its supertypes, if any. But note that we will
- * only consider super-types up until reaching the masked
- * class (if found); this because often mix-in class
- * is a sub-class (for convenience reasons). And if so, we
- * absolutely must NOT include super types of masked class,
- * as that would inverse precedence of annotations.
- */
- for (Class<?> parent : ClassUtil.findSuperClasses(mixin, toMask, false)) {
- _addAnnotationsIfNotPresent(annotations, ClassUtil.findClassAnnotations(parent));
- }
- }
-
- /*
- /**********************************************************
- /* Helper methods for populating creator (ctor, factory) information
- /**********************************************************
- */
-
- protected void _addConstructorMixIns(Class<?> mixin)
- {
- MemberKey[] ctorKeys = null;
- int ctorCount = (_constructors == null) ? 0 : _constructors.size();
- for (ClassUtil.Ctor ctor0 : ClassUtil.getConstructors(mixin)) {
- Constructor<?> ctor = ctor0.getConstructor();
- if (ctor.getParameterTypes().length == 0) {
- if (_defaultConstructor != null) {
- _addMixOvers(ctor, _defaultConstructor, false);
- }
+ private final List<AnnotatedField> _fields() {
+ List<AnnotatedField> f = _fields;
+ if (f == null) {
+ // 09-Jun-2017, tatu: _type only null for primordial, placeholder array types.
+ if (_type == null) {
+ f = Collections.emptyList();
} else {
- if (ctorKeys == null) {
- ctorKeys = new MemberKey[ctorCount];
- for (int i = 0; i < ctorCount; ++i) {
- ctorKeys[i] = new MemberKey(_constructors.get(i).getAnnotated());
- }
- }
- MemberKey key = new MemberKey(ctor);
-
- for (int i = 0; i < ctorCount; ++i) {
- if (!key.equals(ctorKeys[i])) {
- continue;
- }
- _addMixOvers(ctor, _constructors.get(i), true);
- break;
- }
+ f = AnnotatedFieldCollector.collectFields(_annotationIntrospector,
+ this, _mixInResolver, _typeFactory, _type);
}
+ _fields = f;
}
+ return f;
}
- protected void _addFactoryMixIns(Class<?> mixin)
- {
- MemberKey[] methodKeys = null;
- int methodCount = _creatorMethods.size();
-
- for (Method m : ClassUtil.getDeclaredMethods(mixin)) {
- if (!Modifier.isStatic(m.getModifiers())) {
- continue;
- }
- if (m.getParameterTypes().length == 0) {
- continue;
- }
- if (methodKeys == null) {
- methodKeys = new MemberKey[methodCount];
- for (int i = 0; i < methodCount; ++i) {
- methodKeys[i] = new MemberKey(_creatorMethods.get(i).getAnnotated());
- }
- }
- MemberKey key = new MemberKey(m);
- for (int i = 0; i < methodCount; ++i) {
- if (!key.equals(methodKeys[i])) {
- continue;
- }
- _addMixOvers(m, _creatorMethods.get(i), true);
- break;
- }
- }
- }
-
- /*
- /**********************************************************
- /* Helper methods for populating method information
- /**********************************************************
- */
-
- protected void _addMemberMethods(Class<?> cls, TypeResolutionContext typeContext,
- AnnotatedMethodMap methods,
- Class<?> mixInCls, AnnotatedMethodMap mixIns)
- {
- // first, mixIns, since they have higher priority then class methods
- if (mixInCls != null) {
- _addMethodMixIns(cls, methods, mixInCls, mixIns);
- }
- if (cls == null) { // just so caller need not check when passing super-class
- return;
- }
- // then methods from the class itself
- for (Method m : _findClassMethods(cls)) {
- if (!_isIncludableMemberMethod(m)) {
- continue;
- }
- AnnotatedMethod old = methods.find(m);
- if (old == null) {
- AnnotatedMethod newM = _constructMethod(m, typeContext);
- methods.add(newM);
- // Ok, but is there a mix-in to connect now?
- old = mixIns.remove(m);
- if (old != null) {
- _addMixOvers(old.getAnnotated(), newM, false);
- }
+ private final AnnotatedMethodMap _methods() {
+ AnnotatedMethodMap m = _memberMethods;
+ if (m == null) {
+ // 09-Jun-2017, tatu: _type only null for primordial, placeholder array types.
+ // NOTE: would be great to have light-weight shareable maps; no such impl exists for now
+ if (_type == null) {
+ m = new AnnotatedMethodMap();
} else {
- /* If sub-class already has the method, we only want to augment
- * annotations with entries that are not masked by sub-class.
- */
- _addMixUnders(m, old);
-
- /* 06-Jan-2010, tatu: [JACKSON-450] Except that if method we saw first is
- * from an interface, and we now find a non-interface definition, we should
- * use this method, but with combination of annotations.
- * This helps (or rather, is essential) with JAXB annotations and
- * may also result in faster method calls (interface calls are slightly
- * costlier than regular method calls)
- */
- if (old.getDeclaringClass().isInterface() && !m.getDeclaringClass().isInterface()) {
- methods.add(old.withMethod(m));
- }
+ m = AnnotatedMethodCollector.collectMethods(_annotationIntrospector,
+ this,
+ _mixInResolver, _typeFactory,
+ _type, _superTypes, _primaryMixIn);
}
+ _memberMethods = m;
}
+ return m;
}
- protected void _addMethodMixIns(Class<?> targetClass, AnnotatedMethodMap methods,
- Class<?> mixInCls, AnnotatedMethodMap mixIns)
- {
-// List<Class<?>> parents = ClassUtil.findSuperClasses(mixInCls, targetClass, true);
-
- List<Class<?>> parents = ClassUtil.findRawSuperTypes(mixInCls, targetClass, true);
- for (Class<?> mixin : parents) {
- for (Method m : ClassUtil.getDeclaredMethods(mixin)) {
- if (!_isIncludableMemberMethod(m)) {
- continue;
- }
- AnnotatedMethod am = methods.find(m);
- /* Do we already have a method to augment (from sub-class
- * that will mask this mixIn)? If so, add if visible
- * without masking (no such annotation)
- */
- if (am != null) {
- _addMixUnders(m, am);
- /* Otherwise will have precedence, but must wait
- * until we find the real method (mixIn methods are
- * just placeholder, can't be called)
- */
- } else {
- // Well, or, as per [databind#515], multi-level merge within mixins...
- am = mixIns.find(m);
- if (am != null) {
- _addMixUnders(m, am);
- } else {
- // 03-Nov-2015, tatu: Mix-in method never called, should not need
- // to resolve generic types, so this class is fine as context
- mixIns.add(_constructMethod(m, this));
- }
- }
+ private final Creators _creators() {
+ Creators c = _creators;
+ if (c == null) {
+ if (_type == null) {
+ c = NO_CREATORS;
+ } else {
+ c = AnnotatedCreatorCollector.collectCreators(_annotationIntrospector,
+ this, _type, _primaryMixIn);
}
+ _creators = c;
}
+ return c;
}
/*
/**********************************************************
- /* Helper methods for populating field information
- /**********************************************************
- */
-
- protected Map<String,AnnotatedField> _findFields(JavaType type,
- TypeResolutionContext typeContext, Map<String,AnnotatedField> fields)
- {
- /* First, a quick test: we only care for regular classes (not
- * interfaces, primitive types etc), except for Object.class.
- * A simple check to rule out other cases is to see if there
- * is a super class or not.
- */
- JavaType parent = type.getSuperClass();
- if (parent != null) {
- final Class<?> cls = type.getRawClass();
- // Let's add super-class' fields first, then ours.
- /* 21-Feb-2010, tatu: Need to handle masking: as per [JACKSON-226]
- * we otherwise get into trouble...
- */
- fields = _findFields(parent,
- new TypeResolutionContext.Basic(_typeFactory, parent.getBindings()),
- fields);
- for (Field f : ClassUtil.getDeclaredFields(cls)) {
- // static fields not included (transients are at this point, filtered out later)
- if (!_isIncludableField(f)) {
- continue;
- }
- /* Ok now: we can (and need) not filter out ignorable fields
- * at this point; partly because mix-ins haven't been
- * added, and partly because logic can be done when
- * determining get/settability of the field.
- */
- if (fields == null) {
- fields = new LinkedHashMap<String,AnnotatedField>();
- }
- fields.put(f.getName(), _constructField(f, typeContext));
- }
- // And then... any mix-in overrides?
- if (_mixInResolver != null) {
- Class<?> mixin = _mixInResolver.findMixInClassFor(cls);
- if (mixin != null) {
- _addFieldMixIns(mixin, cls, fields);
- }
- }
- }
- return fields;
- }
-
- /**
- * Method called to add field mix-ins from given mix-in class (and its fields)
- * into already collected actual fields (from introspected classes and their
- * super-classes)
- */
- protected void _addFieldMixIns(Class<?> mixInCls, Class<?> targetClass,
- Map<String,AnnotatedField> fields)
- {
- List<Class<?>> parents = ClassUtil.findSuperClasses(mixInCls, targetClass, true);
- for (Class<?> mixin : parents) {
- for (Field mixinField : ClassUtil.getDeclaredFields(mixin)) {
- // there are some dummy things (static, synthetic); better ignore
- if (!_isIncludableField(mixinField)) {
- continue;
- }
- String name = mixinField.getName();
- // anything to mask? (if not, quietly ignore)
- AnnotatedField maskedField = fields.get(name);
- if (maskedField != null) {
- _addOrOverrideAnnotations(maskedField, mixinField.getDeclaredAnnotations());
- }
- }
- }
- }
-
- /*
- /**********************************************************
- /* Helper methods, constructing value types
- /**********************************************************
- */
-
- protected AnnotatedMethod _constructMethod(Method m, TypeResolutionContext typeContext)
- {
- /* note: parameter annotations not used for regular (getter, setter)
- * methods; only for creator methods (static factory methods)
- * -- at least not yet!
- */
- if (_annotationIntrospector == null) { // when annotation processing is disabled
- return new AnnotatedMethod(typeContext, m, _emptyAnnotationMap(), null);
- }
- return new AnnotatedMethod(typeContext, m, _collectRelevantAnnotations(m.getDeclaredAnnotations()), null);
- }
-
- protected AnnotatedConstructor _constructDefaultConstructor(ClassUtil.Ctor ctor,
- TypeResolutionContext typeContext)
- {
- if (_annotationIntrospector == null) { // when annotation processing is disabled
- return new AnnotatedConstructor(typeContext, ctor.getConstructor(), _emptyAnnotationMap(), NO_ANNOTATION_MAPS);
- }
- return new AnnotatedConstructor(typeContext, ctor.getConstructor(),
- _collectRelevantAnnotations(ctor.getDeclaredAnnotations()), NO_ANNOTATION_MAPS);
- }
-
- protected AnnotatedConstructor _constructNonDefaultConstructor(ClassUtil.Ctor ctor,
- TypeResolutionContext typeContext)
- {
- final int paramCount = ctor.getParamCount();
- if (_annotationIntrospector == null) { // when annotation processing is disabled
- return new AnnotatedConstructor(typeContext, ctor.getConstructor(),
- _emptyAnnotationMap(), _emptyAnnotationMaps(paramCount));
- }
-
- /* Looks like JDK has discrepancy, whereas annotations for implicit 'this'
- * (for non-static inner classes) are NOT included, but type is?
- * Strange, sounds like a bug. Alas, we can't really fix that...
- */
- if (paramCount == 0) { // no-arg default constructors, can simplify slightly
- return new AnnotatedConstructor(typeContext, ctor.getConstructor(),
- _collectRelevantAnnotations(ctor.getDeclaredAnnotations()), NO_ANNOTATION_MAPS);
- }
- // Also: enum value constructors
- AnnotationMap[] resolvedAnnotations;
- Annotation[][] paramAnns = ctor.getParameterAnnotations();
- if (paramCount != paramAnns.length) {
- // Limits of the work-around (to avoid hiding real errors):
- // first, only applicable for member classes and then either:
-
- resolvedAnnotations = null;
- Class<?> dc = ctor.getDeclaringClass();
- // (a) is enum, which have two extra hidden params (name, index)
- if (dc.isEnum() && (paramCount == paramAnns.length + 2)) {
- Annotation[][] old = paramAnns;
- paramAnns = new Annotation[old.length+2][];
- System.arraycopy(old, 0, paramAnns, 2, old.length);
- resolvedAnnotations = _collectRelevantAnnotations(paramAnns);
- } else if (dc.isMemberClass()) {
- // (b) non-static inner classes, get implicit 'this' for parameter, not annotation
- if (paramCount == (paramAnns.length + 1)) {
- // hack attack: prepend a null entry to make things match
- Annotation[][] old = paramAnns;
- paramAnns = new Annotation[old.length+1][];
- System.arraycopy(old, 0, paramAnns, 1, old.length);
- resolvedAnnotations = _collectRelevantAnnotations(paramAnns);
- }
- }
- if (resolvedAnnotations == null) {
- throw new IllegalStateException("Internal error: constructor for "+ctor.getDeclaringClass().getName()
- +" has mismatch: "+paramCount+" parameters; "+paramAnns.length+" sets of annotations");
- }
- } else {
- resolvedAnnotations = _collectRelevantAnnotations(paramAnns);
- }
- return new AnnotatedConstructor(typeContext, ctor.getConstructor(),
- _collectRelevantAnnotations(ctor.getDeclaredAnnotations()), resolvedAnnotations);
- }
-
- protected AnnotatedMethod _constructCreatorMethod(Method m, TypeResolutionContext typeContext)
- {
- final int paramCount = m.getParameterTypes().length;
- if (_annotationIntrospector == null) { // when annotation processing is disabled
- return new AnnotatedMethod(typeContext, m, _emptyAnnotationMap(), _emptyAnnotationMaps(paramCount));
- }
- if (paramCount == 0) { // common enough we can slightly optimize
- return new AnnotatedMethod(typeContext, m, _collectRelevantAnnotations(m.getDeclaredAnnotations()),
- NO_ANNOTATION_MAPS);
- }
- return new AnnotatedMethod(typeContext, m, _collectRelevantAnnotations(m.getDeclaredAnnotations()),
- _collectRelevantAnnotations(m.getParameterAnnotations()));
- }
-
- protected AnnotatedField _constructField(Field f, TypeResolutionContext typeContext)
- {
- if (_annotationIntrospector == null) { // when annotation processing is disabled
- return new AnnotatedField(typeContext, f, _emptyAnnotationMap());
- }
- return new AnnotatedField(typeContext, f, _collectRelevantAnnotations(f.getDeclaredAnnotations()));
- }
-
- private AnnotationMap _emptyAnnotationMap() {
- return new AnnotationMap();
- }
-
- private AnnotationMap[] _emptyAnnotationMaps(int count) {
- if (count == 0) {
- return NO_ANNOTATION_MAPS;
- }
- AnnotationMap[] maps = new AnnotationMap[count];
- for (int i = 0; i < count; ++i) {
- maps[i] = _emptyAnnotationMap();
- }
- return maps;
- }
-
- /*
- /**********************************************************
- /* Helper methods, inclusion filtering
- /**********************************************************
- */
-
- protected boolean _isIncludableMemberMethod(Method m)
- {
- if (Modifier.isStatic(m.getModifiers())) {
- return false;
- }
- /* 07-Apr-2009, tatu: Looks like generics can introduce hidden
- * bridge and/or synthetic methods. I don't think we want to
- * consider those...
- */
- if (m.isSynthetic() || m.isBridge()) {
- return false;
- }
- // also, for now we have no use for methods with 2 or more arguments:
- int pcount = m.getParameterTypes().length;
- return (pcount <= 2);
- }
-
- private boolean _isIncludableField(Field f)
- {
- // Most likely synthetic fields, if any, are to be skipped similar to methods
- if (f.isSynthetic()) {
- return false;
- }
- // Static fields are never included. Transient are (since 2.6), for
- // purpose of propagating removal
- int mods = f.getModifiers();
- if (Modifier.isStatic(mods)) {
- return false;
- }
- return true;
- }
-
- // for [databind#1005]: do not use or expose synthetic constructors
- private boolean _isIncludableConstructor(Constructor<?> c)
- {
- return !c.isSynthetic();
- }
-
- /*
- /**********************************************************
- /* Helper methods, attaching annotations
- /**********************************************************
- */
-
- protected AnnotationMap[] _collectRelevantAnnotations(Annotation[][] anns)
- {
- int len = anns.length;
- AnnotationMap[] result = new AnnotationMap[len];
- for (int i = 0; i < len; ++i) {
- result[i] = _collectRelevantAnnotations(anns[i]);
- }
- return result;
- }
-
- protected AnnotationMap _collectRelevantAnnotations(Annotation[] anns)
- {
- return _addAnnotationsIfNotPresent(new AnnotationMap(), anns);
- }
-
- /* Helper method used to add all applicable annotations from given set.
- * Takes into account possible "annotation bundles" (meta-annotations to
- * include instead of main-level annotation)
- */
- private AnnotationMap _addAnnotationsIfNotPresent(AnnotationMap result, Annotation[] anns)
- {
- if (anns != null) {
- List<Annotation> fromBundles = null;
- for (Annotation ann : anns) { // first: direct annotations
- // note: we will NOT filter out non-Jackson anns any more
- boolean wasNotPresent = result.addIfNotPresent(ann);
- if (wasNotPresent && _isAnnotationBundle(ann)) {
- fromBundles = _addFromBundle(ann, fromBundles);
- }
- }
- if (fromBundles != null) { // and secondarily handle bundles, if any found: precedence important
- _addAnnotationsIfNotPresent(result, fromBundles.toArray(new Annotation[fromBundles.size()]));
- }
- }
- return result;
- }
-
- private List<Annotation> _addFromBundle(Annotation bundle, List<Annotation> result)
- {
- for (Annotation a : ClassUtil.findClassAnnotations(bundle.annotationType())) {
- // minor optimization: by-pass 2 common JDK meta-annotations
- if ((a instanceof Target) || (a instanceof Retention)) {
- continue;
- }
- if (result == null) {
- result = new ArrayList<Annotation>();
- }
- result.add(a);
- }
- return result;
- }
-
- private void _addAnnotationsIfNotPresent(AnnotatedMember target, Annotation[] anns)
- {
- if (anns != null) {
- List<Annotation> fromBundles = null;
- for (Annotation ann : anns) { // first: direct annotations
- boolean wasNotPresent = target.addIfNotPresent(ann);
- if (wasNotPresent && _isAnnotationBundle(ann)) {
- fromBundles = _addFromBundle(ann, fromBundles);
- }
- }
- if (fromBundles != null) { // and secondarily handle bundles, if any found: precedence important
- _addAnnotationsIfNotPresent(target, fromBundles.toArray(new Annotation[fromBundles.size()]));
- }
- }
- }
-
- private void _addOrOverrideAnnotations(AnnotatedMember target, Annotation[] anns)
- {
- if (anns != null) {
- List<Annotation> fromBundles = null;
- for (Annotation ann : anns) { // first: direct annotations
- boolean wasModified = target.addOrOverride(ann);
- if (wasModified && _isAnnotationBundle(ann)) {
- fromBundles = _addFromBundle(ann, fromBundles);
- }
- }
- if (fromBundles != null) { // and then bundles, if any: important for precedence
- _addOrOverrideAnnotations(target, fromBundles.toArray(new Annotation[fromBundles.size()]));
- }
- }
- }
-
- /**
- * @param addParamAnnotations Whether parameter annotations are to be
- * added as well
- */
- protected void _addMixOvers(Constructor<?> mixin, AnnotatedConstructor target,
- boolean addParamAnnotations)
- {
- _addOrOverrideAnnotations(target, mixin.getDeclaredAnnotations());
- if (addParamAnnotations) {
- Annotation[][] pa = mixin.getParameterAnnotations();
- for (int i = 0, len = pa.length; i < len; ++i) {
- for (Annotation a : pa[i]) {
- target.addOrOverrideParam(i, a);
- }
- }
- }
- }
-
- /**
- * @param addParamAnnotations Whether parameter annotations are to be
- * added as well
- */
- protected void _addMixOvers(Method mixin, AnnotatedMethod target,
- boolean addParamAnnotations)
- {
- _addOrOverrideAnnotations(target, mixin.getDeclaredAnnotations());
- if (addParamAnnotations) {
- Annotation[][] pa = mixin.getParameterAnnotations();
- for (int i = 0, len = pa.length; i < len; ++i) {
- for (Annotation a : pa[i]) {
- target.addOrOverrideParam(i, a);
- }
- }
- }
- }
-
- /**
- * Method that will add annotations from specified source method to target method,
- * but only if target does not yet have them.
- */
- protected void _addMixUnders(Method src, AnnotatedMethod target) {
- _addAnnotationsIfNotPresent(target, src.getDeclaredAnnotations());
- }
-
- private final boolean _isAnnotationBundle(Annotation ann) {
- return (_annotationIntrospector != null) && _annotationIntrospector.isAnnotationBundle(ann);
- }
-
- /**
- * Helper method that gets methods declared in given class; usually a simple thing,
- * but sometimes (as per [databind#785]) more complicated, depending on classloader
- * setup.
- *
- * @since 2.4.7
- */
- protected Method[] _findClassMethods(Class<?> cls)
- {
- try {
- return ClassUtil.getDeclaredMethods(cls);
- } catch (final NoClassDefFoundError ex) {
- // One of the methods had a class that was not found in the cls.getClassLoader.
- // Maybe the developer was nice and has a different class loader for this context.
- final ClassLoader loader = Thread.currentThread().getContextClassLoader();
- if (loader == null){
- // Nope... this is going to end poorly
- throw ex;
- }
- final Class<?> contextClass;
- try {
- contextClass = loader.loadClass(cls.getName());
- } catch (ClassNotFoundException e) {
- // !!! TODO: 08-May-2015, tatu: Chain appropriately once we have JDK 1.7/Java7 as baseline
- //ex.addSuppressed(e); Not until Jackson 2.7
- throw ex;
- }
- return contextClass.getDeclaredMethods(); // Cross fingers
- }
- }
-
- /*
- /**********************************************************
- /* Other methods
+ /* Standard method overrides
/**********************************************************
*/
@@ -1233,7 +405,43 @@
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
+ if (!ClassUtil.hasClass(o, getClass())) {
+ return false;
+ }
return ((AnnotatedClass) o)._class == _class;
}
+
+ /*
+ /**********************************************************
+ /* Helper classes
+ /**********************************************************
+ */
+
+ public static final class Creators
+ {
+ /**
+ * Default constructor of the annotated class, if it has one.
+ */
+ public final AnnotatedConstructor defaultConstructor;
+
+ /**
+ * Single argument constructors the class has, if any.
+ */
+ public final List<AnnotatedConstructor> constructors;
+
+ /**
+ * Single argument static methods that might be usable
+ * as factory methods
+ */
+ public final List<AnnotatedMethod> creatorMethods;
+
+ public Creators(AnnotatedConstructor defCtor,
+ List<AnnotatedConstructor> ctors,
+ List<AnnotatedMethod> ctorMethods)
+ {
+ defaultConstructor = defCtor;
+ constructors = ctors;
+ creatorMethods = ctorMethods;
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClassResolver.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClassResolver.java
new file mode 100644
index 0000000..65326e2
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClassResolver.java
@@ -0,0 +1,233 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.cfg.MapperConfig;
+import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
+import com.fasterxml.jackson.databind.type.TypeBindings;
+import com.fasterxml.jackson.databind.util.Annotations;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
+/**
+ * Helper class that contains logic for resolving annotations to construct
+ * {@link AnnotatedClass} instances.
+ *
+ * @since 2.9
+ */
+public class AnnotatedClassResolver
+{
+ private final static Annotations NO_ANNOTATIONS = AnnotationCollector.emptyAnnotations();
+
+ private final MapperConfig<?> _config;
+ private final AnnotationIntrospector _intr;
+ private final MixInResolver _mixInResolver;
+ private final TypeBindings _bindings;
+
+ private final JavaType _type;
+ private final Class<?> _class;
+ private final Class<?> _primaryMixin;
+
+ AnnotatedClassResolver(MapperConfig<?> config, JavaType type, MixInResolver r) {
+ _config = config;
+ _type = type;
+ _class = type.getRawClass();
+ _mixInResolver = r;
+ _bindings = type.getBindings();
+ _intr = config.isAnnotationProcessingEnabled()
+ ? config.getAnnotationIntrospector() : null;
+ _primaryMixin = _config.findMixInClassFor(_class);
+ }
+
+ AnnotatedClassResolver(MapperConfig<?> config, Class<?> cls, MixInResolver r) {
+ _config = config;
+ _type = null;
+ _class = cls;
+ _mixInResolver = r;
+ _bindings = TypeBindings.emptyBindings();
+ if (config == null) {
+ _intr = null;
+ _primaryMixin = null;
+ } else {
+ _intr = config.isAnnotationProcessingEnabled()
+ ? config.getAnnotationIntrospector() : null;
+ _primaryMixin = _config.findMixInClassFor(_class);
+ }
+ }
+
+ public static AnnotatedClass resolve(MapperConfig<?> config, JavaType forType,
+ MixInResolver r)
+ {
+ if (forType.isArrayType() && skippableArray(config, forType.getRawClass())) {
+ return createArrayType(config, forType.getRawClass());
+ }
+ return new AnnotatedClassResolver(config, forType, r).resolveFully();
+ }
+
+ public static AnnotatedClass resolveWithoutSuperTypes(MapperConfig<?> config, Class<?> forType) {
+ return resolveWithoutSuperTypes(config, forType, config);
+ }
+
+ public static AnnotatedClass resolveWithoutSuperTypes(MapperConfig<?> config, JavaType forType,
+ MixInResolver r)
+ {
+ if (forType.isArrayType() && skippableArray(config, forType.getRawClass())) {
+ return createArrayType(config, forType.getRawClass());
+ }
+ return new AnnotatedClassResolver(config, forType, r).resolveWithoutSuperTypes();
+ }
+
+ public static AnnotatedClass resolveWithoutSuperTypes(MapperConfig<?> config, Class<?> forType,
+ MixInResolver r)
+ {
+ if (forType.isArray() && skippableArray(config, forType)) {
+ return createArrayType(config, forType);
+ }
+ return new AnnotatedClassResolver(config, forType, r).resolveWithoutSuperTypes();
+ }
+
+ private static boolean skippableArray(MapperConfig<?> config, Class<?> type) {
+ return (config == null) || (config.findMixInClassFor(type) == null);
+
+ }
+
+ /**
+ * Internal helper method used for resolving a small set of "primordial" types for which
+ * we do not accept any annotation information or overrides.
+ */
+ static AnnotatedClass createPrimordial(Class<?> raw) {
+ return new AnnotatedClass(raw);
+ }
+
+ /**
+ * Internal helper method used for resolving array types, unless they happen
+ * to have associated mix-in to apply.
+ */
+ static AnnotatedClass createArrayType(MapperConfig<?> config, Class<?> raw) {
+ return new AnnotatedClass(raw);
+ }
+
+ AnnotatedClass resolveFully() {
+ List<JavaType> superTypes = ClassUtil.findSuperTypes(_type, null, false);
+ return new AnnotatedClass(_type, _class, superTypes, _primaryMixin,
+ resolveClassAnnotations(superTypes),
+ _bindings, _intr, _mixInResolver, _config.getTypeFactory());
+
+ }
+
+ AnnotatedClass resolveWithoutSuperTypes() {
+ List<JavaType> superTypes = Collections.<JavaType>emptyList();
+ return new AnnotatedClass(null, _class, superTypes, _primaryMixin,
+ resolveClassAnnotations(superTypes),
+ _bindings, _intr, _config, _config.getTypeFactory());
+ }
+
+ /*
+ /**********************************************************
+ /* Class annotation resolution
+ /**********************************************************
+ */
+
+ /**
+ * Initialization method that will recursively collect Jackson
+ * annotations for this class and all super classes and
+ * interfaces.
+ */
+ private Annotations resolveClassAnnotations(List<JavaType> superTypes)
+ {
+ // Should skip processing if annotation processing disabled
+ if (_intr == null) {
+ return NO_ANNOTATIONS;
+ }
+ AnnotationCollector resolvedCA = AnnotationCollector.emptyCollector();
+ // add mix-in annotations first (overrides)
+ if (_primaryMixin != null) {
+ resolvedCA = _addClassMixIns(resolvedCA, _class, _primaryMixin);
+ }
+ // then annotations from the class itself:
+ resolvedCA = _addAnnotationsIfNotPresent(resolvedCA,
+ ClassUtil.findClassAnnotations(_class));
+
+ // and then from super types
+ for (JavaType type : superTypes) {
+ // and mix mix-in annotations in-between
+ if (_mixInResolver != null) {
+ Class<?> cls = type.getRawClass();
+ resolvedCA = _addClassMixIns(resolvedCA, cls,
+ _mixInResolver.findMixInClassFor(cls));
+ }
+ resolvedCA = _addAnnotationsIfNotPresent(resolvedCA,
+ ClassUtil.findClassAnnotations(type.getRawClass()));
+ }
+ /* and finally... any annotations there might be for plain
+ * old Object.class: separate because for all other purposes
+ * it is just ignored (not included in super types)
+ */
+ // 12-Jul-2009, tatu: Should this be done for interfaces too?
+ // For now, yes, seems useful for some cases, and not harmful for any?
+ if (_mixInResolver != null) {
+ resolvedCA = _addClassMixIns(resolvedCA, Object.class,
+ _mixInResolver.findMixInClassFor(Object.class));
+ }
+ return resolvedCA.asAnnotations();
+ }
+
+ private AnnotationCollector _addClassMixIns(AnnotationCollector annotations,
+ Class<?> target, Class<?> mixin)
+ {
+ if (mixin != null) {
+ // Ok, first: annotations from mix-in class itself:
+ annotations = _addAnnotationsIfNotPresent(annotations, ClassUtil.findClassAnnotations(mixin));
+
+ // And then from its supertypes, if any. But note that we will only consider
+ // super-types up until reaching the masked class (if found); this because
+ // often mix-in class is a sub-class (for convenience reasons).
+ // And if so, we absolutely must NOT include super types of masked class,
+ // as that would inverse precedence of annotations.
+ for (Class<?> parent : ClassUtil.findSuperClasses(mixin, target, false)) {
+ annotations = _addAnnotationsIfNotPresent(annotations, ClassUtil.findClassAnnotations(parent));
+ }
+ }
+ return annotations;
+ }
+
+ private AnnotationCollector _addAnnotationsIfNotPresent(AnnotationCollector c,
+ Annotation[] anns)
+ {
+ if (anns != null) {
+ for (Annotation ann : anns) { // first: direct annotations
+ // note: we will NOT filter out non-Jackson annotations any more
+ if (!c.isPresent(ann)) {
+ c = c.addOrOverride(ann);
+ if (_intr.isAnnotationBundle(ann)) {
+ c = _addFromBundleIfNotPresent(c, ann);
+ }
+ }
+ }
+ }
+ return c;
+ }
+
+ private AnnotationCollector _addFromBundleIfNotPresent(AnnotationCollector c,
+ Annotation bundle)
+ {
+ for (Annotation ann : ClassUtil.findClassAnnotations(bundle.annotationType())) {
+ // minor optimization: by-pass 2 common JDK meta-annotations
+ if ((ann instanceof Target) || (ann instanceof Retention)) {
+ continue;
+ }
+ if (!c.isPresent(ann)) {
+ c = c.addOrOverride(ann);
+ if (_intr.isAnnotationBundle(ann)) {
+ c = _addFromBundleIfNotPresent(c, ann);
+ }
+ }
+ }
+ return c;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedConstructor.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedConstructor.java
index a5e81ad..4e17a00 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedConstructor.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedConstructor.java
@@ -176,10 +176,10 @@
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
- return ((AnnotatedConstructor) o)._constructor == _constructor;
+ return ClassUtil.hasClass(o, getClass())
+ && (((AnnotatedConstructor) o)._constructor == _constructor);
}
-
+
/*
/**********************************************************
/* JDK serialization handling
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedCreatorCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedCreatorCollector.java
new file mode 100644
index 0000000..0ec6d66
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedCreatorCollector.java
@@ -0,0 +1,358 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.introspect.AnnotatedClass.Creators;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
+/**
+ * Helper class used to contain details of how Creators (annotated constructors
+ * and static methods) are discovered to be accessed by and via {@link AnnotatedClass}.
+ *
+ * @since 2.9
+ */
+final class AnnotatedCreatorCollector
+ extends CollectorBase
+{
+ // // // Configuration
+
+ private final TypeResolutionContext _typeContext;
+
+ // // // Collected state
+
+ private AnnotatedConstructor _defaultConstructor;
+
+ AnnotatedCreatorCollector(AnnotationIntrospector intr,
+ TypeResolutionContext tc)
+ {
+ super(intr);
+ _typeContext = tc;
+ }
+
+ public static Creators collectCreators(AnnotationIntrospector intr,
+ TypeResolutionContext tc,
+ JavaType type, Class<?> primaryMixIn)
+ {
+ // Constructor also always members of resolved class, parent == resolution context
+ return new AnnotatedCreatorCollector(intr, tc)
+ .collect(type, primaryMixIn);
+ }
+
+ Creators collect(JavaType type, Class<?> primaryMixIn)
+ {
+ // 30-Apr-2016, tatu: [databind#1215]: Actually, while true, this does
+ // NOT apply to context since sub-class may have type bindings
+// TypeResolutionContext typeContext = new TypeResolutionContext.Basic(_typeFactory, _type.getBindings());
+
+ List<AnnotatedConstructor> constructors = _findPotentialConstructors(type, primaryMixIn);
+ List<AnnotatedMethod> factories = _findPotentialFactories(type, primaryMixIn);
+
+ /* And then... let's remove all constructors that are deemed
+ * ignorable after all annotations have been properly collapsed.
+ */
+ // AnnotationIntrospector is null if annotations not enabled; if so, can skip:
+ if (_intr != null) {
+ if (_defaultConstructor != null) {
+ if (_intr.hasIgnoreMarker(_defaultConstructor)) {
+ _defaultConstructor = null;
+ }
+ }
+ // count down to allow safe removal
+ for (int i = constructors.size(); --i >= 0; ) {
+ if (_intr.hasIgnoreMarker(constructors.get(i))) {
+ constructors.remove(i);
+ }
+ }
+ for (int i = factories.size(); --i >= 0; ) {
+ if (_intr.hasIgnoreMarker(factories.get(i))) {
+ factories.remove(i);
+ }
+ }
+ }
+ return new AnnotatedClass.Creators(_defaultConstructor, constructors, factories);
+ }
+
+ /**
+ * Helper method for locating constructors (and matching mix-in overrides)
+ * we might want to use; this is needed in order to mix information between
+ * the two and construct resulting {@link AnnotatedConstructor}s
+ */
+ private List<AnnotatedConstructor> _findPotentialConstructors(JavaType type,
+ Class<?> primaryMixIn)
+ {
+ ClassUtil.Ctor defaultCtor = null;
+ List<ClassUtil.Ctor> ctors = null;
+
+ // 18-Jun-2016, tatu: Enum constructors will never be useful (unlike
+ // possibly static factory methods); but they can be royal PITA
+ // due to some oddities by JVM; see:
+ // [https://github.com/FasterXML/jackson-module-parameter-names/issues/35]
+ // for more. So, let's just skip them.
+ if (!type.isEnumType()) {
+ ClassUtil.Ctor[] declaredCtors = ClassUtil.getConstructors(type.getRawClass());
+ for (ClassUtil.Ctor ctor : declaredCtors) {
+ if (!isIncludableConstructor(ctor.getConstructor())) {
+ continue;
+ }
+ if (ctor.getParamCount() == 0) {
+ defaultCtor = ctor;
+ } else {
+ if (ctors == null) {
+ ctors = new ArrayList<>();
+ }
+ ctors.add(ctor);
+ }
+ }
+ }
+ List<AnnotatedConstructor> result;
+ int ctorCount;
+ if (ctors == null) {
+ result = Collections.emptyList();
+ // Nothing found? Short-circuit
+ if (defaultCtor == null) {
+ return result;
+ }
+ ctorCount = 0;
+ } else {
+ ctorCount = ctors.size();
+ result = new ArrayList<>(ctorCount);
+ for (int i = 0; i < ctorCount; ++i) {
+ result.add(null);
+ }
+ }
+
+ // so far so good; but do we also need to find mix-ins overrides?
+ if (primaryMixIn != null) {
+ MemberKey[] ctorKeys = null;
+ for (ClassUtil.Ctor mixinCtor : ClassUtil.getConstructors(primaryMixIn)) {
+ if (mixinCtor.getParamCount() == 0) {
+ if (defaultCtor != null) {
+ _defaultConstructor = constructDefaultConstructor(defaultCtor, mixinCtor);
+ defaultCtor = null;
+ }
+ continue;
+ }
+ if (ctors != null) {
+ if (ctorKeys == null) {
+ ctorKeys = new MemberKey[ctorCount];
+ for (int i = 0; i < ctorCount; ++i) {
+ ctorKeys[i] = new MemberKey(ctors.get(i).getConstructor());
+ }
+ }
+ MemberKey key = new MemberKey(mixinCtor.getConstructor());
+
+ for (int i = 0; i < ctorCount; ++i) {
+ if (key.equals(ctorKeys[i])) {
+ result.set(i,
+ constructNonDefaultConstructor(ctors.get(i), mixinCtor));
+ break;
+ }
+ }
+ }
+ }
+ }
+ // Ok: anything within mix-ins has been resolved; anything remaining we must resolve
+ if (defaultCtor != null) {
+ _defaultConstructor = constructDefaultConstructor(defaultCtor, null);
+ }
+ for (int i = 0; i < ctorCount; ++i) {
+ AnnotatedConstructor ctor = result.get(i);
+ if (ctor == null) {
+ result.set(i,
+ constructNonDefaultConstructor(ctors.get(i), null));
+ }
+ }
+ return result;
+ }
+
+ private List<AnnotatedMethod> _findPotentialFactories(JavaType type, Class<?> primaryMixIn)
+ {
+ List<Method> candidates = null;
+
+ // First find all potentially relevant static methods
+ for (Method m : ClassUtil.getClassMethods(type.getRawClass())) {
+ if (!Modifier.isStatic(m.getModifiers())) {
+ continue;
+ }
+ // all factory methods are fine:
+ //int argCount = m.getParameterTypes().length;
+ if (candidates == null) {
+ candidates = new ArrayList<>();
+ }
+ candidates.add(m);
+ }
+ // and then locate mix-ins, if any
+ if (candidates == null) {
+ return Collections.emptyList();
+ }
+ int factoryCount = candidates.size();
+ List<AnnotatedMethod> result = new ArrayList<>(factoryCount);
+ for (int i = 0; i < factoryCount; ++i) {
+ result.add(null);
+ }
+ // so far so good; but do we also need to find mix-ins overrides?
+ if (primaryMixIn != null) {
+ MemberKey[] methodKeys = null;
+ for (Method mixinFactory : ClassUtil.getDeclaredMethods(primaryMixIn)) {
+ if (!Modifier.isStatic(mixinFactory.getModifiers())) {
+ continue;
+ }
+ if (methodKeys == null) {
+ methodKeys = new MemberKey[factoryCount];
+ for (int i = 0; i < factoryCount; ++i) {
+ methodKeys[i] = new MemberKey(candidates.get(i));
+ }
+ }
+ MemberKey key = new MemberKey(mixinFactory);
+ for (int i = 0; i < factoryCount; ++i) {
+ if (key.equals(methodKeys[i])) {
+ result.set(i,
+ constructFactoryCreator(candidates.get(i), mixinFactory));
+ break;
+ }
+ }
+ }
+ }
+ // Ok: anything within mix-ins has been resolved; anything remaining we must resolve
+ for (int i = 0; i < factoryCount; ++i) {
+ AnnotatedMethod factory = result.get(i);
+ if (factory == null) {
+ result.set(i,
+ constructFactoryCreator(candidates.get(i), null));
+ }
+ }
+ return result;
+ }
+
+ protected AnnotatedConstructor constructDefaultConstructor(ClassUtil.Ctor ctor,
+ ClassUtil.Ctor mixin)
+ {
+ if (_intr == null) { // when annotation processing is disabled
+ return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
+ _emptyAnnotationMap(), NO_ANNOTATION_MAPS);
+ }
+ return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
+ collectAnnotations(ctor, mixin),
+ collectAnnotations(ctor.getConstructor().getParameterAnnotations(),
+ (mixin == null) ? null : mixin.getConstructor().getParameterAnnotations()));
+ }
+
+ protected AnnotatedConstructor constructNonDefaultConstructor(ClassUtil.Ctor ctor,
+ ClassUtil.Ctor mixin)
+ {
+ final int paramCount = ctor.getParamCount();
+ if (_intr == null) { // when annotation processing is disabled
+ return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
+ _emptyAnnotationMap(), _emptyAnnotationMaps(paramCount));
+ }
+
+ /* Looks like JDK has discrepancy, whereas annotations for implicit 'this'
+ * (for non-static inner classes) are NOT included, but type is?
+ * Strange, sounds like a bug. Alas, we can't really fix that...
+ */
+ if (paramCount == 0) { // no-arg default constructors, can simplify slightly
+ return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
+ collectAnnotations(ctor, mixin),
+ NO_ANNOTATION_MAPS);
+ }
+ // Also: enum value constructors
+ AnnotationMap[] resolvedAnnotations;
+ Annotation[][] paramAnns = ctor.getParameterAnnotations();
+ if (paramCount != paramAnns.length) {
+ // Limits of the work-around (to avoid hiding real errors):
+ // first, only applicable for member classes and then either:
+
+ resolvedAnnotations = null;
+ Class<?> dc = ctor.getDeclaringClass();
+ // (a) is enum, which have two extra hidden params (name, index)
+ if (dc.isEnum() && (paramCount == paramAnns.length + 2)) {
+ Annotation[][] old = paramAnns;
+ paramAnns = new Annotation[old.length+2][];
+ System.arraycopy(old, 0, paramAnns, 2, old.length);
+ resolvedAnnotations = collectAnnotations(paramAnns, null);
+ } else if (dc.isMemberClass()) {
+ // (b) non-static inner classes, get implicit 'this' for parameter, not annotation
+ if (paramCount == (paramAnns.length + 1)) {
+ // hack attack: prepend a null entry to make things match
+ Annotation[][] old = paramAnns;
+ paramAnns = new Annotation[old.length+1][];
+ System.arraycopy(old, 0, paramAnns, 1, old.length);
+ paramAnns[0] = NO_ANNOTATIONS;
+ resolvedAnnotations = collectAnnotations(paramAnns, null);
+ }
+ }
+ if (resolvedAnnotations == null) {
+ throw new IllegalStateException(String.format(
+"Internal error: constructor for %s has mismatch: %d parameters; %d sets of annotations",
+ctor.getDeclaringClass().getName(), paramCount, paramAnns.length));
+ }
+ } else {
+ resolvedAnnotations = collectAnnotations(paramAnns,
+ (mixin == null) ? null : mixin.getParameterAnnotations());
+ }
+ return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
+ collectAnnotations(ctor, mixin), resolvedAnnotations);
+ }
+
+ protected AnnotatedMethod constructFactoryCreator(Method m, Method mixin)
+ {
+ final int paramCount = m.getParameterTypes().length;
+ if (_intr == null) { // when annotation processing is disabled
+ return new AnnotatedMethod(_typeContext, m, _emptyAnnotationMap(),
+ _emptyAnnotationMaps(paramCount));
+ }
+ if (paramCount == 0) { // common enough we can slightly optimize
+ return new AnnotatedMethod(_typeContext, m, collectAnnotations(m, mixin),
+ NO_ANNOTATION_MAPS);
+ }
+ return new AnnotatedMethod(_typeContext, m, collectAnnotations(m, mixin),
+ collectAnnotations(m.getParameterAnnotations(),
+ (mixin == null) ? null : mixin.getParameterAnnotations()));
+ }
+
+ private AnnotationMap[] collectAnnotations(Annotation[][] mainAnns, Annotation[][] mixinAnns) {
+ final int count = mainAnns.length;
+ AnnotationMap[] result = new AnnotationMap[count];
+ for (int i = 0; i < count; ++i) {
+ AnnotationCollector c = collectAnnotations(AnnotationCollector.emptyCollector(),
+ mainAnns[i]);
+ if (mixinAnns != null) {
+ c = collectAnnotations(c, mixinAnns[i]);
+ }
+ result[i] = c.asAnnotationMap();
+ }
+ return result;
+ }
+
+ // // NOTE: these are only called when we know we have AnnotationIntrospector
+
+ private AnnotationMap collectAnnotations(ClassUtil.Ctor main, ClassUtil.Ctor mixin) {
+ AnnotationCollector c = collectAnnotations(main.getConstructor().getDeclaredAnnotations());
+ if (mixin != null) {
+ c = collectAnnotations(c, mixin.getConstructor().getDeclaredAnnotations());
+ }
+ return c.asAnnotationMap();
+ }
+
+ private final AnnotationMap collectAnnotations(AnnotatedElement main, AnnotatedElement mixin) {
+ AnnotationCollector c = collectAnnotations(main.getDeclaredAnnotations());
+ if (mixin != null) {
+ c = collectAnnotations(c, mixin.getDeclaredAnnotations());
+ }
+ return c.asAnnotationMap();
+ }
+
+ // for [databind#1005]: do not use or expose synthetic constructors
+ private static boolean isIncludableConstructor(Constructor<?> c) {
+ return !c.isSynthetic();
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedField.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedField.java
index a499407..b81c51e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedField.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedField.java
@@ -18,7 +18,7 @@
/**
* Actual {@link Field} used for access.
*<p>
- * Transient since it can not be persisted directly using
+ * Transient since it cannot be persisted directly using
* JDK serialization
*/
protected final transient Field _field;
@@ -126,10 +126,6 @@
/**********************************************************
*/
- public String getFullName() {
- return getDeclaringClass().getName() + "#" + getName();
- }
-
public int getAnnotationCount() { return _annotations.size(); }
/**
@@ -145,8 +141,8 @@
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
- return ((AnnotatedField) o)._field == _field;
+ return ClassUtil.hasClass(o, getClass())
+ && (((AnnotatedField) o)._field == _field);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedFieldCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedFieldCollector.java
new file mode 100644
index 0000000..0dba88b
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedFieldCollector.java
@@ -0,0 +1,149 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
+public class AnnotatedFieldCollector
+ extends CollectorBase
+{
+ // // // Configuration
+
+ private final TypeFactory _typeFactory;
+ private final MixInResolver _mixInResolver;
+
+ // // // Collected state
+
+ AnnotatedFieldCollector(AnnotationIntrospector intr,
+ TypeFactory types, MixInResolver mixins)
+ {
+ super(intr);
+ _typeFactory = types;
+ _mixInResolver = (intr == null) ? null : mixins;
+ }
+
+ public static List<AnnotatedField> collectFields(AnnotationIntrospector intr,
+ TypeResolutionContext tc,
+ MixInResolver mixins, TypeFactory types,
+ JavaType type)
+ {
+ return new AnnotatedFieldCollector(intr, types, mixins).collect(tc, type);
+ }
+
+ List<AnnotatedField> collect(TypeResolutionContext tc, JavaType type)
+ {
+ Map<String,FieldBuilder> foundFields = _findFields(tc, type, null);
+ if (foundFields == null) {
+ return Collections.emptyList();
+ }
+ List<AnnotatedField> result = new ArrayList<>(foundFields.size());
+ for (FieldBuilder b : foundFields.values()) {
+ result.add(b.build());
+ }
+ return result;
+ }
+
+ private Map<String,FieldBuilder> _findFields(TypeResolutionContext tc,
+ JavaType type, Map<String,FieldBuilder> fields)
+ {
+ // First, a quick test: we only care for regular classes (not interfaces,
+ //primitive types etc), except for Object.class. A simple check to rule out
+ // other cases is to see if there is a super class or not.
+ JavaType parent = type.getSuperClass();
+ if (parent == null) {
+ return fields;
+ }
+ final Class<?> cls = type.getRawClass();
+ // Let's add super-class' fields first, then ours.
+ fields = _findFields(new TypeResolutionContext.Basic(_typeFactory, parent.getBindings()),
+ parent, fields);
+ for (Field f : ClassUtil.getDeclaredFields(cls)) {
+ // static fields not included (transients are at this point, filtered out later)
+ if (!_isIncludableField(f)) {
+ continue;
+ }
+ // Ok now: we can (and need) not filter out ignorable fields at this point; partly
+ // because mix-ins haven't been added, and partly because logic can be done
+ // when determining get/settability of the field.
+ if (fields == null) {
+ fields = new LinkedHashMap<>();
+ }
+ FieldBuilder b = new FieldBuilder(tc, f);
+ if (_intr != null) {
+ b.annotations = collectAnnotations(b.annotations, f.getDeclaredAnnotations());
+ }
+ fields.put(f.getName(), b);
+ }
+ // And then... any mix-in overrides?
+ if (_mixInResolver != null) {
+ Class<?> mixin = _mixInResolver.findMixInClassFor(cls);
+ if (mixin != null) {
+ _addFieldMixIns(mixin, cls, fields);
+ }
+ }
+ return fields;
+ }
+
+ /**
+ * Method called to add field mix-ins from given mix-in class (and its fields)
+ * into already collected actual fields (from introspected classes and their
+ * super-classes)
+ */
+ private void _addFieldMixIns(Class<?> mixInCls, Class<?> targetClass,
+ Map<String,FieldBuilder> fields)
+ {
+ List<Class<?>> parents = ClassUtil.findSuperClasses(mixInCls, targetClass, true);
+ for (Class<?> mixin : parents) {
+ for (Field mixinField : ClassUtil.getDeclaredFields(mixin)) {
+ // there are some dummy things (static, synthetic); better ignore
+ if (!_isIncludableField(mixinField)) {
+ continue;
+ }
+ String name = mixinField.getName();
+ // anything to mask? (if not, quietly ignore)
+ FieldBuilder b = fields.get(name);
+ if (b != null) {
+ b.annotations = collectAnnotations(b.annotations, mixinField.getDeclaredAnnotations());
+ }
+ }
+ }
+ }
+
+ private boolean _isIncludableField(Field f)
+ {
+ // Most likely synthetic fields, if any, are to be skipped similar to methods
+ if (f.isSynthetic()) {
+ return false;
+ }
+ // Static fields are never included. Transient are (since 2.6), for
+ // purpose of propagating removal
+ int mods = f.getModifiers();
+ if (Modifier.isStatic(mods)) {
+ return false;
+ }
+ return true;
+ }
+
+ private final static class FieldBuilder {
+ public final TypeResolutionContext typeContext;
+ public final Field field;
+
+ public AnnotationCollector annotations;
+
+ public FieldBuilder(TypeResolutionContext tc, Field f) {
+ typeContext = tc;
+ field = f;
+ annotations = AnnotationCollector.emptyCollector();
+ }
+
+ public AnnotatedField build() {
+ return new AnnotatedField(typeContext, field, annotations.asAnnotationMap());
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMember.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMember.java
index 423f209..0f7f3d2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMember.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMember.java
@@ -47,7 +47,15 @@
_typeContext = base._typeContext;
_annotations = base._annotations;
}
-
+
+ /**
+ * Fluent factory method that will construct a new instance that uses specified
+ * instance annotations instead of currently configured ones.
+ *
+ * @since 2.9 (promoted from `Annotated`)
+ */
+ public abstract Annotated withAnnotations(AnnotationMap fallback);
+
/**
* Actual physical class in which this memmber was declared.
*/
@@ -55,12 +63,19 @@
public abstract Member getMember();
+ public String getFullName() {
+ return getDeclaringClass().getName() + "#" + getName();
+ }
+
/**
* Accessor for {@link TypeResolutionContext} that is used for resolving
* full generic type of this member.
*
* @since 2.7
+ *
+ * @deprecated Since 2.9
*/
+ @Deprecated
public TypeResolutionContext getTypeContext() {
return _typeContext;
}
@@ -88,39 +103,25 @@
}
return _annotations.hasOneOf(annoClasses);
}
-
+
@Override
+ @Deprecated
public Iterable<Annotation> annotations() {
if (_annotations == null) {
return Collections.emptyList();
}
return _annotations.annotations();
}
-
- @Override
- protected AnnotationMap getAllAnnotations() {
+
+ /**
+ *<p>
+ * NOTE: promoted in 2.9 from `Annotated` up
+ */
+ public AnnotationMap getAllAnnotations() { // alas, used by at least one module, hence public
return _annotations;
}
/**
- * Method called to override an annotation, usually due to a mix-in
- * annotation masking or overriding an annotation 'real' constructor
- * has.
- */
- public final boolean addOrOverride(Annotation a) {
- return _annotations.add(a);
- }
-
- /**
- * Method called to augment annotations, by adding specified
- * annotation if and only if it is not yet present in the
- * annotation map we have.
- */
- public final boolean addIfNotPresent(Annotation a) {
- return _annotations.addIfNotPresent(a);
- }
-
- /**
* Method that can be called to modify access rights, by calling
* {@link java.lang.reflect.AccessibleObject#setAccessible} on
* the underlying annotated element.
@@ -140,15 +141,6 @@
}
/**
- * @deprecated Since 2.7 use {@link #fixAccess(boolean)} instead
- */
- @Deprecated
- public final void fixAccess() {
-// fixAccess(false);
- fixAccess(true);
- }
-
- /**
* Optional method that can be used to assign value of
* this member on given object, if this is a supported
* operation for member type.
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 e7d75ac..2338987 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java
@@ -36,7 +36,7 @@
{
super(ctxt, classAnn, paramAnnotations);
if (method == null) {
- throw new IllegalArgumentException("Can not construct AnnotatedMethod with null Method");
+ throw new IllegalArgumentException("Cannot construct AnnotatedMethod with null Method");
}
_method = method;
}
@@ -51,15 +51,7 @@
_method = null;
_serialization = ser;
}
-
- /**
- * Method that constructs a new instance with settings (annotations, parameter annotations)
- * of this instance, but with different physical {@link Method}.
- */
- public AnnotatedMethod withMethod(Method m) {
- return new AnnotatedMethod(_typeContext, m, _annotations, _paramAnnotations);
- }
-
+
@Override
public AnnotatedMethod withAnnotations(AnnotationMap ann) {
return new AnnotatedMethod(_typeContext, _method, ann, _paramAnnotations);
@@ -123,7 +115,7 @@
}
public final Object callOn(Object pojo) throws Exception {
- return _method.invoke(pojo);
+ return _method.invoke(pojo, (Object[]) null);
}
public final Object callOnWith(Object pojo, Object... args) throws Exception {
@@ -178,10 +170,7 @@
{
try {
_method.invoke(pojo, value);
- } catch (IllegalAccessException e) {
- throw new IllegalArgumentException("Failed to setValue() with method "
- +getFullName()+": "+e.getMessage(), e);
- } catch (InvocationTargetException e) {
+ } catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Failed to setValue() with method "
+getFullName()+": "+e.getMessage(), e);
}
@@ -191,11 +180,8 @@
public Object getValue(Object pojo) throws IllegalArgumentException
{
try {
- return _method.invoke(pojo);
- } catch (IllegalAccessException e) {
- throw new IllegalArgumentException("Failed to getValue() with method "
- +getFullName()+": "+e.getMessage(), e);
- } catch (InvocationTargetException e) {
+ return _method.invoke(pojo, (Object[]) null);
+ } catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Failed to getValue() with method "
+getFullName()+": "+e.getMessage(), e);
}
@@ -207,9 +193,9 @@
/*****************************************************
*/
+ @Override
public String getFullName() {
- return getDeclaringClass().getName() + "#" + getName() + "("
- +getParameterCount()+" params)";
+ return String.format("%s(%d params)", super.getFullName(), getParameterCount());
}
public Class<?>[] getRawParameterTypes()
@@ -260,10 +246,10 @@
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
- return ((AnnotatedMethod) o)._method == _method;
+ return ClassUtil.hasClass(o, getClass())
+ && (((AnnotatedMethod) o)._method == _method);
}
-
+
/*
/**********************************************************
/* JDK serialization handling
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethodCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethodCollector.java
new file mode 100644
index 0000000..0341e3a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethodCollector.java
@@ -0,0 +1,207 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
+public class AnnotatedMethodCollector
+ extends CollectorBase
+{
+ private final MixInResolver _mixInResolver;
+
+ AnnotatedMethodCollector(AnnotationIntrospector intr,
+ MixInResolver mixins)
+ {
+ super(intr);
+ _mixInResolver = (intr == null) ? null : mixins;
+ }
+
+ public static AnnotatedMethodMap collectMethods(AnnotationIntrospector intr,
+ TypeResolutionContext tc,
+ MixInResolver mixins, TypeFactory types,
+ JavaType type, List<JavaType> superTypes, Class<?> primaryMixIn)
+ {
+ // Constructor also always members of resolved class, parent == resolution context
+ return new AnnotatedMethodCollector(intr, mixins)
+ .collect(types, tc, type, superTypes, primaryMixIn);
+ }
+
+ AnnotatedMethodMap collect(TypeFactory typeFactory, TypeResolutionContext tc,
+ JavaType mainType, List<JavaType> superTypes, Class<?> primaryMixIn)
+ {
+ Map<MemberKey,MethodBuilder> methods = new LinkedHashMap<>();
+
+ // first: methods from the class itself
+ _addMemberMethods(tc, mainType.getRawClass(), methods, primaryMixIn);
+
+ // and then augment these with annotations from super-types:
+ for (JavaType type : superTypes) {
+ Class<?> mixin = (_mixInResolver == null) ? null : _mixInResolver.findMixInClassFor(type.getRawClass());
+ _addMemberMethods(
+ new TypeResolutionContext.Basic(typeFactory, type.getBindings()),
+ type.getRawClass(), methods, mixin);
+ }
+ // Special case: mix-ins for Object.class? (to apply to ALL classes)
+ boolean checkJavaLangObject = false;
+ if (_mixInResolver != null) {
+ Class<?> mixin = _mixInResolver.findMixInClassFor(Object.class);
+ if (mixin != null) {
+ _addMethodMixIns(tc, mainType.getRawClass(), methods, mixin); //, mixins);
+ checkJavaLangObject = true;
+ }
+ }
+
+ // Any unmatched mix-ins? Most likely error cases (not matching any method);
+ // but there is one possible real use case: exposing Object#hashCode
+ // (alas, Object#getClass can NOT be exposed)
+ // Since we only know of that ONE case, optimize for it
+ if (checkJavaLangObject && (_intr != null) && !methods.isEmpty()) {
+ // Could use lookup but probably as fast or faster to traverse
+ for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) {
+ MemberKey k = entry.getKey();
+ if (!"hashCode".equals(k.getName()) || (0 != k.argCount())) {
+ continue;
+ }
+ try {
+ // And with that, we can generate it appropriately
+ Method m = Object.class.getDeclaredMethod(k.getName());
+ if (m != null) {
+ MethodBuilder b = entry.getValue();
+ b.annotations = collectDefaultAnnotations(b.annotations,
+ m.getDeclaredAnnotations());
+ b.method = m;
+ }
+ } catch (Exception e) { }
+ }
+ }
+
+ // And then let's create the lookup map
+ if (methods.isEmpty()) {
+ return new AnnotatedMethodMap();
+ }
+ Map<MemberKey,AnnotatedMethod> actual = new LinkedHashMap<>(methods.size());
+ for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) {
+ AnnotatedMethod am = entry.getValue().build();
+ if (am != null) {
+ actual.put(entry.getKey(), am);
+ }
+ }
+ return new AnnotatedMethodMap(actual);
+ }
+
+ private void _addMemberMethods(TypeResolutionContext tc,
+ Class<?> cls, Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls)
+ {
+ // first, mixIns, since they have higher priority then class methods
+ if (mixInCls != null) {
+ _addMethodMixIns(tc, cls, methods, mixInCls);
+ }
+ if (cls == null) { // just so caller need not check when passing super-class
+ return;
+ }
+ // then methods from the class itself
+ for (Method m : ClassUtil.getClassMethods(cls)) {
+ if (!_isIncludableMemberMethod(m)) {
+ continue;
+ }
+ final MemberKey key = new MemberKey(m);
+ MethodBuilder b = methods.get(key);
+ if (b == null) {
+ AnnotationCollector c = (_intr == null) ? AnnotationCollector.emptyCollector()
+ : collectAnnotations(m.getDeclaredAnnotations());
+ methods.put(key, new MethodBuilder(tc, m, c));
+ } else {
+ if (_intr != null) {
+ b.annotations = collectDefaultAnnotations(b.annotations, m.getDeclaredAnnotations());
+ }
+ Method old = b.method;
+ if (old == null) { // had "mix-over", replace
+ b.method = m;
+// } else if (old.getDeclaringClass().isInterface() && !m.getDeclaringClass().isInterface()) {
+ } else if (Modifier.isAbstract(old.getModifiers())
+ && !Modifier.isAbstract(m.getModifiers())) {
+ // 06-Jan-2010, tatu: Except that if method we saw first is
+ // from an interface, and we now find a non-interface definition, we should
+ // use this method, but with combination of annotations.
+ // This helps (or rather, is essential) with JAXB annotations and
+ // may also result in faster method calls (interface calls are slightly
+ // costlier than regular method calls)
+ b.method = m;
+ // 23-Aug-2017, tatu: [databind#1705] Also need to change the type resolution context if so
+ // (note: mix-over case above shouldn't need it)
+ b.typeContext = tc;
+ }
+ }
+ }
+ }
+
+ protected void _addMethodMixIns(TypeResolutionContext tc, Class<?> targetClass,
+ Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls)
+ {
+ if (_intr == null) {
+ return;
+ }
+ for (Class<?> mixin : ClassUtil.findRawSuperTypes(mixInCls, targetClass, true)) {
+ for (Method m : ClassUtil.getDeclaredMethods(mixin)) {
+ if (!_isIncludableMemberMethod(m)) {
+ continue;
+ }
+ final MemberKey key = new MemberKey(m);
+ MethodBuilder b = methods.get(key);
+ Annotation[] anns = m.getDeclaredAnnotations();
+ if (b == null) {
+ // nothing yet; add but do NOT specify method -- this marks it
+ // as "mix-over", floating mix-in
+ methods.put(key, new MethodBuilder(tc, null, collectAnnotations(anns)));
+ } else {
+ b.annotations = collectDefaultAnnotations(b.annotations, anns);
+ }
+ }
+ }
+ }
+
+ private boolean _isIncludableMemberMethod(Method m)
+ {
+ if (Modifier.isStatic(m.getModifiers())
+ // Looks like generics can introduce hidden bridge and/or synthetic methods.
+ // I don't think we want to consider those...
+ || m.isSynthetic() || m.isBridge()) {
+ return false;
+ }
+ // also, for now we have no use for methods with more than 2 arguments:
+ // (2 argument methods for "any setter", fwtw)
+ int pcount = m.getParameterTypes().length;
+ return (pcount <= 2);
+ }
+
+ private final static class MethodBuilder {
+ public TypeResolutionContext typeContext;
+
+ // Method left empty for "floating" mix-in, filled in as need be
+ public Method method;
+ public AnnotationCollector annotations;
+
+ public MethodBuilder(TypeResolutionContext tc, Method m,
+ AnnotationCollector ann) {
+ typeContext = tc;
+ method = m;
+ annotations = ann;
+ }
+
+ public AnnotatedMethod build() {
+ if (method == null) {
+ return null;
+ }
+ // 12-Apr-2017, tatu: Note that parameter annotations are NOT collected -- we could
+ // collect them if that'd make sense but...
+ return new AnnotatedMethod(typeContext, method, annotations.asAnnotationMap(), null);
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethodMap.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethodMap.java
index 0cfb387..dcd08a2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethodMap.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethodMap.java
@@ -11,40 +11,15 @@
public final class AnnotatedMethodMap
implements Iterable<AnnotatedMethod>
{
- protected LinkedHashMap<MemberKey,AnnotatedMethod> _methods;
+ protected Map<MemberKey,AnnotatedMethod> _methods;
public AnnotatedMethodMap() { }
/**
- * Method called to add specified annotated method in the Map.
+ * @since 2.9
*/
- public void add(AnnotatedMethod am)
- {
- if (_methods == null) {
- _methods = new LinkedHashMap<MemberKey,AnnotatedMethod>();
- }
- _methods.put(new MemberKey(am.getAnnotated()), am);
- }
-
- /**
- * Method called to remove specified method, assuming
- * it exists in the Map
- */
- public AnnotatedMethod remove(AnnotatedMethod am)
- {
- return remove(am.getAnnotated());
- }
-
- public AnnotatedMethod remove(Method m)
- {
- if (_methods != null) {
- return _methods.remove(new MemberKey(m));
- }
- return null;
- }
-
- public boolean isEmpty() {
- return (_methods == null || _methods.size() == 0);
+ public AnnotatedMethodMap(Map<MemberKey,AnnotatedMethod> m) {
+ _methods = m;
}
public int size() {
@@ -76,10 +51,9 @@
@Override
public Iterator<AnnotatedMethod> iterator()
{
- if (_methods != null) {
- return _methods.values().iterator();
+ if (_methods == null) {
+ return Collections.emptyIterator();
}
- List<AnnotatedMethod> empty = Collections.emptyList();
- return empty.iterator();
+ return _methods.values().iterator();
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedParameter.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedParameter.java
index 62ce143..9835c35 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedParameter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedParameter.java
@@ -3,11 +3,12 @@
import java.lang.reflect.*;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Object that represents method parameters, mostly so that associated
* annotations can be processed conveniently. Note that many of accessors
- * can not return meaningful values since parameters do not have stand-alone
+ * cannot return meaningful values since parameters do not have stand-alone
* JDK objects associated; so access should mostly be limited to checking
* annotation values which are properly aggregated and included.
*/
@@ -30,7 +31,7 @@
* Index of the parameter within argument list
*/
protected final int _index;
-
+
/*
/**********************************************************
/* Life-cycle
@@ -38,9 +39,10 @@
*/
public AnnotatedParameter(AnnotatedWithParams owner, JavaType type,
+ TypeResolutionContext typeContext,
AnnotationMap annotations, int index)
{
- super((owner == null) ? null : owner.getTypeContext(), annotations);
+ super(typeContext, annotations);
_owner = owner;
_type = type;
_index = index;
@@ -167,7 +169,9 @@
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
+ if (!ClassUtil.hasClass(o, getClass())) {
+ return false;
+ }
AnnotatedParameter other = (AnnotatedParameter) o;
return other._owner.equals(_owner) && (other._index == _index);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedWithParams.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedWithParams.java
index 8cf5a79..4f415a6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedWithParams.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedWithParams.java
@@ -84,7 +84,7 @@
public final AnnotatedParameter getParameter(int index) {
return new AnnotatedParameter(this, getParameterType(index),
- getParameterAnnotations(index), index);
+ _typeContext, getParameterAnnotations(index), index);
}
public abstract int getParameterCount();
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationCollector.java
new file mode 100644
index 0000000..940d435
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationCollector.java
@@ -0,0 +1,302 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.util.Annotations;
+
+/**
+ * Helper class used to collect annotations to be stored as
+ * {@link com.fasterxml.jackson.databind.util.Annotations} (like {@link AnnotationMap}).
+ *
+ * @since 2.9
+ */
+public abstract class AnnotationCollector
+{
+ protected final static Annotations NO_ANNOTATIONS = new NoAnnotations();
+
+ /**
+ * Optional data to carry along
+ */
+ protected final Object _data;
+
+ protected AnnotationCollector(Object d) {
+ _data = d;
+ }
+
+ public static Annotations emptyAnnotations() { return NO_ANNOTATIONS; }
+
+ public static AnnotationCollector emptyCollector() {
+ return EmptyCollector.instance;
+ }
+
+ public static AnnotationCollector emptyCollector(Object data) {
+ return new EmptyCollector(data);
+ }
+
+ public abstract Annotations asAnnotations();
+ public abstract AnnotationMap asAnnotationMap();
+
+ public Object getData() {
+ return _data;
+ }
+
+ /*
+ /**********************************************************
+ /* API
+ /**********************************************************
+ */
+
+ public abstract boolean isPresent(Annotation ann);
+
+ public abstract AnnotationCollector addOrOverride(Annotation ann);
+
+ /*
+ /**********************************************************
+ /* Collector implementations
+ /**********************************************************
+ */
+
+ static class EmptyCollector extends AnnotationCollector
+ {
+ public final static EmptyCollector instance = new EmptyCollector(null);
+
+ EmptyCollector(Object data) { super(data); }
+
+ @Override
+ public Annotations asAnnotations() {
+ return NO_ANNOTATIONS;
+ }
+
+ @Override
+ public AnnotationMap asAnnotationMap() {
+ return new AnnotationMap();
+ }
+
+ @Override
+ public boolean isPresent(Annotation ann) { return false; }
+
+ @Override
+ public AnnotationCollector addOrOverride(Annotation ann) {
+ return new OneCollector(_data, ann.annotationType(), ann);
+ }
+ }
+
+ static class OneCollector extends AnnotationCollector
+ {
+ private Class<?> _type;
+ private Annotation _value;
+
+ public OneCollector(Object data,
+ Class<?> type, Annotation value) {
+ super(data);
+ _type = type;
+ _value = value;
+ }
+
+ @Override
+ public Annotations asAnnotations() {
+ return new OneAnnotation(_type, _value);
+ }
+
+ @Override
+ public AnnotationMap asAnnotationMap() {
+ return AnnotationMap.of(_type, _value);
+ }
+
+ @Override
+ public boolean isPresent(Annotation ann) {
+ return ann.annotationType() == _type;
+ }
+
+ @Override
+ public AnnotationCollector addOrOverride(Annotation ann) {
+ final Class<?> type = ann.annotationType();
+ // true override? Just replace in-place, return
+ if (_type == type) {
+ _value = ann;
+ return this;
+ }
+ return new NCollector(_data, _type, _value, type, ann);
+ }
+ }
+
+ static class NCollector extends AnnotationCollector
+ {
+ protected final HashMap<Class<?>,Annotation> _annotations;
+
+ public NCollector(Object data,
+ Class<?> type1, Annotation value1,
+ Class<?> type2, Annotation value2) {
+ super(data);
+ _annotations = new HashMap<>();
+ _annotations.put(type1, value1);
+ _annotations.put(type2, value2);
+ }
+
+ @Override
+ public Annotations asAnnotations() {
+ if (_annotations.size() == 2) {
+ Iterator<Map.Entry<Class<?>,Annotation>> it = _annotations.entrySet().iterator();
+ Map.Entry<Class<?>,Annotation> en1 = it.next(), en2 = it.next();
+ return new TwoAnnotations(en1.getKey(), en1.getValue(),
+ en2.getKey(), en2.getValue());
+ }
+ return new AnnotationMap(_annotations);
+ }
+
+ @Override
+ public AnnotationMap asAnnotationMap() {
+ AnnotationMap result = new AnnotationMap();
+ for (Annotation ann : _annotations.values()) {
+ result.add(ann);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isPresent(Annotation ann) {
+ return _annotations.containsKey(ann.annotationType());
+ }
+
+ @Override
+ public AnnotationCollector addOrOverride(Annotation ann) {
+ _annotations.put(ann.annotationType(), ann);
+ return this;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Annotations implementations
+ /**********************************************************
+ */
+
+ /**
+ * Immutable implementation for case where no annotations are associated with
+ * an annotatable entity.
+ *
+ * @since 2.9
+ */
+ public static class NoAnnotations
+ implements Annotations, java.io.Serializable
+ {
+ private static final long serialVersionUID = 1L;
+
+ NoAnnotations() { }
+
+ @Override
+ public <A extends Annotation> A get(Class<A> cls) {
+ return null;
+ }
+
+ @Override
+ public boolean has(Class<?> cls) {
+ return false;
+ }
+
+ @Override
+ public boolean hasOneOf(Class<? extends Annotation>[] annoClasses) {
+ return false;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+ }
+
+ public static class OneAnnotation
+ implements Annotations, java.io.Serializable
+ {
+ private static final long serialVersionUID = 1L;
+
+ private final Class<?> _type;
+ private final Annotation _value;
+
+ public OneAnnotation(Class<?> type, Annotation value) {
+ _type = type;
+ _value = value;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A extends Annotation> A get(Class<A> cls) {
+ if (_type == cls) {
+ return (A) _value;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean has(Class<?> cls) {
+ return (_type == cls);
+ }
+
+ @Override
+ public boolean hasOneOf(Class<? extends Annotation>[] annoClasses) {
+ for (Class<?> cls : annoClasses) {
+ if (cls == _type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int size() {
+ return 1;
+ }
+ }
+
+ public static class TwoAnnotations
+ implements Annotations, java.io.Serializable
+ {
+ private static final long serialVersionUID = 1L;
+
+ private final Class<?> _type1, _type2;
+ private final Annotation _value1, _value2;
+
+ public TwoAnnotations(Class<?> type1, Annotation value1,
+ Class<?> type2, Annotation value2) {
+ _type1 = type1;
+ _value1 = value1;
+ _type2 = type2;
+ _value2 = value2;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A extends Annotation> A get(Class<A> cls) {
+ if (_type1 == cls) {
+ return (A) _value1;
+ }
+ if (_type2 == cls) {
+ return (A) _value2;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean has(Class<?> cls) {
+ return (_type1 == cls) || (_type2 == cls);
+ }
+
+ @Override
+ public boolean hasOneOf(Class<? extends Annotation>[] annoClasses) {
+ for (Class<?> cls : annoClasses) {
+ if ((cls == _type1) || (cls == _type2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int size() {
+ return 2;
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java
index 97e5edc..aa288b9 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java
@@ -5,11 +5,13 @@
import java.util.Collection;
import java.util.List;
+import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
@@ -119,43 +121,9 @@
{
JsonIgnoreProperties.Value v2 = _secondary.findPropertyIgnorals(a);
JsonIgnoreProperties.Value v1 = _primary.findPropertyIgnorals(a);
-
- if (v2 == null) { // shouldn't occur but
- return v1;
- }
- return v2.withOverrides(v1);
+ return (v2 == null) // shouldn't occur but
+ ? v1 : v2.withOverrides(v1);
}
-
- @Override
- @Deprecated // since 2.6
- public String[] findPropertiesToIgnore(Annotated ac) {
- String[] result = _primary.findPropertiesToIgnore(ac);
- if (result == null) {
- result = _secondary.findPropertiesToIgnore(ac);
- }
- return result;
- }
-
- @Override
- @Deprecated // since 2.8
- public String[] findPropertiesToIgnore(Annotated ac, boolean forSerialization) {
- String[] result = _primary.findPropertiesToIgnore(ac, forSerialization);
- if (result == null) {
- result = _secondary.findPropertiesToIgnore(ac, forSerialization);
- }
- return result;
- }
-
- @Override
- @Deprecated // since 2.8
- public Boolean findIgnoreUnknownProperties(AnnotatedClass ac)
- {
- Boolean result = _primary.findIgnoreUnknownProperties(ac);
- if (result == null) {
- result = _secondary.findIgnoreUnknownProperties(ac);
- }
- return result;
- }
@Override
public Boolean isIgnorableType(AnnotatedClass ac)
@@ -196,6 +164,37 @@
return str;
}
+ @Override
+ @Deprecated // since 2.6
+ public String[] findPropertiesToIgnore(Annotated ac) {
+ String[] result = _primary.findPropertiesToIgnore(ac);
+ if (result == null) {
+ result = _secondary.findPropertiesToIgnore(ac);
+ }
+ return result;
+ }
+
+ @Override
+ @Deprecated // since 2.8
+ public String[] findPropertiesToIgnore(Annotated ac, boolean forSerialization) {
+ String[] result = _primary.findPropertiesToIgnore(ac, forSerialization);
+ if (result == null) {
+ result = _secondary.findPropertiesToIgnore(ac, forSerialization);
+ }
+ return result;
+ }
+
+ @Override
+ @Deprecated // since 2.8
+ public Boolean findIgnoreUnknownProperties(AnnotatedClass ac)
+ {
+ Boolean result = _primary.findIgnoreUnknownProperties(ac);
+ if (result == null) {
+ result = _secondary.findIgnoreUnknownProperties(ac);
+ }
+ return result;
+ }
+
/*
/******************************************************
/* Property auto-detection
@@ -217,8 +216,8 @@
/******************************************************
/* Type handling
/******************************************************
- */
-
+ */
+
@Override
public TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config,
AnnotatedClass ac, JavaType baseType)
@@ -274,8 +273,11 @@
}
return name;
}
-
- // // // General member (field, method/constructor) annotations
+ /*
+ /******************************************************
+ /* General member (field, method/constructor) annotations
+ /******************************************************
+ */
@Override
public ReferenceProperty findReferenceType(AnnotatedMember member) {
@@ -290,50 +292,69 @@
}
@Override
- public Object findInjectableValueId(AnnotatedMember m) {
- Object r = _primary.findInjectableValueId(m);
- return (r == null) ? _secondary.findInjectableValueId(m) : r;
+ public JacksonInject.Value findInjectableValue(AnnotatedMember m) {
+ JacksonInject.Value r = _primary.findInjectableValue(m);
+ return (r == null) ? _secondary.findInjectableValue(m) : r;
}
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return _primary.hasIgnoreMarker(m) || _secondary.hasIgnoreMarker(m);
}
-
+
@Override
public Boolean hasRequiredMarker(AnnotatedMember m) {
Boolean r = _primary.hasRequiredMarker(m);
return (r == null) ? _secondary.hasRequiredMarker(m) : r;
}
-
+
+ @Override
+ @Deprecated // since 2.9
+ public Object findInjectableValueId(AnnotatedMember m) {
+ Object r = _primary.findInjectableValueId(m);
+ return (r == null) ? _secondary.findInjectableValueId(m) : r;
+ }
+
// // // Serialization: general annotations
@Override
public Object findSerializer(Annotated am) {
Object r = _primary.findSerializer(am);
- return _isExplicitClassOrOb(r, JsonSerializer.None.class)
- ? r : _secondary.findSerializer(am);
+ if (_isExplicitClassOrOb(r, JsonSerializer.None.class)) {
+ return r;
+ }
+ return _explicitClassOrOb(_secondary.findSerializer(am),
+ JsonSerializer.None.class);
}
@Override
public Object findKeySerializer(Annotated a) {
Object r = _primary.findKeySerializer(a);
- return _isExplicitClassOrOb(r, JsonSerializer.None.class)
- ? r : _secondary.findKeySerializer(a);
+ if (_isExplicitClassOrOb(r, JsonSerializer.None.class)) {
+ return r;
+ }
+ return _explicitClassOrOb(_secondary.findKeySerializer(a),
+ JsonSerializer.None.class);
}
@Override
public Object findContentSerializer(Annotated a) {
Object r = _primary.findContentSerializer(a);
- return _isExplicitClassOrOb(r, JsonSerializer.None.class)
- ? r : _secondary.findContentSerializer(a);
+ if (_isExplicitClassOrOb(r, JsonSerializer.None.class)) {
+ return r;
+ }
+ return _explicitClassOrOb(_secondary.findContentSerializer(a),
+ JsonSerializer.None.class);
}
@Override
public Object findNullSerializer(Annotated a) {
Object r = _primary.findNullSerializer(a);
- return _isExplicitClassOrOb(r, JsonSerializer.None.class)
- ? r : _secondary.findNullSerializer(a);
+ if (_isExplicitClassOrOb(r, JsonSerializer.None.class)) {
+ return r;
+ }
+ return _explicitClassOrOb(_secondary.findNullSerializer(a),
+ JsonSerializer.None.class);
}
@Deprecated
@@ -464,9 +485,15 @@
}
@Override
- public String findImplicitPropertyName(AnnotatedMember param) {
- String r = _primary.findImplicitPropertyName(param);
- return (r == null) ? _secondary.findImplicitPropertyName(param) : r;
+ public String findImplicitPropertyName(AnnotatedMember ann) {
+ String r = _primary.findImplicitPropertyName(ann);
+ return (r == null) ? _secondary.findImplicitPropertyName(ann) : r;
+ }
+
+ @Override
+ public List<PropertyName> findPropertyAliases(Annotated ann) {
+ List<PropertyName> r = _primary.findPropertyAliases(ann);
+ return (r == null) ? _secondary.findPropertyAliases(ann) : r;
}
@Override
@@ -562,18 +589,24 @@
}
return n;
}
-
+
@Override
- public boolean hasAsValueAnnotation(AnnotatedMethod am) {
- return _primary.hasAsValueAnnotation(am) || _secondary.hasAsValueAnnotation(am);
+ public Boolean hasAsValue(Annotated a) {
+ Boolean b = _primary.hasAsValue(a);
+ if (b == null) {
+ b = _secondary.hasAsValue(a);
+ }
+ return b;
}
@Override
- @Deprecated
- public String findEnumValue(Enum<?> value) {
- String r = _primary.findEnumValue(value);
- return (r == null) ? _secondary.findEnumValue(value) : r;
- }
+ public Boolean hasAnyGetter(Annotated a) {
+ Boolean b = _primary.hasAnyGetter(a);
+ if (b == null) {
+ b = _secondary.hasAnyGetter(a);
+ }
+ return b;
+ }
@Override
public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[] names) {
@@ -589,27 +622,56 @@
return (en == null) ? _secondary.findDefaultEnumValue(enumCls) : en;
}
- // // // Deserialization: general annotations
+ @Override
+ @Deprecated // since 2.8
+ public String findEnumValue(Enum<?> value) {
+ String r = _primary.findEnumValue(value);
+ return (r == null) ? _secondary.findEnumValue(value) : r;
+ }
@Override
- public Object findDeserializer(Annotated am) {
- Object r = _primary.findDeserializer(am);
- return _isExplicitClassOrOb(r, JsonDeserializer.None.class)
- ? r : _secondary.findDeserializer(am);
+ @Deprecated // since 2.9
+ public boolean hasAsValueAnnotation(AnnotatedMethod am) {
+ return _primary.hasAsValueAnnotation(am) || _secondary.hasAsValueAnnotation(am);
}
@Override
- public Object findKeyDeserializer(Annotated am) {
- Object r = _primary.findKeyDeserializer(am);
- return _isExplicitClassOrOb(r, KeyDeserializer.None.class)
- ? r : _secondary.findKeyDeserializer(am);
+ @Deprecated // since 2.9
+ public boolean hasAnyGetterAnnotation(AnnotatedMethod am) {
+ return _primary.hasAnyGetterAnnotation(am) || _secondary.hasAnyGetterAnnotation(am);
+ }
+
+ // // // Deserialization: general annotations
+
+ @Override
+ public Object findDeserializer(Annotated a) {
+ Object r = _primary.findDeserializer(a);
+ if (_isExplicitClassOrOb(r, JsonDeserializer.None.class)) {
+ return r;
+ }
+ return _explicitClassOrOb(_secondary.findDeserializer(a),
+ JsonDeserializer.None.class);
+ }
+
+ @Override
+ public Object findKeyDeserializer(Annotated a) {
+ Object r = _primary.findKeyDeserializer(a);
+ if (_isExplicitClassOrOb(r, KeyDeserializer.None.class)) {
+ return r;
+ }
+ return _explicitClassOrOb(_secondary.findKeyDeserializer(a),
+ KeyDeserializer.None.class);
}
@Override
public Object findContentDeserializer(Annotated am) {
Object r = _primary.findContentDeserializer(am);
- return _isExplicitClassOrOb(r, JsonDeserializer.None.class)
- ? r : _secondary.findContentDeserializer(am);
+ if (_isExplicitClassOrOb(r, JsonDeserializer.None.class)) {
+ return r;
+ }
+ return _explicitClassOrOb(_secondary.findContentDeserializer(am),
+ JsonDeserializer.None.class);
+
}
@Override
@@ -675,7 +737,7 @@
JsonPOJOBuilder.Value result = _primary.findPOJOBuilderConfig(ac);
return (result == null) ? _secondary.findPOJOBuilderConfig(ac) : result;
}
-
+
// // // Deserialization: method annotations
@Override
@@ -693,23 +755,41 @@
}
return n;
}
-
+
@Override
- public boolean hasAnySetterAnnotation(AnnotatedMethod am) {
- return _primary.hasAnySetterAnnotation(am) || _secondary.hasAnySetterAnnotation(am);
+ public Boolean hasAnySetter(Annotated a) {
+ Boolean b = _primary.hasAnySetter(a);
+ if (b == null) {
+ b = _secondary.hasAnySetter(a);
+ }
+ return b;
}
@Override
- public boolean hasAnyGetterAnnotation(AnnotatedMethod am) {
- return _primary.hasAnyGetterAnnotation(am) || _secondary.hasAnyGetterAnnotation(am);
+ public JsonSetter.Value findSetterInfo(Annotated a) {
+ JsonSetter.Value v2 = _secondary.findSetterInfo(a);
+ JsonSetter.Value v1 = _primary.findSetterInfo(a);
+ return (v2 == null) // shouldn't occur but
+ ? v1 : v2.withOverrides(v1);
}
-
+
+ @Override // since 2.9
+ public Boolean findMergeInfo(Annotated a) {
+ Boolean b = _primary.findMergeInfo(a);
+ if (b == null) {
+ b = _secondary.findMergeInfo(a);
+ }
+ return b;
+ }
+
@Override
+ @Deprecated // since 2.9
public boolean hasCreatorAnnotation(Annotated a) {
return _primary.hasCreatorAnnotation(a) || _secondary.hasCreatorAnnotation(a);
}
@Override
+ @Deprecated // since 2.9
public JsonCreator.Mode findCreatorBinding(Annotated a) {
JsonCreator.Mode mode = _primary.findCreatorBinding(a);
if (mode != null) {
@@ -717,15 +797,37 @@
}
return _secondary.findCreatorBinding(a);
}
-
+
+ @Override
+ public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
+ JsonCreator.Mode mode = _primary.findCreatorAnnotation(config, a);
+ return (mode == null) ? _secondary.findCreatorAnnotation(config, a) : mode;
+ }
+
+ @Override
+ @Deprecated // since 2.9
+ public boolean hasAnySetterAnnotation(AnnotatedMethod am) {
+ return _primary.hasAnySetterAnnotation(am) || _secondary.hasAnySetterAnnotation(am);
+ }
+
protected boolean _isExplicitClassOrOb(Object maybeCls, Class<?> implicit) {
- if (maybeCls == null) {
+ if ((maybeCls == null) || (maybeCls == implicit)) {
return false;
}
- if (!(maybeCls instanceof Class<?>)) {
- return true;
+ if (maybeCls instanceof Class<?>) {
+ return !ClassUtil.isBogusClass((Class<?>) maybeCls);
}
- Class<?> cls = (Class<?>) maybeCls;
- return (cls != implicit && !ClassUtil.isBogusClass(cls));
+ return true;
+ }
+
+ // @since 2.9
+ protected Object _explicitClassOrOb(Object maybeCls, Class<?> implicit) {
+ if ((maybeCls == null) || (maybeCls == implicit)) {
+ return null;
+ }
+ if ((maybeCls instanceof Class<?>) && ClassUtil.isBogusClass((Class<?>) maybeCls)) {
+ return null;
+ }
+ return maybeCls;
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationMap.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationMap.java
index 26b31cd..8ae39b6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationMap.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationMap.java
@@ -16,11 +16,23 @@
protected HashMap<Class<?>,Annotation> _annotations;
public AnnotationMap() { }
-
- private AnnotationMap(HashMap<Class<?>,Annotation> a) {
+
+ public static AnnotationMap of(Class<?> type, Annotation value) {
+ HashMap<Class<?>,Annotation> ann = new HashMap<>(4);
+ ann.put(type, value);
+ return new AnnotationMap(ann);
+ }
+
+ AnnotationMap(HashMap<Class<?>,Annotation> a) {
_annotations = a;
}
+ /*
+ /**********************************************************
+ /* Annotations impl
+ /**********************************************************
+ */
+
@SuppressWarnings("unchecked")
@Override
public <A extends Annotation> A get(Class<A> cls)
@@ -31,6 +43,7 @@
return (A) _annotations.get(cls);
}
+ @Override
public boolean has(Class<?> cls)
{
if (_annotations == null) {
@@ -45,6 +58,7 @@
*
* @since 2.7
*/
+ @Override
public boolean hasOneOf(Class<? extends Annotation>[] annoClasses) {
if (_annotations != null) {
for (int i = 0, end = annoClasses.length; i < end; ++i) {
@@ -56,6 +70,12 @@
return false;
}
+ /*
+ /**********************************************************
+ /* Other API
+ /**********************************************************
+ */
+
/**
* @since 2.3
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java
index fd2f0d4..139f73a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java
@@ -4,6 +4,7 @@
import java.lang.reflect.Method;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -26,6 +27,9 @@
*/
public class BasicBeanDescription extends BeanDescription
{
+ // since 2.9
+ private final static Class<?>[] NO_VIEWS = new Class<?>[0];
+
/*
/**********************************************************
/* General configuration
@@ -42,12 +46,28 @@
final protected MapperConfig<?> _config;
final protected AnnotationIntrospector _annotationIntrospector;
+
+ /*
+ /**********************************************************
+ /* Information about type itself
+ /**********************************************************
+ */
/**
* Information collected about the class introspected.
*/
final protected AnnotatedClass _classInfo;
+ /**
+ * @since 2.9
+ */
+ protected Class<?>[] _defaultViews;
+
+ /**
+ * @since 2.9
+ */
+ protected boolean _defaultViewsResolved;
+
/*
/**********************************************************
/* Member information
@@ -220,11 +240,18 @@
}
@Override
+ @Deprecated // since 2.9
public AnnotatedMethod findJsonValueMethod() {
return (_propCollector == null) ? null
: _propCollector.getJsonValueMethod();
}
+ @Override // since 2.9
+ public AnnotatedMember findJsonValueAccessor() {
+ return (_propCollector == null) ? null
+ : _propCollector.getJsonValueAccessor();
+ }
+
@Override
public Set<String> getIgnoredPropertyNames() {
Set<String> ign = (_propCollector == null) ? null
@@ -266,25 +293,39 @@
}
@Override
- public AnnotatedMethod findAnySetter() throws IllegalArgumentException
+ public AnnotatedMember findAnySetterAccessor() throws IllegalArgumentException
{
- AnnotatedMethod anySetter = (_propCollector == null) ? null
- : _propCollector.getAnySetterMethod();
- if (anySetter != null) {
- /* Also, let's be somewhat strict on how field name is to be
- * passed; String, Object make sense, others not
- * so much.
- */
- /* !!! 18-May-2009, tatu: how about enums? Can add support if
- * requested; easy enough for devs to add support within
- * method.
- */
- Class<?> type = anySetter.getRawParameterType(0);
- if (type != String.class && type != Object.class) {
- throw new IllegalArgumentException("Invalid 'any-setter' annotation on method "+anySetter.getName()+"(): first argument not of type String or Object, but "+type.getName());
+ if (_propCollector != null) {
+ AnnotatedMethod anyMethod = _propCollector.getAnySetterMethod();
+ if (anyMethod != null) {
+ // Also, let's be somewhat strict on how field name is to be
+ // passed; String, Object make sense, others not so much.
+
+ /* !!! 18-May-2009, tatu: how about enums? Can add support if
+ * requested; easy enough for devs to add support within method.
+ */
+ Class<?> type = anyMethod.getRawParameterType(0);
+ if ((type != String.class) && (type != Object.class)) {
+ throw new IllegalArgumentException(String.format(
+"Invalid 'any-setter' annotation on method '%s()': first argument not of type String or Object, but %s",
+anyMethod.getName(), type.getName()));
+ }
+ return anyMethod;
+ }
+ AnnotatedMember anyField = _propCollector.getAnySetterField();
+ if (anyField != null) {
+ // For now let's require a Map; in future can add support for other
+ // types like perhaps Iterable<Map.Entry>?
+ Class<?> type = anyField.getRawType();
+ if (!Map.class.isAssignableFrom(type)) {
+ throw new IllegalArgumentException(String.format(
+"Invalid 'any-setter' annotation on field '%s': type is not instance of java.util.Map",
+anyField.getName()));
+ }
+ return anyField;
}
}
- return anySetter;
+ return null;
}
@Override
@@ -316,9 +357,11 @@
while (t.getCause() != null) {
t = t.getCause();
}
- if (t instanceof Error) throw (Error) t;
- if (t instanceof RuntimeException) throw (RuntimeException) t;
- throw new IllegalArgumentException("Failed to instantiate bean of type "+_classInfo.getAnnotated().getName()+": ("+t.getClass().getName()+") "+t.getMessage(), t);
+ ClassUtil.throwIfError(t);
+ ClassUtil.throwIfRTE(t);
+ throw new IllegalArgumentException("Failed to instantiate bean of type "
+ +_classInfo.getAnnotated().getName()+": ("+t.getClass().getName()+") "
+ +ClassUtil.exceptionMessage(t), t);
}
}
@@ -364,7 +407,25 @@
}
return defValue;
}
-
+
+ @Override // since 2.9
+ public Class<?>[] findDefaultViews()
+ {
+ if (!_defaultViewsResolved) {
+ _defaultViewsResolved = true;
+ Class<?>[] def = (_annotationIntrospector == null) ? null
+ : _annotationIntrospector.findViews(_classInfo);
+ // one more twist: if default inclusion disabled, need to force empty set of views
+ if (def == null) {
+ if (!_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
+ def = NO_VIEWS;
+ }
+ }
+ _defaultViews = def;
+ }
+ return _defaultViews;
+ }
+
/*
/**********************************************************
/* Introspection for serialization
@@ -421,54 +482,41 @@
}
@Override
- public AnnotatedMember findAnySetterField() throws IllegalArgumentException {
- AnnotatedMember anySetter = (_propCollector == null) ? null : _propCollector.getAnySetterField();
- if (anySetter != null) {
- /*
- * For now let's require a Map; in future can add support for other
- * types like perhaps Iterable<Map.Entry>?
- */
- Class<?> type = anySetter.getRawType();
- if (!Map.class.isAssignableFrom(type)) {
- throw new IllegalArgumentException("Invalid 'any-setter' annotation on field " + anySetter.getName()
- + "(): type is not instance of java.util.Map");
- }
- }
- return anySetter;
- }
-
- @Override
- public Map<String,AnnotatedMember> findBackReferenceProperties()
+ public List<BeanPropertyDefinition> findBackReferences()
{
- HashMap<String,AnnotatedMember> result = null;
-// boolean hasIgnored = (_ignoredPropertyNames != null);
-
+ List<BeanPropertyDefinition> result = null;
+ HashSet<String> names = null;
for (BeanPropertyDefinition property : _properties()) {
- /* 23-Sep-2014, tatu: As per [databind#426], we _should_ try to avoid
- * calling accessor, as it triggers exception from seeming conflict.
- * But the problem is that _ignoredPropertyNames here only contains
- * ones ignored on per-property annotations, but NOT class annotations...
- * so commented out part does not work, alas
- */
- /*
- if (hasIgnored && _ignoredPropertyNames.contains(property.getName())) {
+ AnnotationIntrospector.ReferenceProperty refDef = property.findReferenceType();
+ if ((refDef == null) || !refDef.isBackReference()) {
continue;
}
- */
- AnnotatedMember am = property.getMutator();
- if (am == null) {
- continue;
- }
- AnnotationIntrospector.ReferenceProperty refDef = _annotationIntrospector.findReferenceType(am);
- if (refDef != null && refDef.isBackReference()) {
- if (result == null) {
- result = new HashMap<String,AnnotatedMember>();
- }
- String refName = refDef.getName();
- if (result.put(refName, am) != null) {
+ final String refName = refDef.getName();
+ if (result == null) {
+ result = new ArrayList<BeanPropertyDefinition>();
+ names = new HashSet<>();
+ names.add(refName);
+ } else {
+ if (!names.add(refName)) {
throw new IllegalArgumentException("Multiple back-reference properties with name '"+refName+"'");
}
}
+ result.add(property);
+ }
+ return result;
+ }
+
+ @Deprecated // since 2.9
+ @Override
+ public Map<String,AnnotatedMember> findBackReferenceProperties()
+ {
+ List<BeanPropertyDefinition> props = findBackReferences();
+ if (props == null) {
+ return null;
+ }
+ Map<String,AnnotatedMember> result = new HashMap<>();
+ for (BeanPropertyDefinition prop : props) {
+ result.put(prop.getName(), prop.getMutator());
}
return result;
}
@@ -483,16 +531,22 @@
public List<AnnotatedMethod> getFactoryMethods()
{
// must filter out anything that clearly is not a factory method
- List<AnnotatedMethod> candidates = _classInfo.getStaticMethods();
+ List<AnnotatedMethod> candidates = _classInfo.getFactoryMethods();
if (candidates.isEmpty()) {
return candidates;
}
- ArrayList<AnnotatedMethod> result = new ArrayList<AnnotatedMethod>();
+ List<AnnotatedMethod> result = null;
for (AnnotatedMethod am : candidates) {
if (isFactoryMethod(am)) {
+ if (result == null) {
+ result = new ArrayList<AnnotatedMethod>();
+ }
result.add(am);
}
}
+ if (result == null) {
+ return Collections.emptyList();
+ }
return result;
}
@@ -520,7 +574,7 @@
public Method findFactoryMethod(Class<?>... expArgTypes)
{
// So, of all single-arg static methods:
- for (AnnotatedMethod am : _classInfo.getStaticMethods()) {
+ for (AnnotatedMethod am : _classInfo.getFactoryMethods()) {
// 24-Oct-2016, tatu: Better ensure it only takes 1 arg, no matter what
if (isFactoryMethod(am) && am.getParameterCount() == 1) {
// And must take one of expected arg types (or supertype)
@@ -538,9 +592,8 @@
protected boolean isFactoryMethod(AnnotatedMethod am)
{
- /* First: return type must be compatible with the introspected class
- * (i.e. allowed to be sub-class, although usually is the same class)
- */
+ // First: return type must be compatible with the introspected class
+ // (i.e. allowed to be sub-class, although usually is the same class)
Class<?> rt = am.getRawReturnType();
if (!getBeanClass().isAssignableFrom(rt)) {
return false;
@@ -549,7 +602,8 @@
* (a) marked with @JsonCreator annotation, or
* (b) "valueOf" (at this point, need not be public)
*/
- if (_annotationIntrospector.hasCreatorAnnotation(am)) {
+ JsonCreator.Mode mode = _annotationIntrospector.findCreatorAnnotation(_config, am);
+ if ((mode != null) && (mode != JsonCreator.Mode.DISABLED)) {
return true;
}
final String name = am.getName();
@@ -666,7 +720,7 @@
*/
@SuppressWarnings("unchecked")
- public Converter<Object,Object> _createConverter(Object converterDef)
+ protected Converter<Object,Object> _createConverter(Object converterDef)
{
if (converterDef == null) {
return null;
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java
index 7d83043..f511efb 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java
@@ -24,29 +24,28 @@
* This is strictly performance optimization to reduce what is
* usually one-time cost, but seems useful for some cases considering
* simplicity.
- *
+ *
* @since 2.4
*/
-
protected final static BasicBeanDescription STRING_DESC;
static {
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(String.class, null);
- STRING_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(String.class), ac);
+ STRING_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(String.class),
+ AnnotatedClassResolver.createPrimordial(String.class));
}
protected final static BasicBeanDescription BOOLEAN_DESC;
static {
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(Boolean.TYPE, null);
- BOOLEAN_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(Boolean.TYPE), ac);
+ BOOLEAN_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(Boolean.TYPE),
+ AnnotatedClassResolver.createPrimordial(Boolean.TYPE));
}
protected final static BasicBeanDescription INT_DESC;
static {
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(Integer.TYPE, null);
- INT_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(Integer.TYPE), ac);
+ INT_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(Integer.TYPE),
+ AnnotatedClassResolver.createPrimordial(Integer.TYPE));
}
protected final static BasicBeanDescription LONG_DESC;
static {
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(Long.TYPE, null);
- LONG_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(Long.TYPE), ac);
+ LONG_DESC = BasicBeanDescription.forOtherUse(null, SimpleType.constructUnsafe(Long.TYPE),
+ AnnotatedClassResolver.createPrimordial(Long.TYPE));
}
/*
@@ -55,9 +54,6 @@
/**********************************************************
*/
- @Deprecated // since 2.5: construct instance directly
- public final static BasicClassIntrospector instance = new BasicClassIntrospector();
-
/**
* Looks like 'forClassAnnotations()' gets called so frequently that we
* should consider caching to avoid some of the lookups.
@@ -70,7 +66,12 @@
// a small cache should go a long way here
_cachedFCA = new LRUMap<JavaType,BasicBeanDescription>(16, 64);
}
-
+
+ @Override
+ public ClassIntrospector copy() {
+ return new BasicClassIntrospector();
+ }
+
/*
/**********************************************************
/* Factory method impls
@@ -84,7 +85,7 @@
// minor optimization: for some JDK types do minimal introspection
BasicBeanDescription desc = _findStdTypeDesc(type);
if (desc == null) {
- // As per [Databind#550], skip full introspection for some of standard
+ // As per [databind#550], skip full introspection for some of standard
// structured types as well
desc = _findStdJdkCollectionDesc(cfg, type);
if (desc == null) {
@@ -157,8 +158,8 @@
if (desc == null) {
desc = _cachedFCA.get(type);
if (desc == null) {
- AnnotatedClass ac = AnnotatedClass.construct(type, config, r);
- desc = BasicBeanDescription.forOtherUse(config, type, ac);
+ desc = BasicBeanDescription.forOtherUse(config, type,
+ _resolveAnnotatedClass(config, type, r));
_cachedFCA.put(type, desc);
}
}
@@ -171,12 +172,12 @@
{
BasicBeanDescription desc = _findStdTypeDesc(type);
if (desc == null) {
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(type.getRawClass(), config, r);
- desc = BasicBeanDescription.forOtherUse(config, type, ac);
+ desc = BasicBeanDescription.forOtherUse(config, type,
+ _resolveAnnotatedWithoutSuperTypes(config, type, r));
}
return desc;
}
-
+
/*
/**********************************************************
/* Overridable helper methods
@@ -187,18 +188,18 @@
JavaType type, MixInResolver r, boolean forSerialization,
String mutatorPrefix)
{
- AnnotatedClass ac = AnnotatedClass.construct(type, config, r);
- return constructPropertyCollector(config, ac, type, forSerialization, mutatorPrefix);
+ return constructPropertyCollector(config,
+ _resolveAnnotatedClass(config, type, r),
+ type, forSerialization, mutatorPrefix);
}
-
+
protected POJOPropertiesCollector collectPropertiesWithBuilder(MapperConfig<?> config,
JavaType type, MixInResolver r, boolean forSerialization)
{
- boolean useAnnotations = config.isAnnotationProcessingEnabled();
- AnnotationIntrospector ai = useAnnotations ? config.getAnnotationIntrospector() : null;
- AnnotatedClass ac = AnnotatedClass.construct(type, config, r);
+ AnnotatedClass ac = _resolveAnnotatedClass(config, type, r);
+ AnnotationIntrospector ai = config.isAnnotationProcessingEnabled() ? config.getAnnotationIntrospector() : null;
JsonPOJOBuilder.Value builderConfig = (ai == null) ? null : ai.findPOJOBuilderConfig(ac);
- String mutatorPrefix = (builderConfig == null) ? "with" : builderConfig.withPrefix;
+ String mutatorPrefix = (builderConfig == null) ? JsonPOJOBuilder.DEFAULT_WITH_PREFIX : builderConfig.withPrefix;
return constructPropertyCollector(config, ac, type, forSerialization, mutatorPrefix);
}
@@ -211,7 +212,7 @@
{
return new POJOPropertiesCollector(config, forSerialization, type, ac, mutatorPrefix);
}
-
+
/**
* Method called to see if type is one of core JDK types
* that we have cached for efficiency.
@@ -267,9 +268,25 @@
protected BasicBeanDescription _findStdJdkCollectionDesc(MapperConfig<?> cfg, JavaType type)
{
if (_isStdJDKCollection(type)) {
- AnnotatedClass ac = AnnotatedClass.construct(type, cfg);
- return BasicBeanDescription.forOtherUse(cfg, type, ac);
+ return BasicBeanDescription.forOtherUse(cfg, type,
+ _resolveAnnotatedClass(cfg, type, cfg));
}
return null;
}
+
+ /**
+ * @since 2.9
+ */
+ protected AnnotatedClass _resolveAnnotatedClass(MapperConfig<?> config,
+ JavaType type, MixInResolver r) {
+ return AnnotatedClassResolver.resolve(config, type, r);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected AnnotatedClass _resolveAnnotatedWithoutSuperTypes(MapperConfig<?> config,
+ JavaType type, MixInResolver r) {
+ return AnnotatedClassResolver.resolveWithoutSuperTypes(config, type, r);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java
index 4039230..caa2663 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BeanPropertyDefinition.java
@@ -48,7 +48,7 @@
/*
/**********************************************************
- /* Basic property information, name, type
+ /* Property name information
/**********************************************************
*/
@@ -83,15 +83,6 @@
public abstract PropertyName getWrapperName();
/**
- * Method for accessing additional metadata.
- * NOTE: will never return null, so de-referencing return value
- * is safe.
- *
- * @since 2.3
- */
- public abstract PropertyMetadata getMetadata();
-
- /**
* Accessor that can be called to check whether property was included
* due to an explicit marker (usually annotation), or just by naming
* convention.
@@ -116,7 +107,42 @@
public boolean isExplicitlyNamed() {
return isExplicitlyIncluded();
}
+
+ /*
+ /**********************************************************
+ /* Basic property metadata
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ public abstract JavaType getPrimaryType();
+
+ /**
+ * @since 2.9
+ */
+ public abstract Class<?> getRawPrimaryType();
+ /**
+ * Method for accessing additional metadata.
+ * NOTE: will never return null, so de-referencing return value
+ * is safe.
+ *
+ * @since 2.3
+ */
+ public abstract PropertyMetadata getMetadata();
+
+ /**
+ * Method used to check if this property is expected to have a value;
+ * and if none found, should either be considered invalid (and most likely
+ * fail deserialization), or handled by other means (by providing default
+ * value)
+ */
+ public boolean isRequired() {
+ return getMetadata().isRequired();
+ }
+
/*
/**********************************************************
/* Capabilities
@@ -157,20 +183,42 @@
* value of the property.
* Null if no such member exists.
*/
- public abstract AnnotatedMember getAccessor();
+ public AnnotatedMember getAccessor()
+ {
+ AnnotatedMember m = getGetter();
+ if (m == null) {
+ m = getField();
+ }
+ return m;
+ }
/**
* Method used to find mutator (constructor parameter, setter, field) to use for
* changing value of the property.
* Null if no such member exists.
*/
- public abstract AnnotatedMember getMutator();
+ public AnnotatedMember getMutator() {
+ AnnotatedMember acc = getConstructorParameter();
+ if (acc == null) {
+ acc = getSetter();
+ if (acc == null) {
+ acc = getField();
+ }
+ }
+ return acc;
+ }
/**
* @since 2.3
*/
- public abstract AnnotatedMember getNonConstructorMutator();
-
+ public AnnotatedMember getNonConstructorMutator() {
+ AnnotatedMember m = getSetter();
+ if (m == null) {
+ m = getField();
+ }
+ return m;
+ }
+
/**
* Method used to find the property member (getter, setter, field) that has
* the highest precedence in current context (getter method when serializing,
@@ -185,12 +233,12 @@
/*
/**********************************************************
/* More refined access to configuration features
- /* (usually based on annotations)
+ /* (usually based on annotations and/or config overrides)
/* Since most trivial implementations do not support
/* these methods, they are implemented as no-ops.
/**********************************************************
*/
-
+
/**
* Method used to find View-inclusion definitions for the property.
*/
@@ -203,6 +251,14 @@
public AnnotationIntrospector.ReferenceProperty findReferenceType() { return null; }
/**
+ * @since 2.9
+ */
+ public String findReferenceName() {
+ AnnotationIntrospector.ReferenceProperty ref = findReferenceType();
+ return (ref == null) ? null : ref.getName();
+ }
+
+ /**
* Method used to check whether this logical property has a marker
* to indicate it should be used as the type id for polymorphic type
* handling.
@@ -215,17 +271,6 @@
* (or, when multiple references exist, all but first AS Object Identifier).
*/
public ObjectIdInfo findObjectIdInfo() { return null; }
-
- /**
- * Method used to check if this property is expected to have a value;
- * and if none found, should either be considered invalid (and most likely
- * fail deserialization), or handled by other means (by providing default
- * value)
- */
- public boolean isRequired() {
- PropertyMetadata md = getMetadata();
- return (md != null) && md.isRequired();
- }
/**
* Method used to check if this property has specific inclusion override
@@ -235,7 +280,5 @@
*
* @since 2.5
*/
- public JsonInclude.Value findInclusion() {
- return EMPTY_INCLUDE;
- }
+ public abstract JsonInclude.Value findInclusion();
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java
index 9bd3f29..6a5dbbf 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java
@@ -46,13 +46,20 @@
}
protected ClassIntrospector() { }
-
+
+ /**
+ * Method that may be needed when `copy()`ing `ObjectMapper` instances.
+ *
+ * @since 2.9.6
+ */
+ public abstract ClassIntrospector copy();
+
/*
/**********************************************************
/* Public API: factory methods
/**********************************************************
*/
-
+
/**
* Factory method that constructs an introspector that has all
* information needed for serialization purposes.
@@ -100,4 +107,3 @@
public abstract BeanDescription forDirectClassAnnotations(MapperConfig<?> cfg, JavaType type,
MixInResolver r);
}
-
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/CollectorBase.java b/src/main/java/com/fasterxml/jackson/databind/introspect/CollectorBase.java
new file mode 100644
index 0000000..e52dcb4
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/CollectorBase.java
@@ -0,0 +1,123 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+
+// @since 2.9
+class CollectorBase
+{
+ protected final static AnnotationMap[] NO_ANNOTATION_MAPS = new AnnotationMap[0];
+ protected final static Annotation[] NO_ANNOTATIONS = new Annotation[0];
+
+ protected final AnnotationIntrospector _intr;
+
+ protected CollectorBase(AnnotationIntrospector intr) {
+ _intr = intr;
+ }
+
+ // // // Annotation overrides ("mix over")
+
+ protected final AnnotationCollector collectAnnotations(Annotation[] anns) {
+ AnnotationCollector c = AnnotationCollector.emptyCollector();
+ for (int i = 0, end = anns.length; i < end; ++i) {
+ Annotation ann = anns[i];
+ c = c.addOrOverride(ann);
+ if (_intr.isAnnotationBundle(ann)) {
+ c = collectFromBundle(c, ann);
+ }
+ }
+ return c;
+ }
+
+ protected final AnnotationCollector collectAnnotations(AnnotationCollector c, Annotation[] anns) {
+ for (int i = 0, end = anns.length; i < end; ++i) {
+ Annotation ann = anns[i];
+ c = c.addOrOverride(ann);
+ if (_intr.isAnnotationBundle(ann)) {
+ c = collectFromBundle(c, ann);
+ }
+ }
+ return c;
+ }
+
+ protected final AnnotationCollector collectFromBundle(AnnotationCollector c, Annotation bundle) {
+ Annotation[] anns = ClassUtil.findClassAnnotations(bundle.annotationType());
+ for (int i = 0, end = anns.length; i < end; ++i) {
+ Annotation ann = anns[i];
+ // minor optimization: by-pass 2 common JDK meta-annotations
+ if (_ignorableAnnotation(ann)) {
+ continue;
+ }
+ if (_intr.isAnnotationBundle(ann)) {
+ // 11-Apr-2017, tatu: Also must guard against recursive definitions...
+ if (!c.isPresent(ann)) {
+ c = c.addOrOverride(ann);
+ c = collectFromBundle(c, ann);
+ }
+ } else {
+ c = c.addOrOverride(ann);
+ }
+ }
+ return c;
+ }
+
+ // // // Defaulting ("mix under")
+
+ // Variant that only adds annotations that are missing
+ protected final AnnotationCollector collectDefaultAnnotations(AnnotationCollector c,
+ Annotation[] anns) {
+ for (int i = 0, end = anns.length; i < end; ++i) {
+ Annotation ann = anns[i];
+ if (!c.isPresent(ann)) {
+ c = c.addOrOverride(ann);
+ if (_intr.isAnnotationBundle(ann)) {
+ c = collectDefaultFromBundle(c, ann);
+ }
+ }
+ }
+ return c;
+ }
+
+ protected final AnnotationCollector collectDefaultFromBundle(AnnotationCollector c,
+ Annotation bundle) {
+ Annotation[] anns = ClassUtil.findClassAnnotations(bundle.annotationType());
+ for (int i = 0, end = anns.length; i < end; ++i) {
+ Annotation ann = anns[i];
+ // minor optimization: by-pass 2 common JDK meta-annotations
+ if (_ignorableAnnotation(ann)) {
+ continue;
+ }
+ // also only defaulting, not overrides:
+ if (!c.isPresent(ann)) {
+ c = c.addOrOverride(ann);
+ if (_intr.isAnnotationBundle(ann)) {
+ c = collectFromBundle(c, ann);
+ }
+ }
+ }
+ return c;
+ }
+
+ protected final static boolean _ignorableAnnotation(Annotation a) {
+ return (a instanceof Target) || (a instanceof Retention);
+ }
+
+ static AnnotationMap _emptyAnnotationMap() {
+ return new AnnotationMap();
+ }
+
+ static AnnotationMap[] _emptyAnnotationMaps(int count) {
+ if (count == 0) {
+ return NO_ANNOTATION_MAPS;
+ }
+ AnnotationMap[] maps = new AnnotationMap[count];
+ for (int i = 0; i < count; ++i) {
+ maps[i] = _emptyAnnotationMap();
+ }
+ return maps;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java b/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java
index 7ea742b..7d2bc48 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java
@@ -1,10 +1,14 @@
package com.fasterxml.jackson.databind.introspect;
+import java.util.Collections;
+import java.util.List;
+
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.PropertyMetadata;
+import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
/**
@@ -24,7 +28,7 @@
* @since 2.3
*/
protected final PropertyMetadata _metadata;
-
+
/**
* Lazily accessed value for per-property format override definition.
*
@@ -32,6 +36,11 @@
*/
protected transient JsonFormat.Value _propertyFormat;
+ /**
+ * @since 2.9
+ */
+ protected transient List<PropertyName> _aliases;
+
protected ConcreteBeanPropertyBase(PropertyMetadata md) {
_metadata = (md == null) ? PropertyMetadata.STD_REQUIRED_OR_OPTIONAL : md;
}
@@ -95,16 +104,37 @@
@Override
public JsonInclude.Value findPropertyInclusion(MapperConfig<?> config, Class<?> baseType)
{
- JsonInclude.Value v0 = config.getDefaultPropertyInclusion(baseType);
AnnotationIntrospector intr = config.getAnnotationIntrospector();
AnnotatedMember member = getMember();
- if ((intr == null) || (member == null)) {
+ if (member == null) {
+ JsonInclude.Value def = config.getDefaultPropertyInclusion(baseType);
+ return def;
+ }
+ JsonInclude.Value v0 = config.getDefaultInclusion(baseType, member.getRawType());
+ if (intr == null) {
return v0;
}
JsonInclude.Value v = intr.findPropertyInclusion(member);
- if (v == null) {
- return v0;
+ if (v0 == null) {
+ return v;
}
return v0.withOverrides(v);
}
+
+ @Override
+ public List<PropertyName> findAliases(MapperConfig<?> config)
+ {
+ List<PropertyName> aliases = _aliases;
+ if (aliases == null) {
+ AnnotationIntrospector intr = config.getAnnotationIntrospector();
+ if (intr != null) {
+ aliases = intr.findPropertyAliases(getMember());
+ }
+ if (aliases == null) {
+ aliases = Collections.emptyList();
+ }
+ _aliases = aliases;
+ }
+ return aliases;
+ }
}
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 8417aa3..823deb6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java
@@ -19,6 +19,8 @@
import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter;
import com.fasterxml.jackson.databind.ser.std.RawSerializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.*;
/**
@@ -53,7 +55,8 @@
JsonTypeInfo.class,
JsonUnwrapped.class,
JsonBackReference.class,
- JsonManagedReference.class
+ JsonManagedReference.class,
+ JsonMerge.class // since 2.9
};
// NOTE: loading of Java7 dependencies is encapsulated by handlers in Java7Support,
@@ -172,11 +175,11 @@
* explicit serialized name
*/
@Override
- @Deprecated
+ @Deprecated // since 2.8
public String findEnumValue(Enum<?> value)
{
// 11-Jun-2015, tatu: As per [databind#677], need to allow explicit naming.
- // Unfortunately can not quite use standard AnnotatedClass here (due to various
+ // Unfortunately cannot quite use standard AnnotatedClass here (due to various
// reasons, including odd representation JVM uses); has to do for now
try {
// We know that values are actually static fields with matching name so:
@@ -270,39 +273,10 @@
{
JsonIgnoreProperties v = _findAnnotation(a, JsonIgnoreProperties.class);
if (v == null) {
- // could alternatively return `Value.empty()`?
- return null;
+ return JsonIgnoreProperties.Value.empty();
}
return JsonIgnoreProperties.Value.from(v);
}
-
- @Override // since 2.6
- @Deprecated // since 2.8
- public String[] findPropertiesToIgnore(Annotated a, boolean forSerialization) {
- JsonIgnoreProperties.Value v = findPropertyIgnorals(a);
- if (v == null) {
- return null;
- }
- // 13-May-2015, tatu: As per [databind#95], allow read-only/write-only props
- if (forSerialization) {
- if (v.getAllowGetters()) {
- return null;
- }
- } else {
- if (v.getAllowSetters()) {
- return null;
- }
- }
- Set<String> ignored = v.getIgnored();
- return ignored.toArray(new String[ignored.size()]);
- }
-
- @Override
- @Deprecated // since 2.8
- public Boolean findIgnoreUnknownProperties(AnnotatedClass a) {
- JsonIgnoreProperties.Value v = findPropertyIgnorals(a);
- return (v == null) ? null : v.getIgnoreUnknown();
- }
@Override
public Boolean isIgnorableType(AnnotatedClass ac) {
@@ -361,7 +335,25 @@
PropertyName n = _findConstructorName(m);
return (n == null) ? null : n.getSimpleName();
}
-
+
+ @Override
+ public List<PropertyName> findPropertyAliases(Annotated m) {
+ JsonAlias ann = _findAnnotation(m, JsonAlias.class);
+ if (ann == null) {
+ return null;
+ }
+ String[] strs = ann.value();
+ final int len = strs.length;
+ if (len == 0) {
+ return Collections.emptyList();
+ }
+ List<PropertyName> result = new ArrayList<>(len);
+ for (int i = 0; i < len; ++i) {
+ result.add(PropertyName.construct(strs[i]));
+ }
+ return result;
+ }
+
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return _isIgnorable(m);
@@ -449,29 +441,37 @@
return NameTransformer.simpleTransformer(prefix, suffix);
}
- @Override
- public Object findInjectableValueId(AnnotatedMember m)
- {
+ @Override // since 2.9
+ public JacksonInject.Value findInjectableValue(AnnotatedMember m) {
JacksonInject ann = _findAnnotation(m, JacksonInject.class);
if (ann == null) {
return null;
}
- /* Empty String means that we should use name of declared
- * value class.
- */
- String id = ann.value();
- if (id.length() == 0) {
+ // Empty String means that we should use name of declared value class.
+ JacksonInject.Value v = JacksonInject.Value.from(ann);
+ if (!v.hasId()) {
+ Object id;
// slight complication; for setters, type
if (!(m instanceof AnnotatedMethod)) {
- return m.getRawType().getName();
+ id = m.getRawType().getName();
+ } else {
+ AnnotatedMethod am = (AnnotatedMethod) m;
+ if (am.getParameterCount() == 0) { // getter
+ id = m.getRawType().getName();
+ } else { // setter
+ id = am.getRawParameterType(0).getName();
+ }
}
- AnnotatedMethod am = (AnnotatedMethod) m;
- if (am.getParameterCount() == 0) {
- return m.getRawType().getName();
- }
- return am.getRawParameterType(0).getName();
+ v = v.withId(id);
}
- return id;
+ return v;
+ }
+
+ @Override
+ @Deprecated // since 2.9
+ public Object findInjectableValueId(AnnotatedMember m) {
+ JacksonInject.Value v = findInjectableValue(m);
+ return (v == null) ? null : v.getId();
}
@Override
@@ -678,106 +678,39 @@
}
@Override
- @SuppressWarnings("deprecation")
- public JsonInclude.Include findSerializationInclusion(Annotated a, JsonInclude.Include defValue)
- {
- JsonInclude inc = _findAnnotation(a, JsonInclude.class);
- if (inc != null) {
- JsonInclude.Include v = inc.value();
- if (v != JsonInclude.Include.USE_DEFAULTS) {
- return v;
- }
- }
- JsonSerialize ann = _findAnnotation(a, JsonSerialize.class);
- if (ann != null) {
- JsonSerialize.Inclusion i2 = ann.include();
- switch (i2) {
- case ALWAYS:
- return JsonInclude.Include.ALWAYS;
- case NON_NULL:
- return JsonInclude.Include.NON_NULL;
- case NON_DEFAULT:
- return JsonInclude.Include.NON_DEFAULT;
- case NON_EMPTY:
- return JsonInclude.Include.NON_EMPTY;
- case DEFAULT_INCLUSION: // since 2.3 -- fall through, use default
- break;
- }
- }
- return defValue;
- }
-
- @Override
- @Deprecated
- public JsonInclude.Include findSerializationInclusionForContent(Annotated a, JsonInclude.Include defValue)
- {
- JsonInclude inc = _findAnnotation(a, JsonInclude.class);
- if (inc != null) {
- JsonInclude.Include incl = inc.content();
- if (incl != JsonInclude.Include.USE_DEFAULTS) {
- return incl;
- }
- }
- return defValue;
- }
-
- @Override
- @SuppressWarnings("deprecation")
public JsonInclude.Value findPropertyInclusion(Annotated a)
{
JsonInclude inc = _findAnnotation(a, JsonInclude.class);
- JsonInclude.Include valueIncl = (inc == null) ? JsonInclude.Include.USE_DEFAULTS : inc.value();
- if (valueIncl == JsonInclude.Include.USE_DEFAULTS) {
- JsonSerialize ann = _findAnnotation(a, JsonSerialize.class);
- if (ann != null) {
- JsonSerialize.Inclusion i2 = ann.include();
- switch (i2) {
- case ALWAYS:
- valueIncl = JsonInclude.Include.ALWAYS;
- break;
- case NON_NULL:
- valueIncl = JsonInclude.Include.NON_NULL;
- break;
- case NON_DEFAULT:
- valueIncl = JsonInclude.Include.NON_DEFAULT;
- break;
- case NON_EMPTY:
- valueIncl = JsonInclude.Include.NON_EMPTY;
- break;
- case DEFAULT_INCLUSION:
- default:
- }
+ JsonInclude.Value value = (inc == null) ? JsonInclude.Value.empty() : JsonInclude.Value.from(inc);
+
+ // only consider deprecated variant if we didn't have non-deprecated one:
+ if (value.getValueInclusion() == JsonInclude.Include.USE_DEFAULTS) {
+ value = _refinePropertyInclusion(a, value);
+ }
+ return value;
+ }
+
+ @SuppressWarnings("deprecation")
+ private JsonInclude.Value _refinePropertyInclusion(Annotated a, JsonInclude.Value value) {
+ JsonSerialize ann = _findAnnotation(a, JsonSerialize.class);
+ if (ann != null) {
+ switch (ann.include()) {
+ case ALWAYS:
+ return value.withValueInclusion(JsonInclude.Include.ALWAYS);
+ case NON_NULL:
+ return value.withValueInclusion(JsonInclude.Include.NON_NULL);
+ case NON_DEFAULT:
+ return value.withValueInclusion(JsonInclude.Include.NON_DEFAULT);
+ case NON_EMPTY:
+ return value.withValueInclusion(JsonInclude.Include.NON_EMPTY);
+ case DEFAULT_INCLUSION:
+ default:
}
}
- JsonInclude.Include contentIncl = (inc == null) ? JsonInclude.Include.USE_DEFAULTS : inc.content();
- return JsonInclude.Value.construct(valueIncl, contentIncl);
+ return value;
}
@Override
- @Deprecated
- public Class<?> findSerializationType(Annotated am)
- {
- JsonSerialize ann = _findAnnotation(am, JsonSerialize.class);
- return (ann == null) ? null : _classIfExplicit(ann.as());
- }
-
- @Override
- @Deprecated
- public Class<?> findSerializationKeyType(Annotated am, JavaType baseType)
- {
- JsonSerialize ann = _findAnnotation(am, JsonSerialize.class);
- return (ann == null) ? null : _classIfExplicit(ann.keyAs());
- }
-
- @Override
- @Deprecated
- public Class<?> findSerializationContentType(Annotated am, JavaType baseType)
- {
- JsonSerialize ann = _findAnnotation(am, JsonSerialize.class);
- return (ann == null) ? null : _classIfExplicit(ann.contentAs());
- }
-
- @Override
public JsonSerialize.Typing findSerializationTyping(Annotated a)
{
JsonSerialize ann = _findAnnotation(a, JsonSerialize.class);
@@ -798,6 +731,148 @@
/*
/**********************************************************
+ /* Serialization: type refinements
+ /**********************************************************
+ */
+
+ @Override
+ public JavaType refineSerializationType(final MapperConfig<?> config,
+ final Annotated a, final JavaType baseType) throws JsonMappingException
+ {
+ JavaType type = baseType;
+ final TypeFactory tf = config.getTypeFactory();
+
+ final JsonSerialize jsonSer = _findAnnotation(a, JsonSerialize.class);
+
+ // Ok: start by refining the main type itself; common to all types
+
+ final Class<?> serClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.as());
+ if (serClass != null) {
+ if (type.hasRawClass(serClass)) {
+ // 30-Nov-2015, tatu: As per [databind#1023], need to allow forcing of
+ // static typing this way
+ type = type.withStaticTyping();
+ } else {
+ Class<?> currRaw = type.getRawClass();
+ try {
+ // 11-Oct-2015, tatu: For deser, we call `TypeFactory.constructSpecializedType()`,
+ // may be needed here too in future?
+ if (serClass.isAssignableFrom(currRaw)) { // common case
+ type = tf.constructGeneralizedType(type, serClass);
+ } else if (currRaw.isAssignableFrom(serClass)) { // specialization, ok as well
+ type = tf.constructSpecializedType(type, serClass);
+ } else if (_primitiveAndWrapper(currRaw, serClass)) {
+ // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements
+ type = type.withStaticTyping();
+ } else {
+ throw new JsonMappingException(null,
+ String.format("Cannot refine serialization type %s into %s; types not related",
+ type, serClass.getName()));
+ }
+ } catch (IllegalArgumentException iae) {
+ throw new JsonMappingException(null,
+ String.format("Failed to widen type %s with annotation (value %s), from '%s': %s",
+ type, serClass.getName(), a.getName(), iae.getMessage()),
+ iae);
+ }
+ }
+ }
+ // Then further processing for container types
+
+ // First, key type (for Maps, Map-like types):
+ if (type.isMapLikeType()) {
+ JavaType keyType = type.getKeyType();
+ final Class<?> keyClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.keyAs());
+ if (keyClass != null) {
+ if (keyType.hasRawClass(keyClass)) {
+ keyType = keyType.withStaticTyping();
+ } else {
+ Class<?> currRaw = keyType.getRawClass();
+ try {
+ // 19-May-2016, tatu: As per [databind#1231], [databind#1178] may need to actually
+ // specialize (narrow) type sometimes, even if more commonly opposite
+ // is needed.
+ if (keyClass.isAssignableFrom(currRaw)) { // common case
+ keyType = tf.constructGeneralizedType(keyType, keyClass);
+ } else if (currRaw.isAssignableFrom(keyClass)) { // specialization, ok as well
+ keyType = tf.constructSpecializedType(keyType, keyClass);
+ } else if (_primitiveAndWrapper(currRaw, keyClass)) {
+ // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements
+ keyType = keyType.withStaticTyping();
+ } else {
+ throw new JsonMappingException(null,
+ String.format("Cannot refine serialization key type %s into %s; types not related",
+ keyType, keyClass.getName()));
+ }
+ } catch (IllegalArgumentException iae) {
+ throw new JsonMappingException(null,
+ String.format("Failed to widen key type of %s with concrete-type annotation (value %s), from '%s': %s",
+ type, keyClass.getName(), a.getName(), iae.getMessage()),
+ iae);
+ }
+ }
+ type = ((MapLikeType) type).withKeyType(keyType);
+ }
+ }
+
+ JavaType contentType = type.getContentType();
+ if (contentType != null) { // collection[like], map[like], array, reference
+ // And then value types for all containers:
+ final Class<?> contentClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.contentAs());
+ if (contentClass != null) {
+ if (contentType.hasRawClass(contentClass)) {
+ contentType = contentType.withStaticTyping();
+ } else {
+ // 03-Apr-2016, tatu: As per [databind#1178], may need to actually
+ // specialize (narrow) type sometimes, even if more commonly opposite
+ // is needed.
+ Class<?> currRaw = contentType.getRawClass();
+ try {
+ if (contentClass.isAssignableFrom(currRaw)) { // common case
+ contentType = tf.constructGeneralizedType(contentType, contentClass);
+ } else if (currRaw.isAssignableFrom(contentClass)) { // specialization, ok as well
+ contentType = tf.constructSpecializedType(contentType, contentClass);
+ } else if (_primitiveAndWrapper(currRaw, contentClass)) {
+ // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements
+ contentType = contentType.withStaticTyping();
+ } else {
+ throw new JsonMappingException(null,
+ String.format("Cannot refine serialization content type %s into %s; types not related",
+ contentType, contentClass.getName()));
+ }
+ } catch (IllegalArgumentException iae) { // shouldn't really happen
+ throw new JsonMappingException(null,
+ String.format("Internal error: failed to refine value type of %s with concrete-type annotation (value %s), from '%s': %s",
+ type, contentClass.getName(), a.getName(), iae.getMessage()),
+ iae);
+ }
+ }
+ type = type.withContentType(contentType);
+ }
+ }
+ return type;
+ }
+
+ @Override
+ @Deprecated // since 2.7
+ public Class<?> findSerializationType(Annotated am) {
+ return null;
+ }
+
+ @Override
+ @Deprecated // since 2.7
+ public Class<?> findSerializationKeyType(Annotated am, JavaType baseType) {
+ return null;
+ }
+
+ @Override
+ @Deprecated // since 2.7
+ public Class<?> findSerializationContentType(Annotated am, JavaType baseType) {
+ return null;
+ }
+
+ /*
+ /**********************************************************
/* Serialization: class annotations
/**********************************************************
*/
@@ -815,9 +890,8 @@
private final Boolean _findSortAlpha(Annotated ann) {
JsonPropertyOrder order = _findAnnotation(ann, JsonPropertyOrder.class);
- /* 23-Jun-2015, tatu: as per [databind#840], let's only consider
- * `true` to have any significance.
- */
+ // 23-Jun-2015, tatu: as per [databind#840], let's only consider
+ // `true` to have any significance.
if ((order != null) && order.alphabetic()) {
return Boolean.TRUE;
}
@@ -923,25 +997,57 @@
@Override
public PropertyName findNameForSerialization(Annotated a)
{
+ boolean useDefault = false;
JsonGetter jg = _findAnnotation(a, JsonGetter.class);
if (jg != null) {
- return PropertyName.construct(jg.value());
+ String s = jg.value();
+ // 04-May-2018, tatu: Should allow for "nameless" `@JsonGetter` too
+ if (!s.isEmpty()) {
+ return PropertyName.construct(s);
+ }
+ useDefault = true;
}
JsonProperty pann = _findAnnotation(a, JsonProperty.class);
if (pann != null) {
return PropertyName.construct(pann.value());
}
- if (_hasOneOf(a, ANNOTATIONS_TO_INFER_SER)) {
+ if (useDefault || _hasOneOf(a, ANNOTATIONS_TO_INFER_SER)) {
return PropertyName.USE_DEFAULT;
}
return null;
}
+ @Override // since 2.9
+ public Boolean hasAsValue(Annotated a) {
+ JsonValue ann = _findAnnotation(a, JsonValue.class);
+ if (ann == null) {
+ return null;
+ }
+ return ann.value();
+ }
+
+ @Override // since 2.9
+ public Boolean hasAnyGetter(Annotated a) {
+ JsonAnyGetter ann = _findAnnotation(a, JsonAnyGetter.class);
+ if (ann == null) {
+ return null;
+ }
+ return ann.enabled();
+ }
+
@Override
+ @Deprecated // since 2.9
+ public boolean hasAnyGetterAnnotation(AnnotatedMethod am) {
+ // No dedicated disabling; regular @JsonIgnore used if needs to be ignored (handled separately)
+ return _hasAnnotation(am, JsonAnyGetter.class);
+ }
+
+ @Override
+ @Deprecated // since 2.9
public boolean hasAsValueAnnotation(AnnotatedMethod am) {
JsonValue ann = _findAnnotation(am, JsonValue.class);
// value of 'false' means disabled...
- return (ann != null && ann.value());
+ return (ann != null) && ann.value();
}
/*
@@ -1012,33 +1118,90 @@
*/
@Override
- @Deprecated
- public Class<?> findDeserializationContentType(Annotated am, JavaType baseContentType)
+ public JavaType refineDeserializationType(final MapperConfig<?> config,
+ final Annotated a, final JavaType baseType) throws JsonMappingException
{
- JsonDeserialize ann = _findAnnotation(am, JsonDeserialize.class);
- return (ann == null) ? null : _classIfExplicit(ann.contentAs());
- }
-
- @Deprecated
- @Override
- public Class<?> findDeserializationType(Annotated am, JavaType baseType) {
- JsonDeserialize ann = _findAnnotation(am, JsonDeserialize.class);
- return (ann == null) ? null : _classIfExplicit(ann.as());
+ JavaType type = baseType;
+ final TypeFactory tf = config.getTypeFactory();
+
+ final JsonDeserialize jsonDeser = _findAnnotation(a, JsonDeserialize.class);
+
+ // Ok: start by refining the main type itself; common to all types
+ final Class<?> valueClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.as());
+ if ((valueClass != null) && !type.hasRawClass(valueClass)
+ && !_primitiveAndWrapper(type, valueClass)) {
+ try {
+ type = tf.constructSpecializedType(type, valueClass);
+ } catch (IllegalArgumentException iae) {
+ throw new JsonMappingException(null,
+ String.format("Failed to narrow type %s with annotation (value %s), from '%s': %s",
+ type, valueClass.getName(), a.getName(), iae.getMessage()),
+ iae);
+ }
+ }
+ // Then further processing for container types
+
+ // First, key type (for Maps, Map-like types):
+ if (type.isMapLikeType()) {
+ JavaType keyType = type.getKeyType();
+ final Class<?> keyClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.keyAs());
+ if ((keyClass != null)
+ && !_primitiveAndWrapper(keyType, keyClass)) {
+ try {
+ keyType = tf.constructSpecializedType(keyType, keyClass);
+ type = ((MapLikeType) type).withKeyType(keyType);
+ } catch (IllegalArgumentException iae) {
+ throw new JsonMappingException(null,
+ String.format("Failed to narrow key type of %s with concrete-type annotation (value %s), from '%s': %s",
+ type, keyClass.getName(), a.getName(), iae.getMessage()),
+ iae);
+ }
+ }
+ }
+ JavaType contentType = type.getContentType();
+ if (contentType != null) { // collection[like], map[like], array, reference
+ // And then value types for all containers:
+ final Class<?> contentClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.contentAs());
+ if ((contentClass != null)
+ && !_primitiveAndWrapper(contentType, contentClass)) {
+ try {
+ contentType = tf.constructSpecializedType(contentType, contentClass);
+ type = type.withContentType(contentType);
+ } catch (IllegalArgumentException iae) {
+ throw new JsonMappingException(null,
+ String.format("Failed to narrow value type of %s with concrete-type annotation (value %s), from '%s': %s",
+ type, contentClass.getName(), a.getName(), iae.getMessage()),
+ iae);
+ }
+ }
+ }
+ return type;
}
@Override
- @Deprecated
- public Class<?> findDeserializationKeyType(Annotated am, JavaType baseKeyType) {
- JsonDeserialize ann = _findAnnotation(am, JsonDeserialize.class);
- return (ann == null) ? null : _classIfExplicit(ann.keyAs());
+ @Deprecated // since 2.7
+ public Class<?> findDeserializationContentType(Annotated am, JavaType baseContentType) {
+ return null;
}
-
+
+ @Override
+ @Deprecated // since 2.7
+ public Class<?> findDeserializationType(Annotated am, JavaType baseType) {
+ return null;
+ }
+
+ @Override
+ @Deprecated // since 2.7
+ public Class<?> findDeserializationKeyType(Annotated am, JavaType baseKeyType) {
+ return null;
+ }
+
/*
/**********************************************************
/* Deserialization: Class annotations
/**********************************************************
*/
-
+
@Override
public Object findValueInstantiator(AnnotatedClass ac)
{
@@ -1071,41 +1234,53 @@
public PropertyName findNameForDeserialization(Annotated a)
{
// @JsonSetter has precedence over @JsonProperty, being more specific
- // @JsonDeserialize implies that there is a property, but no name
+
+ boolean useDefault = false;
JsonSetter js = _findAnnotation(a, JsonSetter.class);
if (js != null) {
- return PropertyName.construct(js.value());
+ String s = js.value();
+ // 04-May-2018, tatu: Need to allow for "nameless" `@JsonSetter` too
+ if (s.isEmpty()) {
+ useDefault = true;
+ } else {
+ return PropertyName.construct(s);
+ }
}
JsonProperty pann = _findAnnotation(a, JsonProperty.class);
if (pann != null) {
return PropertyName.construct(pann.value());
}
- if (_hasOneOf(a, ANNOTATIONS_TO_INFER_DESER)) {
+ if (useDefault || _hasOneOf(a, ANNOTATIONS_TO_INFER_DESER)) {
return PropertyName.USE_DEFAULT;
}
return null;
}
@Override
- public boolean hasAnySetterAnnotation(AnnotatedMethod am)
- {
- /* No dedicated disabling; regular @JsonIgnore used
- * if needs to be ignored (and if so, is handled prior
- * to this method getting called)
- */
+ public Boolean hasAnySetter(Annotated a) {
+ JsonAnySetter ann = _findAnnotation(a, JsonAnySetter.class);
+ return (ann == null) ? null : ann.enabled();
+ }
+
+ @Override
+ public JsonSetter.Value findSetterInfo(Annotated a) {
+ return JsonSetter.Value.from(_findAnnotation(a, JsonSetter.class));
+ }
+
+ @Override // since 2.9
+ public Boolean findMergeInfo(Annotated a) {
+ JsonMerge ann = _findAnnotation(a, JsonMerge.class);
+ return (ann == null) ? null : ann.value().asBoolean();
+ }
+
+ @Override
+ @Deprecated // since 2.9
+ public boolean hasAnySetterAnnotation(AnnotatedMethod am) {
return _hasAnnotation(am, JsonAnySetter.class);
}
@Override
- public boolean hasAnyGetterAnnotation(AnnotatedMethod am)
- {
- /* No dedicated disabling; regular @JsonIgnore used
- * if needs to be ignored (handled separately
- */
- return _hasAnnotation(am, JsonAnyGetter.class);
- }
-
- @Override
+ @Deprecated // since 2.9
public boolean hasCreatorAnnotation(Annotated a)
{
/* No dedicated disabling; regular @JsonIgnore used if needs to be
@@ -1131,11 +1306,35 @@
}
@Override
+ @Deprecated // since 2.9
public JsonCreator.Mode findCreatorBinding(Annotated a) {
JsonCreator ann = _findAnnotation(a, JsonCreator.class);
return (ann == null) ? null : ann.mode();
}
+ @Override
+ public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
+ JsonCreator ann = _findAnnotation(a, JsonCreator.class);
+ if (ann != null) {
+ return ann.mode();
+ }
+ if (_cfgConstructorPropertiesImpliesCreator
+ && config.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES)
+ ) {
+ if (a instanceof AnnotatedConstructor) {
+ if (_java7Helper != null) {
+ Boolean b = _java7Helper.hasCreatorAnnotation(a);
+ if ((b != null) && b.booleanValue()) {
+ // 13-Sep-2016, tatu: Judgment call, but I don't think JDK ever implies
+ // use of delegate; assumes as-properties implicitly
+ return JsonCreator.Mode.PROPERTIES;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
/*
/**********************************************************
/* Helper methods
@@ -1252,7 +1451,7 @@
// 08-Dec-2014, tatu: To deprecate `JsonTypeInfo.None` we need to use other placeholder(s);
// and since `java.util.Void` has other purpose (to indicate "deser as null"), we'll instead
// use `JsonTypeInfo.class` itself. But any annotation type will actually do, as they have no
- // valid use (can not instantiate as default)
+ // valid use (cannot instantiate as default)
if (defaultImpl != JsonTypeInfo.None.class && !defaultImpl.isAnnotation()) {
b = b.defaultImpl(defaultImpl);
}
@@ -1276,9 +1475,25 @@
return StdTypeResolverBuilder.noTypeInfoBuilder();
}
- /*
- /**********************************************************
- /* Helper classes
- /**********************************************************
- */
+ private boolean _primitiveAndWrapper(Class<?> baseType, Class<?> refinement)
+ {
+ if (baseType.isPrimitive()) {
+ return baseType == ClassUtil.primitiveType(refinement);
+ }
+ if (refinement.isPrimitive()) {
+ return refinement == ClassUtil.primitiveType(baseType);
+ }
+ return false;
+ }
+
+ private boolean _primitiveAndWrapper(JavaType baseType, Class<?> refinement)
+ {
+ if (baseType.isPrimitive()) {
+ return baseType.hasRawClass(ClassUtil.primitiveType(refinement));
+ }
+ if (refinement.isPrimitive()) {
+ return refinement == ClassUtil.primitiveType(baseType.getRawClass());
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/MemberKey.java b/src/main/java/com/fasterxml/jackson/databind/introspect/MemberKey.java
index d44d885..58563b8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/MemberKey.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/MemberKey.java
@@ -31,14 +31,21 @@
_argTypes = (argTypes == null) ? NO_CLASSES : argTypes;
}
+ public String getName() {
+ return _name;
+ }
+
+ public int argCount() {
+ return _argTypes.length;
+ }
+
@Override
public String toString() {
return _name + "(" + _argTypes.length+"-args)";
}
@Override
- public int hashCode()
- {
+ public int hashCode() {
return _name.hashCode() + _argTypes.length;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/ObjectIdInfo.java b/src/main/java/com/fasterxml/jackson/databind/introspect/ObjectIdInfo.java
index 049ab35..0605830 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/ObjectIdInfo.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/ObjectIdInfo.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.ObjectIdResolver;
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
import com.fasterxml.jackson.databind.PropertyName;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Container object that encapsulates information usually
@@ -30,17 +31,6 @@
this(name, scope, gen, false, resolver);
}
- @Deprecated // since 2.4
- public ObjectIdInfo(PropertyName name, Class<?> scope, Class<? extends ObjectIdGenerator<?>> gen)
- {
- this(name, scope, gen, false);
- }
-
- @Deprecated // since 2.3
- public ObjectIdInfo(String name, Class<?> scope, Class<? extends ObjectIdGenerator<?>> gen) {
- this(new PropertyName(name), scope, gen, false);
- }
-
protected ObjectIdInfo(PropertyName prop, Class<?> scope, Class<? extends ObjectIdGenerator<?>> gen,
boolean alwaysAsId)
{
@@ -81,8 +71,8 @@
@Override
public String toString() {
return "ObjectIdInfo: propName="+_propertyName
- +", scope="+(_scope == null ? "null" : _scope.getName())
- +", generatorType="+(_generator == null ? "null" : _generator.getName())
+ +", scope="+ClassUtil.nameOf(_scope)
+ +", generatorType="+ClassUtil.nameOf(_generator)
+", alwaysAsId="+_alwaysAsId;
}
}
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 fd6a613..4612cbd 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java
@@ -3,9 +3,12 @@
import java.lang.reflect.Modifier;
import java.util.*;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.fasterxml.jackson.annotation.JsonProperty.Access;
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
import com.fasterxml.jackson.databind.*;
+
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.util.BeanUtil;
@@ -54,6 +57,11 @@
protected final AnnotationIntrospector _annotationIntrospector;
/**
+ * @since 2.9
+ */
+ protected final boolean _useAnnotations;
+
+ /**
* Prefix used by auto-detected mutators ("setters"): usually "set",
* but differs for builder objects ("with" by default).
*/
@@ -80,7 +88,7 @@
*/
protected LinkedHashMap<String, POJOPropertyBuilder> _properties;
- protected LinkedList<POJOPropertyBuilder> _creatorProperties ;
+ protected LinkedList<POJOPropertyBuilder> _creatorProperties;
protected LinkedList<AnnotatedMember> _anyGetters;
@@ -90,8 +98,10 @@
/**
* Method(s) marked with 'JsonValue' annotation
+ *<p>
+ * NOTE: before 2.9, was `AnnotatedMethod`; with 2.9 allows fields too
*/
- protected LinkedList<AnnotatedMethod> _jsonValueGetters;
+ protected LinkedList<AnnotatedMember> _jsonValueAccessors;
/**
* Lazily collected list of properties that can be implicitly
@@ -122,14 +132,15 @@
_type = type;
_classDef = classDef;
_mutatorPrefix = (mutatorPrefix == null) ? "set" : mutatorPrefix;
- _annotationIntrospector = config.isAnnotationProcessingEnabled() ?
- _config.getAnnotationIntrospector() : null;
- if (_annotationIntrospector == null) {
- _visibilityChecker = _config.getDefaultVisibilityChecker();
+ if (config.isAnnotationProcessingEnabled()) {
+ _useAnnotations = true;
+ _annotationIntrospector = _config.getAnnotationIntrospector();
} else {
- _visibilityChecker = _annotationIntrospector.findAutoDetectVisibility(classDef,
- _config.getDefaultVisibilityChecker());
+ _useAnnotations = false;
+ _annotationIntrospector = AnnotationIntrospector.nopInstance();
}
+ _visibilityChecker = _config.getDefaultVisibilityChecker(type.getRawClass(),
+ classDef);
}
/*
@@ -166,20 +177,33 @@
}
return _injectables;
}
-
- public AnnotatedMethod getJsonValueMethod()
+
+ @Deprecated // since 2.9
+ public AnnotatedMethod getJsonValueMethod() {
+ AnnotatedMember m = getJsonValueAccessor();
+ if (m instanceof AnnotatedMethod) {
+ return (AnnotatedMethod) m;
+ }
+ return null;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public AnnotatedMember getJsonValueAccessor()
{
if (!_collected) {
collectAll();
}
// If @JsonValue defined, must have a single one
- if (_jsonValueGetters != null) {
- if (_jsonValueGetters.size() > 1) {
- reportProblem("Multiple value properties defined ("+_jsonValueGetters.get(0)+" vs "
- +_jsonValueGetters.get(1)+")");
+ if (_jsonValueAccessors != null) {
+ if (_jsonValueAccessors.size() > 1) {
+ reportProblem("Multiple 'as-value' properties defined (%s vs %s)",
+ _jsonValueAccessors.get(0),
+ _jsonValueAccessors.get(1));
}
// otherwise we won't greatly care
- return _jsonValueGetters.get(0);
+ return _jsonValueAccessors.get(0);
}
return null;
}
@@ -191,14 +215,14 @@
}
if (_anyGetters != null) {
if (_anyGetters.size() > 1) {
- reportProblem("Multiple 'any-getters' defined ("+_anyGetters.get(0)+" vs "
- +_anyGetters.get(1)+")");
+ reportProblem("Multiple 'any-getters' defined (%s vs %s)",
+ _anyGetters.get(0), _anyGetters.get(1));
}
return _anyGetters.getFirst();
}
return null;
}
-
+
public AnnotatedMember getAnySetterField()
{
if (!_collected) {
@@ -206,8 +230,8 @@
}
if (_anySetterField != null) {
if (_anySetterField.size() > 1) {
- reportProblem("Multiple 'any-Setters' defined ("+_anySetters.get(0)+" vs "
- +_anySetterField.get(1)+")");
+ reportProblem("Multiple 'any-setter' fields defined (%s vs %s)",
+ _anySetterField.get(0), _anySetterField.get(1));
}
return _anySetterField.getFirst();
}
@@ -221,8 +245,8 @@
}
if (_anySetters != null) {
if (_anySetters.size() > 1) {
- reportProblem("Multiple 'any-setters' defined ("+_anySetters.get(0)+" vs "
- +_anySetters.get(1)+")");
+ reportProblem("Multiple 'any-setter' methods defined (%s vs %s)",
+ _anySetters.get(0), _anySetters.get(1));
}
return _anySetters.getFirst();
}
@@ -243,9 +267,6 @@
*/
public ObjectIdInfo getObjectIdInfo()
{
- if (_annotationIntrospector == null) {
- return null;
- }
ObjectIdInfo info = _annotationIntrospector.findObjectIdInfo(_classDef);
if (info != null) { // 2.1: may also have different defaults for refs:
info = _annotationIntrospector.findObjectReferenceInfo(_classDef, info);
@@ -256,8 +277,7 @@
/**
* Method for finding Class to use as POJO builder, if any.
*/
- public Class<?> findPOJOBuilderClass()
- {
+ public Class<?> findPOJOBuilderClass() {
return _annotationIntrospector.findPOJOBuilder(_classDef);
}
@@ -276,20 +296,6 @@
*/
/**
- * Method that orchestrates collection activities, and needs to be called
- * after creating the instance.
- *<p>
- * Since 2.6 has become a no-op and actual collection is done more lazily
- * at point where properties are actually needed.
- *
- * @deprecated Since 2.6; no need to call
- */
- @Deprecated
- public POJOPropertiesCollector collect() {
- return this;
- }
-
- /**
* Internal method that will collect actual property information.
*
* @since 2.6
@@ -311,17 +317,19 @@
// Remove ignored properties, first; this MUST precede annotation merging
// since logic relies on knowing exactly which accessor has which annotation
_removeUnwantedProperties(props);
-
- // then merge annotations, to simplify further processing
- for (POJOPropertyBuilder property : props.values()) {
- property.mergeAnnotations(_forSerialization);
- }
// and then remove unneeded accessors (wrt read-only, read-write)
_removeUnwantedAccessor(props);
// Rename remaining properties
_renameProperties(props);
+ // then merge annotations, to simplify further processing
+ // 26-Sep-2017, tatu: Before 2.9.2 was done earlier but that prevented some of
+ // annotations from getting properly merged
+ for (POJOPropertyBuilder property : props.values()) {
+ property.mergeAnnotations(_forSerialization);
+ }
+
// And use custom naming strategy, if applicable...
PropertyNamingStrategy naming = _findNamingStrategy();
if (naming != null) {
@@ -342,7 +350,7 @@
if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) {
_renameWithWrappers(props);
}
-
+
// well, almost last: there's still ordering...
_sortProperties(props);
_properties = props;
@@ -369,16 +377,29 @@
final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
for (AnnotatedField f : _classDef.fields()) {
- String implName = (ai == null) ? null : ai.findImplicitPropertyName(f);
+ String implName = ai.findImplicitPropertyName(f);
+ // @JsonValue?
+ if (Boolean.TRUE.equals(ai.hasAsValue(f))) {
+ if (_jsonValueAccessors == null) {
+ _jsonValueAccessors = new LinkedList<>();
+ }
+ _jsonValueAccessors.add(f);
+ continue;
+ }
+ // @JsonAnySetter?
+ if (Boolean.TRUE.equals(ai.hasAnySetter(f))) {
+ if (_anySetterField == null) {
+ _anySetterField = new LinkedList<AnnotatedMember>();
+ }
+ _anySetterField.add(f);
+ continue;
+ }
if (implName == null) {
implName = f.getName();
}
-
PropertyName pn;
- if (ai == null) {
- pn = null;
- } else if (_forSerialization) {
+ if (_forSerialization) {
/* 18-Aug-2011, tatu: As per existing unit tests, we should only
* use serialization annotation (@JsonSerialize) when serializing
* fields, and similarly for deserialize-only annotations... so
@@ -401,7 +422,7 @@
visible = _visibilityChecker.isFieldVisible(f);
}
// and finally, may also have explicit ignoral
- boolean ignored = (ai != null) && ai.hasIgnoreMarker(f);
+ boolean ignored = ai.hasIgnoreMarker(f);
// 13-May-2015, tatu: Moved from earlier place (AnnotatedClass) in 2.6
if (f.isTransient()) {
@@ -419,17 +440,10 @@
* Also: if 'ignored' is set, need to included until a later point, to
* avoid losing ignoral information.
*/
- if (pruneFinalFields && (pn == null) && !ignored && Modifier.isFinal(f.getModifiers())) {
+ if (pruneFinalFields && (pn == null) && !ignored
+ && Modifier.isFinal(f.getModifiers())) {
continue;
}
-
- //if field has annotation @JsonAnySetter
- if(f.hasAnnotation(JsonAnySetter.class)) {
- if (_anySetterField == null) {
- _anySetterField = new LinkedList<AnnotatedMember>();
- }
- _anySetterField.add(f);
- }
_property(props, implName).addField(f, pn, nameExplicit, visible, ignored);
}
}
@@ -440,7 +454,7 @@
protected void _addCreators(Map<String, POJOPropertyBuilder> props)
{
// can be null if annotation processing is disabled...
- if (_annotationIntrospector == null) {
+ if (!_useAnnotations) {
return;
}
for (AnnotatedConstructor ctor : _classDef.getConstructors()) {
@@ -451,7 +465,7 @@
_addCreatorParam(props, ctor.getParameter(i));
}
}
- for (AnnotatedMethod factory : _classDef.getStaticMethods()) {
+ for (AnnotatedMethod factory : _classDef.getFactoryMethods()) {
if (_creatorProperties == null) {
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
}
@@ -476,14 +490,14 @@
boolean expl = (pn != null && !pn.isEmpty());
if (!expl) {
if (impl.isEmpty()) {
- /* Important: if neither implicit nor explicit name, can not make use
- * of this creator parameter -- may or may not be a problem, verified
- * at a later point.
- */
+ // Important: if neither implicit nor explicit name, cannot make use of
+ // this creator parameter -- may or may not be a problem, verified at a later point.
return;
}
// Also: if this occurs, there MUST be explicit annotation on creator itself
- if (!_annotationIntrospector.hasCreatorAnnotation(param.getOwner())) {
+ JsonCreator.Mode creatorMode = _annotationIntrospector.findCreatorAnnotation(_config,
+ param.getOwner());
+ if ((creatorMode == null) || (creatorMode == JsonCreator.Mode.DISABLED)) {
return;
}
pn = PropertyName.construct(impl);
@@ -501,14 +515,13 @@
prop.addCtor(param, pn, expl, true, false);
_creatorProperties.add(prop);
}
-
+
/**
* Method for collecting basic information on all fields found
*/
protected void _addMethods(Map<String, POJOPropertyBuilder> props)
{
final AnnotationIntrospector ai = _annotationIntrospector;
-
for (AnnotatedMethod m : _classDef.memberMethods()) {
/* For methods, handling differs between getters and setters; and
* we will also only consider entries that either follow the bean
@@ -521,11 +534,13 @@
} else if (argCount == 1) { // setters
_addSetterMethod(props, m, ai);
} else if (argCount == 2) { // any getter?
- if (ai != null && ai.hasAnySetterAnnotation(m)) {
- if (_anySetters == null) {
- _anySetters = new LinkedList<AnnotatedMethod>();
+ if (ai != null) {
+ if (Boolean.TRUE.equals(ai.hasAnySetter(m))) {
+ if (_anySetters == null) {
+ _anySetters = new LinkedList<AnnotatedMethod>();
+ }
+ _anySetters.add(m);
}
- _anySetters.add(m);
}
}
}
@@ -540,31 +555,30 @@
}
// any getter?
- if (ai != null) {
- if (ai.hasAnyGetterAnnotation(m)) {
- if (_anyGetters == null) {
- _anyGetters = new LinkedList<AnnotatedMember>();
- }
- _anyGetters.add(m);
- return;
+ // @JsonAnyGetter?
+ if (Boolean.TRUE.equals(ai.hasAnyGetter(m))) {
+ if (_anyGetters == null) {
+ _anyGetters = new LinkedList<AnnotatedMember>();
}
- // @JsonValue?
- if (ai.hasAsValueAnnotation(m)) {
- if (_jsonValueGetters == null) {
- _jsonValueGetters = new LinkedList<AnnotatedMethod>();
- }
- _jsonValueGetters.add(m);
- return;
+ _anyGetters.add(m);
+ return;
+ }
+ // @JsonValue?
+ if (Boolean.TRUE.equals(ai.hasAsValue(m))) {
+ if (_jsonValueAccessors == null) {
+ _jsonValueAccessors = new LinkedList<>();
}
+ _jsonValueAccessors.add(m);
+ return;
}
String implName; // from naming convention
boolean visible;
- PropertyName pn = (ai == null) ? null : ai.findNameForSerialization(m);
+ PropertyName pn = ai.findNameForSerialization(m);
boolean nameExplicit = (pn != null);
if (!nameExplicit) { // no explicit name; must consider implicit
- implName = (ai == null) ? null : ai.findImplicitPropertyName(m);
+ implName = ai.findImplicitPropertyName(m);
if (implName == null) {
implName = BeanUtil.okNameForRegularGetter(m, m.getName(), _stdBeanNaming);
}
@@ -579,7 +593,7 @@
}
} else { // explicit indication of inclusion, but may be empty
// we still need implicit name to link with other pieces
- implName = (ai == null) ? null : ai.findImplicitPropertyName(m);
+ implName = ai.findImplicitPropertyName(m);
if (implName == null) {
implName = BeanUtil.okNameForGetter(m, _stdBeanNaming);
}
@@ -594,7 +608,7 @@
}
visible = true;
}
- boolean ignore = (ai == null) ? false : ai.hasIgnoreMarker(m);
+ boolean ignore = ai.hasIgnoreMarker(m);
_property(props, implName).addGetter(m, pn, nameExplicit, visible, ignore);
}
@@ -634,43 +648,41 @@
boolean ignore = (ai == null) ? false : ai.hasIgnoreMarker(m);
_property(props, implName).addSetter(m, pn, nameExplicit, visible, ignore);
}
-
+
protected void _addInjectables(Map<String, POJOPropertyBuilder> props)
{
final AnnotationIntrospector ai = _annotationIntrospector;
- if (ai == null) {
- return;
- }
-
- // first fields, then methods
+ // first fields, then methods, to allow overriding
for (AnnotatedField f : _classDef.fields()) {
- _doAddInjectable(ai.findInjectableValueId(f), f);
+ _doAddInjectable(ai.findInjectableValue(f), f);
}
for (AnnotatedMethod m : _classDef.memberMethods()) {
- /* for now, only allow injection of a single arg
- * (to be changed in future)
- */
+ // for now, only allow injection of a single arg (to be changed in future?)
if (m.getParameterCount() != 1) {
continue;
}
- _doAddInjectable(ai.findInjectableValueId(m), m);
+ _doAddInjectable(ai.findInjectableValue(m), m);
}
}
- protected void _doAddInjectable(Object id, AnnotatedMember m)
+ protected void _doAddInjectable(JacksonInject.Value injectable, AnnotatedMember m)
{
- if (id == null) {
+ if (injectable == null) {
return;
}
+ Object id = injectable.getId();
if (_injectables == null) {
_injectables = new LinkedHashMap<Object, AnnotatedMember>();
}
AnnotatedMember prev = _injectables.put(id, m);
if (prev != null) {
- String type = id.getClass().getName();
- throw new IllegalArgumentException("Duplicate injectable value with id '"
- +String.valueOf(id)+"' (of type "+type+")");
+ // 12-Apr-2017, tatu: Let's allow masking of Field by Method
+ if (prev.getClass() == m.getClass()) {
+ String type = id.getClass().getName();
+ throw new IllegalArgumentException("Duplicate injectable value with id '"
+ +String.valueOf(id)+"' (of type "+type+")");
+ }
}
}
@@ -709,7 +721,7 @@
}
// otherwise just remove ones marked to be ignored
prop.removeIgnored();
- if (!_forSerialization && !prop.couldDeserialize()) {
+ if (!prop.couldDeserialize()) {
_collectIgnorals(prop.getName());
}
}
@@ -729,8 +741,8 @@
while (it.hasNext()) {
POJOPropertyBuilder prop = it.next();
// 26-Jan-2017, tatu: [databind#935]: need to denote removal of
- Access acc = prop.removeNonVisible(inferMutators);
- if (!_forSerialization && (acc == Access.READ_ONLY)) {
+ JsonProperty.Access acc = prop.removeNonVisible(inferMutators);
+ if (acc == JsonProperty.Access.READ_ONLY) {
_collectIgnorals(prop.getName());
}
}
@@ -810,6 +822,12 @@
}
// 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);
+ }
}
}
}
@@ -847,21 +865,20 @@
}
}
final String simpleName;
- if (rename != null && !fullName.hasSimpleName(rename)) {
+ if ((rename != null) && !fullName.hasSimpleName(rename)) {
prop = prop.withSimpleName(rename);
simpleName = rename;
} else {
simpleName = fullName.getSimpleName();
}
- /* As per [JACKSON-687], need to consider case where there may already be
- * something in there...
- */
+ // Need to consider case where there may already be something in there...
POJOPropertyBuilder old = propMap.get(simpleName);
if (old == null) {
propMap.put(simpleName, prop);
} else {
old.addAll(prop);
}
+
// replace the creatorProperty too, if there is one
_updateCreatorProperty(prop, _creatorProperties);
}
@@ -869,9 +886,8 @@
protected void _renameWithWrappers(Map<String, POJOPropertyBuilder> props)
{
- /* 11-Sep-2012, tatu: To support 'MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME',
- * need another round of renaming...
- */
+ // 11-Sep-2012, tatu: To support 'MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME',
+ // need another round of renaming...
Iterator<Map.Entry<String,POJOPropertyBuilder>> it = props.entrySet().iterator();
LinkedList<POJOPropertyBuilder> renamed = null;
while (it.hasNext()) {
@@ -882,7 +898,7 @@
continue;
}
PropertyName wrapperName = _annotationIntrospector.findWrapperName(member);
- // One trickier part (wrt [Issue#24] of JAXB annotations: wrapper that
+ // One trickier part (wrt [#24] of JAXB annotations: wrapper that
// indicates use of actual property... But hopefully has been taken care
// of previously
if (wrapperName == null || !wrapperName.hasSimpleName()) {
@@ -924,7 +940,7 @@
{
// Then how about explicit ordering?
AnnotationIntrospector intr = _annotationIntrospector;
- Boolean alpha = (intr == null) ? null : intr.findSerializationSortAlphabetically((Annotated) _classDef);
+ Boolean alpha = intr.findSerializationSortAlphabetically((Annotated) _classDef);
boolean sort;
if (alpha == null) {
@@ -932,7 +948,7 @@
} else {
sort = alpha.booleanValue();
}
- String[] propertyOrder = (intr == null) ? null : intr.findSerializationPropertyOrder(_classDef);
+ String[] propertyOrder = intr.findSerializationPropertyOrder(_classDef);
// no sorting? no need to shuffle, then
if (!sort && (_creatorProperties == null) && (propertyOrder == null)) {
@@ -999,7 +1015,6 @@
}
// And finally whatever is left (trying to put again will not change ordering)
ordered.putAll(all);
-
props.clear();
props.putAll(ordered);
}
@@ -1010,13 +1025,23 @@
/**********************************************************
*/
- protected void reportProblem(String msg) {
+ protected void reportProblem(String msg, Object... args) {
+ if (args.length > 0) {
+ msg = String.format(msg, args);
+ }
throw new IllegalArgumentException("Problem with definition of "+_classDef+": "+msg);
}
protected POJOPropertyBuilder _property(Map<String, POJOPropertyBuilder> props,
PropertyName name) {
- return _property(props, name.getSimpleName());
+ String simpleName = name.getSimpleName();
+ POJOPropertyBuilder prop = props.get(simpleName);
+ if (prop == null) {
+ prop = new POJOPropertyBuilder(_config, _annotationIntrospector,
+ _forSerialization, name);
+ props.put(simpleName, prop);
+ }
+ return prop;
}
// !!! TODO: deprecate, require use of PropertyName
@@ -1034,8 +1059,7 @@
private PropertyNamingStrategy _findNamingStrategy()
{
- Object namingDef = (_annotationIntrospector == null)? null
- : _annotationIntrospector.findNamingStrategy(_classDef);
+ Object namingDef = _annotationIntrospector.findNamingStrategy(_classDef);
if (namingDef == null) {
return _config.getPropertyNamingStrategy();
}
@@ -1071,9 +1095,11 @@
}
protected void _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(prop.getInternalName())) {
+ if (creatorProperties.get(i).getInternalName().equals(intName)) {
creatorProperties.set(i, prop);
break;
}
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 7cee780..dc37e28 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java
@@ -4,8 +4,12 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.ConfigOverride;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
+import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
@@ -17,6 +21,15 @@
implements Comparable<POJOPropertyBuilder>
{
/**
+ * Marker value used to denote that no reference-property information found for
+ * this property
+ *
+ * @since 2.9
+ */
+ private final static AnnotationIntrospector.ReferenceProperty NOT_REFEFERENCE_PROP =
+ AnnotationIntrospector.ReferenceProperty.managed("");
+
+ /**
* Whether property is being composed for serialization
* (true) or deserialization (false)
*/
@@ -47,6 +60,16 @@
protected Linked<AnnotatedMethod> _setters;
+ protected transient PropertyMetadata _metadata;
+
+ /**
+ * Lazily accessed information about this property iff it is a forward or
+ * back reference.
+ *
+ * @since 2.9
+ */
+ protected transient AnnotationIntrospector.ReferenceProperty _referenceInfo;
+
public POJOPropertyBuilder(MapperConfig<?> config, AnnotationIntrospector ai,
boolean forSerialization, PropertyName internalName) {
this(config, ai, forSerialization, internalName, internalName);
@@ -62,7 +85,8 @@
_forSerialization = forSerialization;
}
- public POJOPropertyBuilder(POJOPropertyBuilder src, PropertyName newName)
+ // protected since 2.9 (was public before)
+ protected POJOPropertyBuilder(POJOPropertyBuilder src, PropertyName newName)
{
_config = src._config;
_annotationIntrospector = src._annotationIntrospector;
@@ -74,10 +98,10 @@
_setters = src._setters;
_forSerialization = src._forSerialization;
}
-
+
/*
/**********************************************************
- /* Fluent factory methods
+ /* Mutant factory methods
/**********************************************************
*/
@@ -92,7 +116,7 @@
PropertyName newName = _name.withSimpleName(newSimpleName);
return (newName == _name) ? this : new POJOPropertyBuilder(this, newName);
}
-
+
/*
/**********************************************************
/* Comparable implementation: sort alphabetically, except
@@ -183,7 +207,157 @@
|| _anyExplicitNames(_ctorParameters)
;
}
-
+
+ /*
+ /**********************************************************
+ /* Simple metadata
+ /**********************************************************
+ */
+
+ @Override
+ 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);
+ } else {
+ _metadata = PropertyMetadata.construct(b, desc, idx, def);
+ }
+ if (!_forSerialization) {
+ _metadata = _getSetterInfo(_metadata);
+ }
+ }
+ return _metadata;
+ }
+
+ /**
+ * Helper method that contains logic for accessing and merging all setter
+ * information that we needed, regarding things like possible merging
+ * of property value, and handling of incoming nulls.
+ */
+ protected PropertyMetadata _getSetterInfo(PropertyMetadata metadata)
+ {
+ boolean needMerge = true;
+ Nulls valueNulls = null;
+ Nulls contentNulls = null;
+
+ // Slightly confusing: first, annotations should be accessed via primary member
+ // (mutator); but accessor is needed for actual merge operation. So:
+ AnnotatedMember prim = getPrimaryMember();
+ AnnotatedMember acc = getAccessor();
+
+ if (prim != null) {
+ // Ok, first: does property itself have something to say?
+ if (_annotationIntrospector != null) {
+ if (acc != null) {
+ Boolean b = _annotationIntrospector.findMergeInfo(prim);
+ if (b != null) {
+ needMerge = false;
+ if (b.booleanValue()) {
+ metadata = metadata.withMergeInfo(PropertyMetadata.MergeInfo.createForPropertyOverride(acc));
+ }
+ }
+ }
+ JsonSetter.Value setterInfo = _annotationIntrospector.findSetterInfo(prim);
+ if (setterInfo != null) {
+ valueNulls = setterInfo.nonDefaultValueNulls();
+ contentNulls = setterInfo.nonDefaultContentNulls();
+ }
+ }
+ // If not, config override?
+ // 25-Oct-2016, tatu: Either this, or type of accessor...
+ if (needMerge || (valueNulls == null) || (contentNulls == null)) {
+ Class<?> rawType = getRawPrimaryType();
+ ConfigOverride co = _config.getConfigOverride(rawType);
+ JsonSetter.Value setterInfo = co.getSetterInfo();
+ if (setterInfo != null) {
+ if (valueNulls == null) {
+ valueNulls = setterInfo.nonDefaultValueNulls();
+ }
+ if (contentNulls == null) {
+ contentNulls = setterInfo.nonDefaultContentNulls();
+ }
+ }
+ if (needMerge && (acc != null)) {
+ Boolean b = co.getMergeable();
+ if (b != null) {
+ needMerge = false;
+ if (b.booleanValue()) {
+ metadata = metadata.withMergeInfo(PropertyMetadata.MergeInfo.createForTypeOverride(acc));
+ }
+ }
+ }
+ }
+ }
+ if (needMerge || (valueNulls == null) || (contentNulls == null)) {
+ JsonSetter.Value setterInfo = _config.getDefaultSetterInfo();
+ if (valueNulls == null) {
+ valueNulls = setterInfo.nonDefaultValueNulls();
+ }
+ if (contentNulls == null) {
+ contentNulls = setterInfo.nonDefaultContentNulls();
+ }
+ if (needMerge) {
+ Boolean b = _config.getDefaultMergeable();
+ if (Boolean.TRUE.equals(b) && (acc != null)) {
+ metadata = metadata.withMergeInfo(PropertyMetadata.MergeInfo.createForDefaults(acc));
+ }
+ }
+ }
+ if ((valueNulls != null) || (contentNulls != null)) {
+ metadata = metadata.withNulls(valueNulls, contentNulls);
+ }
+ return metadata;
+ }
+
+ /**
+ * Type determined from the primary member for the property being built,
+ * considering precedence according to whether we are processing serialization
+ * or deserialization.
+ */
+ @Override
+ public JavaType getPrimaryType() {
+ if (_forSerialization) {
+ AnnotatedMember m = getGetter();
+ if (m == null) {
+ m = getField();
+ if (m == null) {
+ // 09-Feb-2017, tatu: Not sure if this or `null` but...
+ return TypeFactory.unknownType();
+ }
+ return m.getType();
+ }
+ return m.getType();
+ }
+ AnnotatedMember m = getConstructorParameter();
+ if (m == null) {
+ m = getSetter();
+ // Important: can't try direct type access for setter; what we need is
+ // type of the first parameter
+ if (m != null) {
+ return ((AnnotatedMethod) m).getParameterType(0);
+ }
+ m = getField();
+ }
+ // for setterless properties, however, can further try getter
+ if (m == null) {
+ m = getGetter();
+ if (m == null) {
+ return TypeFactory.unknownType();
+ }
+ }
+ return m.getType();
+ }
+
+ @Override
+ public Class<?> getRawPrimaryType() {
+ return getPrimaryType().getRawClass();
+ }
+
/*
/**********************************************************
/* BeanPropertyDefinition implementation, accessor access
@@ -388,45 +562,18 @@
}
return new MemberIterator<AnnotatedParameter>(_ctorParameters);
}
-
- @Override
- public AnnotatedMember getAccessor()
- {
- AnnotatedMember m = getGetter();
- if (m == null) {
- m = getField();
- }
- return m;
- }
-
- @Override
- public AnnotatedMember getMutator()
- {
- AnnotatedMember m = getConstructorParameter();
- if (m == null) {
- m = getSetter();
- if (m == null) {
- m = getField();
- }
- }
- return m;
- }
-
- @Override
- public AnnotatedMember getNonConstructorMutator() {
- AnnotatedMember m = getSetter();
- if (m == null) {
- m = getField();
- }
- return m;
- }
@Override
public AnnotatedMember getPrimaryMember() {
if (_forSerialization) {
return getAccessor();
}
- return getMutator();
+ AnnotatedMember m = getMutator();
+ // for setterless properties, however...
+ if (m == null) {
+ m = getAccessor();
+ }
+ return m;
}
protected int _getterPriority(AnnotatedMethod m)
@@ -471,12 +618,23 @@
@Override
public AnnotationIntrospector.ReferenceProperty findReferenceType() {
- return fromMemberAnnotations(new WithMember<AnnotationIntrospector.ReferenceProperty>() {
+ // 30-Mar-2017, tatu: Access lazily but retain information since it needs
+ // to be accessed multiple times during processing.
+ AnnotationIntrospector.ReferenceProperty result = _referenceInfo;
+ if (result != null) {
+ if (result == NOT_REFEFERENCE_PROP) {
+ return null;
+ }
+ return result;
+ }
+ result = fromMemberAnnotations(new WithMember<AnnotationIntrospector.ReferenceProperty>() {
@Override
public AnnotationIntrospector.ReferenceProperty withMember(AnnotatedMember member) {
return _annotationIntrospector.findReferenceType(member);
}
});
+ _referenceInfo = (result == null) ? NOT_REFEFERENCE_PROP : result;
+ return result;
}
@Override
@@ -490,19 +648,6 @@
return (b != null) && b.booleanValue();
}
- @Override
- public PropertyMetadata getMetadata() {
- final Boolean b = _findRequired();
- final String desc = _findDescription();
- final Integer idx = _findIndex();
- final String def = _findDefaultValue();
- if (b == null && idx == null && def == null) {
- return (desc == null) ? PropertyMetadata.STD_REQUIRED_OR_OPTIONAL
- : PropertyMetadata.STD_REQUIRED_OR_OPTIONAL.withDescription(desc);
- }
- return PropertyMetadata.construct(b, desc, idx, def);
- }
-
protected Boolean _findRequired() {
return fromMemberAnnotations(new WithMember<Boolean>() {
@Override
@@ -573,7 +718,7 @@
}
}, JsonProperty.Access.AUTO);
}
-
+
/*
/**********************************************************
/* Data aggregation
@@ -736,7 +881,7 @@
AnnotationMap ann = _getAllAnnotations(nodes[index]);
while (++index < nodes.length) {
if (nodes[index] != null) {
- return AnnotationMap.merge(ann, _mergeAnnotations(index, nodes));
+ return AnnotationMap.merge(ann, _mergeAnnotations(index, nodes));
}
}
return ann;
@@ -1073,7 +1218,7 @@
}
return null;
}
-
+
/*
/**********************************************************
/* Helper classes
@@ -1140,7 +1285,7 @@
if (explName) {
if (this.name == null) { // sanity check to catch internal problems
- throw new IllegalArgumentException("Can not pass true for 'explName' if name is null/empty");
+ throw new IllegalArgumentException("Cannot pass true for 'explName' if name is null/empty");
}
// 03-Apr-2014, tatu: But how about name-space only override?
// Probably should not be explicit? Or, need to merge somehow?
@@ -1228,8 +1373,8 @@
@Override
public String toString() {
- String msg = value.toString()+"[visible="+isVisible+",ignore="+isMarkedIgnored
- +",explicitName="+isNameExplicit+"]";
+ String msg = String.format("%s[visible=%b,ignore=%b,explicitName=%b]",
+ value.toString(), isVisible, isMarkedIgnored, isNameExplicit);
if (next != null) {
msg = msg + ", "+next.toString();
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/VirtualAnnotatedMember.java b/src/main/java/com/fasterxml/jackson/databind/introspect/VirtualAnnotatedMember.java
index 13038f0..d46f04f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/VirtualAnnotatedMember.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/VirtualAnnotatedMember.java
@@ -3,6 +3,7 @@
import java.lang.reflect.*;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Placeholder used by virtual properties as placeholder for
@@ -39,15 +40,6 @@
_name = name;
}
- /**
- * @deprecated Since 2.8
- */
- @Deprecated
- public VirtualAnnotatedMember(TypeResolutionContext typeContext, Class<?> declaringClass,
- String name, Class<?> rawType) {
- this(typeContext, declaringClass, name, typeContext.resolveType(rawType));
- }
-
@Override
public Annotated withAnnotations(AnnotationMap fallback) {
return this;
@@ -92,12 +84,12 @@
@Override
public void setValue(Object pojo, Object value) throws IllegalArgumentException {
- throw new IllegalArgumentException("Can not set virtual property '"+_name+"'");
+ throw new IllegalArgumentException("Cannot set virtual property '"+_name+"'");
}
@Override
public Object getValue(Object pojo) throws IllegalArgumentException {
- throw new IllegalArgumentException("Can not get virtual property '"+_name+"'");
+ throw new IllegalArgumentException("Cannot get virtual property '"+_name+"'");
}
/*
@@ -106,10 +98,6 @@
/**********************************************************
*/
- public String getFullName() {
- return getDeclaringClass().getName() + "#" + getName();
- }
-
public int getAnnotationCount() { return 0; }
@Override
@@ -120,7 +108,9 @@
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
+ if (!ClassUtil.hasClass(o, getClass())) {
+ return false;
+ }
VirtualAnnotatedMember other = (VirtualAnnotatedMember) o;
return (other._declaringClass == _declaringClass)
&& other._name.equals(_name);
@@ -128,6 +118,6 @@
@Override
public String toString() {
- return "[field "+getFullName()+"]";
+ return "[virtual "+getFullName()+"]";
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/VisibilityChecker.java b/src/main/java/com/fasterxml/jackson/databind/introspect/VisibilityChecker.java
index 0e2d0ad..0cb7766 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/VisibilityChecker.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/VisibilityChecker.java
@@ -29,6 +29,16 @@
public T with(JsonAutoDetect ann);
/**
+ * Method that can be used for merging default values from `this`
+ * instance with specified overrides; and either return `this`
+ * if overrides had no effect (that is, result would be equal),
+ * or a new instance with merged visibility settings.
+ *
+ * @since 2.9
+ */
+ public T withOverrides(JsonAutoDetect.Value vis);
+
+ /**
* Builder method that will create and return an instance that has specified
* {@link Visibility} value to use for all property elements.
* Typical usage would be something like:
@@ -140,23 +150,7 @@
* methods. As a result, type is declared is funky recursive generic
* type, to allow for sub-classing of build methods with property type
* co-variance.
- *<p>
- * Note on <code>JsonAutoDetect</code> annotation: it is used to
- * access default minimum visibility access definitions.
*/
- @JsonAutoDetect(
- getterVisibility = Visibility.PUBLIC_ONLY,
- isGetterVisibility = Visibility.PUBLIC_ONLY,
- setterVisibility = Visibility.ANY,
- /**
- * By default, all matching single-arg constructed are found,
- * regardless of visibility. Does not apply to factory methods,
- * they can not be auto-detected; ditto for multiple-argument
- * constructors.
- */
- creatorVisibility = Visibility.ANY,
- fieldVisibility = Visibility.PUBLIC_ONLY
- )
public static class Std
implements VisibilityChecker<Std>,
java.io.Serializable
@@ -167,8 +161,14 @@
* This is the canonical base instance, configured with default
* visibility values
*/
- protected final static Std DEFAULT = new Std(Std.class.getAnnotation(JsonAutoDetect.class));
-
+ protected final static Std DEFAULT = new Std(
+ Visibility.PUBLIC_ONLY, // getter
+ Visibility.PUBLIC_ONLY, // is-getter
+ Visibility.ANY, // setter
+ Visibility.ANY, // creator -- legacy, to support single-arg ctors
+ Visibility.PUBLIC_ONLY // field
+ );
+
protected final Visibility _getterMinLevel;
protected final Visibility _isGetterMinLevel;
protected final Visibility _setterMinLevel;
@@ -185,7 +185,7 @@
*/
public Std(JsonAutoDetect ann)
{
- // let's combine checks for enabled/disabled, with minimimum level checks:
+ // let's combine checks for enabled/disabled, with minimum level checks:
_getterMinLevel = ann.getterVisibility();
_isGetterMinLevel = ann.isGetterVisibility();
_setterMinLevel = ann.setterVisibility();
@@ -196,7 +196,8 @@
/**
* Constructor that allows directly specifying minimum visibility levels to use
*/
- public Std(Visibility getter, Visibility isGetter, Visibility setter, Visibility creator, Visibility field)
+ public Std(Visibility getter, Visibility isGetter, Visibility setter,
+ Visibility creator, Visibility field)
{
_getterMinLevel = getter;
_isGetterMinLevel = isGetter;
@@ -229,6 +230,13 @@
}
}
+ /**
+ * @since 2.9
+ */
+ public static Std construct(JsonAutoDetect.Value vis) {
+ return DEFAULT.withOverrides(vis);
+ }
+
/*
/********************************************************
/* Builder/fluent methods for instantiating configured
@@ -236,20 +244,58 @@
/********************************************************
*/
+ protected Std _with(Visibility g, Visibility isG, Visibility s,
+ Visibility cr, Visibility f) {
+ if ((g == _getterMinLevel)
+ && (isG == _isGetterMinLevel)
+ && (s == _setterMinLevel)
+ && (cr == _creatorMinLevel)
+ && (f == _fieldMinLevel)
+ ) {
+ return this;
+ }
+ return new Std(g, isG, s, cr, f);
+ }
+
@Override
public Std with(JsonAutoDetect ann)
{
Std curr = this;
if (ann != null) {
- curr = curr.withGetterVisibility(ann.getterVisibility());
- curr = curr.withIsGetterVisibility(ann.isGetterVisibility());
- curr = curr.withSetterVisibility(ann.setterVisibility());
- curr = curr.withCreatorVisibility(ann.creatorVisibility());
- curr = curr.withFieldVisibility(ann.fieldVisibility());
+ return _with(
+ _defaultOrOverride(_getterMinLevel, ann.getterVisibility()),
+ _defaultOrOverride(_isGetterMinLevel, ann.isGetterVisibility()),
+ _defaultOrOverride(_setterMinLevel, ann.setterVisibility()),
+ _defaultOrOverride(_creatorMinLevel, ann.creatorVisibility()),
+ _defaultOrOverride(_fieldMinLevel, ann.fieldVisibility())
+ );
}
return curr;
}
+ @Override // since 2.9
+ public Std withOverrides(JsonAutoDetect.Value vis)
+ {
+ Std curr = this;
+ if (vis != null) {
+ return _with(
+ _defaultOrOverride(_getterMinLevel, vis.getGetterVisibility()),
+ _defaultOrOverride(_isGetterMinLevel, vis.getIsGetterVisibility()),
+ _defaultOrOverride(_setterMinLevel, vis.getSetterVisibility()),
+ _defaultOrOverride(_creatorMinLevel, vis.getCreatorVisibility()),
+ _defaultOrOverride(_fieldMinLevel, vis.getFieldVisibility())
+ );
+ }
+ return curr;
+ }
+
+ private Visibility _defaultOrOverride(Visibility defaults, Visibility override) {
+ if (override == Visibility.DEFAULT) {
+ return defaults;
+ }
+ return override;
+ }
+
@Override
public Std with(Visibility v)
{
@@ -258,7 +304,7 @@
}
return new Std(v);
}
-
+
@Override
public Std withVisibility(PropertyAccessor method, Visibility v)
{
@@ -284,39 +330,39 @@
@Override
public Std withGetterVisibility(Visibility v) {
- if (v == Visibility.DEFAULT) v = DEFAULT._getterMinLevel;
+ if (v == Visibility.DEFAULT) v = DEFAULT._getterMinLevel;
if (_getterMinLevel == v) return this;
return new Std(v, _isGetterMinLevel, _setterMinLevel, _creatorMinLevel, _fieldMinLevel);
}
@Override
public Std withIsGetterVisibility(Visibility v) {
- if (v == Visibility.DEFAULT) v = DEFAULT._isGetterMinLevel;
+ if (v == Visibility.DEFAULT) v = DEFAULT._isGetterMinLevel;
if (_isGetterMinLevel == v) return this;
return new Std(_getterMinLevel, v, _setterMinLevel, _creatorMinLevel, _fieldMinLevel);
}
@Override
public Std withSetterVisibility(Visibility v) {
- if (v == Visibility.DEFAULT) v = DEFAULT._setterMinLevel;
+ if (v == Visibility.DEFAULT) v = DEFAULT._setterMinLevel;
if (_setterMinLevel == v) return this;
return new Std(_getterMinLevel, _isGetterMinLevel, v, _creatorMinLevel, _fieldMinLevel);
}
-
+
@Override
public Std withCreatorVisibility(Visibility v) {
- if (v == Visibility.DEFAULT) v = DEFAULT._creatorMinLevel;
+ if (v == Visibility.DEFAULT) v = DEFAULT._creatorMinLevel;
if (_creatorMinLevel == v) return this;
return new Std(_getterMinLevel, _isGetterMinLevel, _setterMinLevel, v, _fieldMinLevel);
}
-
+
@Override
public Std withFieldVisibility(Visibility v) {
if (v == Visibility.DEFAULT) v = DEFAULT._fieldMinLevel;
if (_fieldMinLevel == v) return this;
return new Std(_getterMinLevel, _isGetterMinLevel, _setterMinLevel, _creatorMinLevel, v);
}
-
+
/*
/********************************************************
/* Public API impl
@@ -327,7 +373,7 @@
public boolean isCreatorVisible(Member m) {
return _creatorMinLevel.isVisible(m);
}
-
+
@Override
public boolean isCreatorVisible(AnnotatedMember m) {
return isCreatorVisible(m.getMember());
@@ -337,17 +383,17 @@
public boolean isFieldVisible(Field f) {
return _fieldMinLevel.isVisible(f);
}
-
+
@Override
public boolean isFieldVisible(AnnotatedField f) {
return isFieldVisible(f.getAnnotated());
}
-
+
@Override
public boolean isGetterVisible(Method m) {
return _getterMinLevel.isVisible(m);
}
-
+
@Override
public boolean isGetterVisible(AnnotatedMethod m) {
return isGetterVisible(m.getAnnotated());
@@ -381,13 +427,8 @@
@Override
public String toString() {
- return new StringBuilder("[Visibility:")
- .append(" getter: ").append(_getterMinLevel)
- .append(", isGetter: ").append(_isGetterMinLevel)
- .append(", setter: ").append(_setterMinLevel)
- .append(", creator: ").append(_creatorMinLevel)
- .append(", field: ").append(_fieldMinLevel)
- .append("]").toString();
+ return String.format("[Visibility: getter=%s,isGetter=%s,setter=%s,creator=%s,field=%s]",
+ _getterMinLevel, _isGetterMinLevel, _setterMinLevel, _creatorMinLevel, _fieldMinLevel);
}
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonObjectFormatVisitor.java b/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonObjectFormatVisitor.java
index 02ba141..72dbbe1 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonObjectFormatVisitor.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonObjectFormatVisitor.java
@@ -56,8 +56,7 @@
JavaType propertyTypeHint) throws JsonMappingException { }
@Override
- public void optionalProperty(BeanProperty prop)
- throws JsonMappingException { }
+ public void optionalProperty(BeanProperty prop) throws JsonMappingException { }
@Override
public void optionalProperty(String name, JsonFormatVisitable handler,
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsonschema/JsonSerializableSchema.java b/src/main/java/com/fasterxml/jackson/databind/jsonschema/JsonSerializableSchema.java
index 553206e..b34ce6d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsonschema/JsonSerializableSchema.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsonschema/JsonSerializableSchema.java
@@ -25,7 +25,7 @@
{
/**
* Marker value used to indicate that property has "no value";
- * needed because annotations can not have null as default
+ * needed because annotations cannot have null as default
* value.
*/
public final static String NO_VALUE = "##irrelevant";
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java
index e64b7f2..226668e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java
@@ -43,6 +43,7 @@
@Override
public String toString() {
- return "[NamedType, class "+_class.getName()+", name: "+(_name == null ? "null" :("'"+_name+"'"))+"]";
+ return "[NamedType, class "+_class.getName()+", name: "
+ +(_name == null ? "null" :("'"+_name+"'"))+"]";
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java
index e4953ca..0f6c50b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/SubtypeResolver.java
@@ -29,6 +29,11 @@
public abstract void registerSubtypes(Class<?>... classes);
+ /**
+ * @since 2.9
+ */
+ public abstract void registerSubtypes(Collection<Class<?>> subtypes);
+
/*
/**********************************************************
/* Subtype resolution
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeDeserializer.java
index 37a86e1..b5e265f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeDeserializer.java
@@ -66,7 +66,7 @@
/**
* Accessor for "default implementation" type; optionally defined
* class to use in cases where type id is not
- * accessible for some reason (either missing, or can not be
+ * accessible for some reason (either missing, or cannot be
* resolved)
*/
public abstract Class<?> getDefaultImpl();
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeResolverBuilder.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeResolverBuilder.java
index 0e630b5..7878dba 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeResolverBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeResolverBuilder.java
@@ -135,7 +135,7 @@
/**
* Method for specifying default implementation to use if type id
- * is either not available, or can not be resolved.
+ * is either not available, or cannot be resolved.
*
* @return Resulting builder instance (usually this builder,
* but may be a newly constructed instance for immutable builders}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeSerializer.java
index 09f4be9..f32379f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/TypeSerializer.java
@@ -3,6 +3,8 @@
import java.io.IOException;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
+import com.fasterxml.jackson.core.util.VersionUtil;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@@ -13,6 +15,12 @@
* {@link com.fasterxml.jackson.databind.JsonSerializer}s using proper contextual
* calls, to add type information using mechanism type serializer was
* configured with.
+ *<p>
+ * NOTE: version 2.9 contains significant attempt at simplifying interface,
+ * as well as giving format implementation (via {@link JsonGenerator}) more
+ * control over actual serialization details. Minor changes are required to change
+ * call pattern so that return value of "prefix" write needs to be passed to "suffix"
+ * write.
*/
public abstract class TypeSerializer
{
@@ -51,107 +59,220 @@
* property-based inclusion is used.
*/
public abstract String getPropertyName();
-
+
/**
* Accessor for object that handles conversions between
* types and matching type ids.
*/
public abstract TypeIdResolver getTypeIdResolver();
-
+
/*
/**********************************************************
- /* Type serialization methods
+ /* Type serialization methods: new (2.9)
/**********************************************************
*/
-
+
/**
- * Method called to write initial part of type information for given
- * value, when it will be output as scalar JSON value (not as JSON
- * Object or Array).
- * This means that the context after call can not be that of JSON Object;
- * it may be Array or root context.
- *
- * @param value Value that will be serialized, for which type information is
- * to be written
- * @param g Generator to use for writing type information
+ * Factory method for constructing type id value object to pass to
+ * {@link #writeTypePrefix}.
*/
- public abstract void writeTypePrefixForScalar(Object value, JsonGenerator g) throws IOException;
+ public WritableTypeId typeId(Object value, JsonToken valueShape) {
+ WritableTypeId typeIdDef = new WritableTypeId(value, valueShape);
+ switch (getTypeInclusion()) {
+ case EXISTING_PROPERTY:
+ typeIdDef.include = WritableTypeId.Inclusion.PAYLOAD_PROPERTY;
+ typeIdDef.asProperty = getPropertyName();
+ break;
+ case EXTERNAL_PROPERTY:
+ typeIdDef.include = WritableTypeId.Inclusion.PARENT_PROPERTY;
+ typeIdDef.asProperty = getPropertyName();
+ break;
+ case PROPERTY:
+ typeIdDef.include = WritableTypeId.Inclusion.METADATA_PROPERTY;
+ typeIdDef.asProperty = getPropertyName();
+ break;
+ case WRAPPER_ARRAY:
+ typeIdDef.include = WritableTypeId.Inclusion.WRAPPER_ARRAY;
+ break;
+ case WRAPPER_OBJECT:
+ typeIdDef.include = WritableTypeId.Inclusion.WRAPPER_OBJECT;
+ break;
+ default:
+ VersionUtil.throwInternal();
+ }
+ return typeIdDef;
+ }
+
+ public WritableTypeId typeId(Object value, JsonToken valueShape,
+ Object id) {
+ WritableTypeId typeId = typeId(value, valueShape);
+ typeId.id = id;
+ return typeId;
+ }
+
+ public WritableTypeId typeId(Object value, Class<?> typeForId,
+ JsonToken valueShape) {
+ WritableTypeId typeId = typeId(value, valueShape);
+ typeId.forValueType = typeForId;
+ return typeId;
+ }
/**
* Method called to write initial part of type information for given
- * value, when it will be output as JSON Object value (not as JSON
- * Array or scalar).
- * This means that context after call must be JSON Object, meaning that
- * caller can then proceed to output field entries.
+ * value, along with possible wrapping to use: details are specified
+ * by `typeId` argument.
+ * Note that for structured types (Object, Array), this call will add
+ * necessary start token so it should NOT be explicitly written, unlike
+ * with non-type-id value writes.
+ *<p>
+ * See {@link #writeTypeSuffix(JsonGenerator, WritableTypeId)} for a complete
+ * example of typical usage.
+ *
+ * @param g Generator to use for outputting type id and possible wrapping
+ * @param typeId Details of what type id is to be written, how.
*
- * @param value Value that will be serialized, for which type information is
- * to be written
- * @param g Generator to use for writing type information
+ * @since 2.9
*/
- public abstract void writeTypePrefixForObject(Object value, JsonGenerator g) throws IOException;
+ public abstract WritableTypeId writeTypePrefix(JsonGenerator g,
+ WritableTypeId typeId) throws IOException;
/**
- * Method called to write initial part of type information for given
- * value, when it will be output as JSON Array value (not as JSON
- * Object or scalar).
- * This means that context after call must be JSON Array, that is, there
- * must be an open START_ARRAY to write contents in.
- *
- * @param value Value that will be serialized, for which type information is
- * to be written
- * @param g Generator to use for writing type information
+ * Method that should be called after {@link #writeTypePrefix(JsonGenerator, WritableTypeId)}
+ * and matching value write have been called, passing {@link WritableTypeId} returned.
+ * Usual idiom is:
+ *<pre>
+ * // Indicator generator that type identifier may be needed; generator may write
+ * // one as suggested, modify information, or take some other action
+ * // NOTE! For Object/Array types, this will ALSO write start marker!
+ * WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
+ * typeSer.typeId(value, JsonToken.START_OBJECT));
+ *
+ * // serializing actual value for which TypeId may have been written... like
+ * // NOTE: do NOT write START_OBJECT before OR END_OBJECT after:
+ * g.writeStringField("message", "Hello, world!"
+ *
+ * // matching type suffix call to let generator chance to add suffix, if any
+ * // NOTE! For Object/Array types, this will ALSO write end marker!
+ * typeSer.writeTypeSuffix(gen, typeIdDef);
+ *</pre>
+ *
+ * @since 2.9
*/
- public abstract void writeTypePrefixForArray(Object value, JsonGenerator g) throws IOException;
+ public abstract WritableTypeId writeTypeSuffix(JsonGenerator g,
+ WritableTypeId typeId) throws IOException;
+
+ /*
+ /**********************************************************
+ /* Legacy type serialization methods
+ /**********************************************************
+ */
/**
- * Method called after value has been serialized, to close any scopes opened
- * by earlier matching call to {@link #writeTypePrefixForScalar}.
- * Actual action to take may depend on various factors, but has to match with
- * action {@link #writeTypePrefixForScalar} did (close array or object; or do nothing).
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, JsonToken.VALUE_STRING));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
*/
- public abstract void writeTypeSuffixForScalar(Object value, JsonGenerator g) throws IOException;
+ @Deprecated // since 2.9
+ public void writeTypePrefixForScalar(Object value, JsonGenerator g) throws IOException {
+ writeTypePrefix(g, typeId(value, JsonToken.VALUE_STRING));
+ }
/**
- * Method called after value has been serialized, to close any scopes opened
- * by earlier matching call to {@link #writeTypePrefixForObject}.
- * It needs to write closing END_OBJECT marker, and any other decoration
- * that needs to be matched.
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, JsonToken.START_OBJECT));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
*/
- public abstract void writeTypeSuffixForObject(Object value, JsonGenerator g) throws IOException;
+ @Deprecated // since 2.9
+ public void writeTypePrefixForObject(Object value, JsonGenerator g) throws IOException {
+ writeTypePrefix(g, typeId(value, JsonToken.START_OBJECT));
+ }
/**
- * Method called after value has been serialized, to close any scopes opened
- * by earlier matching call to {@link #writeTypeSuffixForScalar}.
- * It needs to write closing END_ARRAY marker, and any other decoration
- * that needs to be matched.
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, JsonToken.START_ARRAY));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
*/
- public abstract void writeTypeSuffixForArray(Object value, JsonGenerator g) throws IOException;
+ @Deprecated // since 2.9
+ public void writeTypePrefixForArray(Object value, JsonGenerator g) throws IOException {
+ writeTypePrefix(g, typeId(value, JsonToken.START_ARRAY));
+ }
/**
- * Alternative version of the prefix-for-scalar method, which is given
- * actual type to use (instead of using exact type of the value); typically
- * a super type of actual value type
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypeSuffix(g, typeId(value, JsonToken.VALUE_STRING));}.
+ * See {@link #writeTypeSuffix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypeSuffix(JsonGenerator, WritableTypeId)} instead
*/
+ @Deprecated // since 2.9
+ public void writeTypeSuffixForScalar(Object value, JsonGenerator g) throws IOException {
+ _writeLegacySuffix(g, typeId(value, JsonToken.VALUE_STRING));
+ }
+
+ /**
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypeSuffix(g, typeId(value, JsonToken.START_OBJECT));}.
+ * See {@link #writeTypeSuffix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypeSuffix(JsonGenerator, WritableTypeId)} instead
+ */
+ @Deprecated // since 2.9
+ public void writeTypeSuffixForObject(Object value, JsonGenerator g) throws IOException {
+ _writeLegacySuffix(g, typeId(value, JsonToken.START_OBJECT));
+ }
+
+ /**
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypeSuffix(g, typeId(value, JsonToken.START_ARRAY));}.
+ * See {@link #writeTypeSuffix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypeSuffix(JsonGenerator, WritableTypeId)} instead
+ */
+ @Deprecated // since 2.9
+ public void writeTypeSuffixForArray(Object value, JsonGenerator g) throws IOException {
+ _writeLegacySuffix(g, typeId(value, JsonToken.START_ARRAY));
+ }
+
+ /**
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, type, JsonToken.VALUE_STRING));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
+ */
+ @Deprecated // since 2.9
public void writeTypePrefixForScalar(Object value, JsonGenerator g, Class<?> type) throws IOException {
- writeTypePrefixForScalar(value, g);
+ writeTypePrefix(g, typeId(value, type, JsonToken.VALUE_STRING));
}
/**
- * Alternative version of the prefix-for-object method, which is given
- * actual type to use (instead of using exact type of the value); typically
- * a super type of actual value type
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, type, JsonToken.START_OBJECT));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
*/
+ @Deprecated // since 2.9
public void writeTypePrefixForObject(Object value, JsonGenerator g, Class<?> type) throws IOException {
- writeTypePrefixForObject(value, g);
+ writeTypePrefix(g, typeId(value, type, JsonToken.START_OBJECT));
}
/**
- * Alternative version of the prefix-for-array method, which is given
- * actual type to use (instead of using exact type of the value); typically
- * a super type of actual value type
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, type, JsonToken.START_ARRAY));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
*/
+ @Deprecated // since 2.9
public void writeTypePrefixForArray(Object value, JsonGenerator g, Class<?> type) throws IOException {
- writeTypePrefixForArray(value, g);
+ writeTypePrefix(g, typeId(value, type, JsonToken.START_ARRAY));
}
/*
@@ -161,40 +282,77 @@
*/
/**
- * Method called to write initial part of type information for given
- * value, when it will be output as scalar JSON value (not as JSON
- * Object or Array),
- * using specified custom type id instead of calling {@link TypeIdResolver}.
- * This means that the context after call can not be that of JSON Object;
- * it may be Array or root context.
- *
- * @param value Value that will be serialized, for which type information is
- * to be written
- * @param g Generator to use for writing type information
- * @param typeId Exact type id to use
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, JsonToken.VALUE_STRING, typeId));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
*/
- public abstract void writeCustomTypePrefixForScalar(Object value, JsonGenerator g, String typeId) throws IOException;
+ @Deprecated // since 2.9
+ public void writeCustomTypePrefixForScalar(Object value, JsonGenerator g, String typeId) throws IOException {
+ writeTypePrefix(g, typeId(value, JsonToken.VALUE_STRING, typeId));
+ }
/**
- * Method called to write initial part of type information for given
- * value, when it will be output as JSON Object value (not as JSON
- * Array or scalar),
- * using specified custom type id instead of calling {@link TypeIdResolver}.
- * This means that context after call must be JSON Object, meaning that
- * caller can then proceed to output field entries.
- *
- * @param value Value that will be serialized, for which type information is
- * to be written
- * @param g Generator to use for writing type information
- * @param typeId Exact type id to use
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, JsonToken.START_OBJECT, typeId));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
*/
- public abstract void writeCustomTypePrefixForObject(Object value, JsonGenerator g, String typeId) throws IOException;
-
- public abstract void writeCustomTypePrefixForArray(Object value, JsonGenerator g, String typeId) throws IOException;
+ @Deprecated // since 2.9
+ public void writeCustomTypePrefixForObject(Object value, JsonGenerator g, String typeId) throws IOException {
+ writeTypePrefix(g, typeId(value, JsonToken.START_OBJECT, typeId));
+ }
- public abstract void writeCustomTypeSuffixForScalar(Object value, JsonGenerator g, String typeId) throws IOException;
+ /**
+ * DEPRECATED: now equivalent to:
+ *{@code writeTypePrefix(g, typeId(value, JsonToken.START_ARRAY, typeId));}.
+ * See {@link #writeTypePrefix} for more info.
+ *
+ * @deprecated Since 2.9 use {@link #writeTypePrefix(JsonGenerator, WritableTypeId)} instead
+ */
+ @Deprecated // since 2.9
+ public void writeCustomTypePrefixForArray(Object value, JsonGenerator g, String typeId) throws IOException {
+ writeTypePrefix(g, typeId(value, JsonToken.START_ARRAY, typeId));
+ }
- public abstract void writeCustomTypeSuffixForObject(Object value, JsonGenerator g, String typeId) throws IOException;
+ /**
+ * @deprecated Since 2.9 use {@link #writeTypeSuffix(JsonGenerator, WritableTypeId)} instead
+ */
+ @Deprecated // since 2.9
+ public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator g, String typeId) throws IOException {
+ _writeLegacySuffix(g, typeId(value, JsonToken.VALUE_STRING, typeId));
+ }
- public abstract void writeCustomTypeSuffixForArray(Object value, JsonGenerator g, String typeId) throws IOException;
+ /**
+ * @deprecated Since 2.9 use {@link #writeTypeSuffix(JsonGenerator, WritableTypeId)} instead
+ */
+ @Deprecated // since 2.9
+ public void writeCustomTypeSuffixForObject(Object value, JsonGenerator g, String typeId) throws IOException {
+ _writeLegacySuffix(g, typeId(value, JsonToken.START_OBJECT, typeId));
+ }
+
+ /**
+ * @deprecated Since 2.9 use {@link #writeTypeSuffix(JsonGenerator, WritableTypeId)} instead
+ */
+ @Deprecated // since 2.9
+ public void writeCustomTypeSuffixForArray(Object value, JsonGenerator g, String typeId) throws IOException {
+ _writeLegacySuffix(g, typeId(value, JsonToken.START_ARRAY, typeId));
+ }
+
+ /**
+ * Helper method needed for backwards compatibility: since original type id
+ * can not be routed through completely, we have to reverse-engineer likely
+ * setting before calling suffix.
+ *
+ * @since 2.9
+ */
+ protected final void _writeLegacySuffix(JsonGenerator g,
+ WritableTypeId typeId) throws IOException
+ {
+ // most likely logic within generator is this:
+ typeId.wrapperWritten = !g.canWriteTypeId();
+ writeTypeSuffix(g, typeId);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java
index 4172c17..250db2b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java
@@ -116,7 +116,7 @@
Object value = deser.deserialize(p, ctxt);
// And then need the closing END_ARRAY
if (hadStartArray && p.nextToken() != JsonToken.END_ARRAY) {
- ctxt.reportWrongTokenException(p, JsonToken.END_ARRAY,
+ ctxt.reportWrongTokenException(baseType(), JsonToken.END_ARRAY,
"expected closing END_ARRAY after type information and deserialized value");
// 05-May-2016, tatu: Not 100% what to do if exception is stored for
// future, and not thrown immediately: should probably skip until END_ARRAY
@@ -134,7 +134,7 @@
if (_defaultImpl != null) {
return _idResolver.idFromBaseType();
}
- ctxt.reportWrongTokenException(p, JsonToken.START_ARRAY,
+ ctxt.reportWrongTokenException(baseType(), JsonToken.START_ARRAY,
"need JSON Array to contain As.WRAPPER_ARRAY type information for class "+baseTypeName());
return null;
}
@@ -148,7 +148,8 @@
if (_defaultImpl != null) {
return _idResolver.idFromBaseType();
}
- ctxt.reportWrongTokenException(p, JsonToken.VALUE_STRING, "need JSON String that contains type id (for subtype of "+baseTypeName()+")");
+ ctxt.reportWrongTokenException(baseType(), JsonToken.VALUE_STRING,
+ "need JSON String that contains type id (for subtype of %s)", baseTypeName());
return null;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeSerializer.java
index adde36e..1ee6f2b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeSerializer.java
@@ -1,9 +1,6 @@
package com.fasterxml.jackson.databind.jsontype.impl;
-import java.io.IOException;
-
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
-import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
@@ -25,192 +22,4 @@
@Override
public As getTypeInclusion() { return As.WRAPPER_ARRAY; }
-
- /*
- /**********************************************************
- /* Writing prefixes
- /**********************************************************
- */
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator g) throws IOException {
- final String typeId = idFromValue(value);
- // NOTE: can not always avoid writing type id, even if null
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartArray();
- g.writeString(typeId);
- }
- g.writeStartObject();
- }
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator g, Class<?> type) throws IOException {
- final String typeId = idFromValueAndType(value, type);
- // NOTE: can not always avoid writing type id, even if null
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartArray();
- g.writeString(typeId);
- }
- g.writeStartObject();
- }
-
- @Override
- public void writeTypePrefixForArray(Object value, JsonGenerator g) throws IOException {
- final String typeId = idFromValue(value);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartArray();
- g.writeString(typeId);
- }
- g.writeStartArray();
- }
-
- @Override
- public void writeTypePrefixForArray(Object value, JsonGenerator g, Class<?> type) throws IOException {
- final String typeId = idFromValueAndType(value, type);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartArray();
- g.writeString(typeId);
- }
- g.writeStartArray();
- }
-
- @Override
- public void writeTypePrefixForScalar(Object value, JsonGenerator g) throws IOException {
- final String typeId = idFromValue(value);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- // only need the wrapper array
- g.writeStartArray();
- g.writeString(typeId);
- }
- }
-
- @Override
- public void writeTypePrefixForScalar(Object value, JsonGenerator g, Class<?> type) throws IOException {
- final String typeId = idFromValueAndType(value, type);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- // only need the wrapper array
- g.writeStartArray();
- g.writeString(typeId);
- }
- }
-
- /*
- /**********************************************************
- /* Writing suffixes
- /**********************************************************
- */
-
- @Override
- public void writeTypeSuffixForObject(Object value, JsonGenerator g) throws IOException {
- g.writeEndObject();
- if (!g.canWriteTypeId()) {
- g.writeEndArray();
- }
- }
-
- @Override
- public void writeTypeSuffixForArray(Object value, JsonGenerator g) throws IOException {
- // first array caller needs to close, then wrapper array
- g.writeEndArray();
- if (!g.canWriteTypeId()) {
- g.writeEndArray();
- }
- }
-
- @Override
- public void writeTypeSuffixForScalar(Object value, JsonGenerator g) throws IOException {
- if (!g.canWriteTypeId()) {
- // just the wrapper array to close
- g.writeEndArray();
- }
- }
-
- /*
- /**********************************************************
- /* Writing with custom type id
- /**********************************************************
- */
-
- @Override
- public void writeCustomTypePrefixForObject(Object value, JsonGenerator g, String typeId) throws IOException {
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartArray();
- g.writeString(typeId);
- }
- g.writeStartObject();
- }
-
- @Override
- public void writeCustomTypePrefixForArray(Object value, JsonGenerator g, String typeId) throws IOException {
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartArray();
- g.writeString(typeId);
- }
- g.writeStartArray();
- }
-
- @Override
- public void writeCustomTypePrefixForScalar(Object value, JsonGenerator g, String typeId) throws IOException {
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartArray();
- g.writeString(typeId);
- }
- }
-
- @Override
- public void writeCustomTypeSuffixForObject(Object value, JsonGenerator g, String typeId) throws IOException {
- if (!g.canWriteTypeId()) {
- writeTypeSuffixForObject(value, g); // standard impl works fine
- }
- }
-
- @Override
- public void writeCustomTypeSuffixForArray(Object value, JsonGenerator g, String typeId) throws IOException {
- if (!g.canWriteTypeId()) {
- writeTypeSuffixForArray(value, g); // standard impl works fine
- }
- }
-
- @Override
- public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator g, String typeId) throws IOException {
- if (!g.canWriteTypeId()) {
- writeTypeSuffixForScalar(value, g); // standard impl works fine
- }
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeSerializer.java
index fd10302..ec8ddaa 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeSerializer.java
@@ -1,9 +1,6 @@
package com.fasterxml.jackson.databind.jsontype.impl;
-import java.io.IOException;
-
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
-import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
@@ -15,7 +12,6 @@
public class AsExistingPropertyTypeSerializer
extends AsPropertyTypeSerializer
{
-
public AsExistingPropertyTypeSerializer(TypeIdResolver idRes,
BeanProperty property, String propName)
{
@@ -27,36 +23,7 @@
return (_property == prop) ? this :
new AsExistingPropertyTypeSerializer(_idResolver, prop, _typePropertyName);
}
-
- @Override
- public As getTypeInclusion() { return As.EXISTING_PROPERTY; }
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator gen) throws IOException
- {
- final String typeId = idFromValue(value);
- if ((typeId != null) && gen.canWriteTypeId()) {
- gen.writeTypeId(typeId);
- }
- gen.writeStartObject();
- }
@Override
- public void writeTypePrefixForObject(Object value, JsonGenerator gen, Class<?> type) throws IOException
- {
- final String typeId = idFromValueAndType(value, type);
- if ((typeId != null) && gen.canWriteTypeId()) {
- gen.writeTypeId(typeId);
- }
- gen.writeStartObject();
- }
-
- @Override
- public void writeCustomTypePrefixForObject(Object value, JsonGenerator gen, String typeId) throws IOException
- {
- if ((typeId != null) && gen.canWriteTypeId()) {
- gen.writeTypeId(typeId);
- }
- gen.writeStartObject();
- }
+ public As getTypeInclusion() { return As.EXISTING_PROPERTY; }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExternalTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExternalTypeSerializer.java
index 8c87391..821b6f7 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExternalTypeSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExternalTypeSerializer.java
@@ -11,7 +11,7 @@
* Type serializer that preferably embeds type information as an "external"
* type property; embedded in enclosing JSON object.
* Note that this serializer should only be used when value is being output
- * at JSON Object context; otherwise it can not work reliably, and will have
+ * at JSON Object context; otherwise it cannot work reliably, and will have
* to revert operation similar to {@link AsPropertyTypeSerializer}.
*<p>
* Note that implementation of serialization is bit cumbersome as we must
@@ -43,131 +43,38 @@
/*
/**********************************************************
- /* Writing prefixes
+ /* Helper methods
/**********************************************************
*/
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator gen) throws IOException {
- _writeObjectPrefix(value, gen);
+
+ // nothing to wrap it with:
+ protected final void _writeScalarPrefix(Object value, JsonGenerator g) throws IOException { }
+
+ protected final void _writeObjectPrefix(Object value, JsonGenerator g) throws IOException {
+ g.writeStartObject();
}
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator gen, Class<?> type) throws IOException {
- _writeObjectPrefix(value, gen);
+ protected final void _writeArrayPrefix(Object value, JsonGenerator g) throws IOException {
+ g.writeStartArray();
+ }
+
+ protected final void _writeScalarSuffix(Object value, JsonGenerator g, String typeId) throws IOException {
+ if (typeId != null) {
+ g.writeStringField(_typePropertyName, typeId);
+ }
+ }
+
+ protected final void _writeObjectSuffix(Object value, JsonGenerator g, String typeId) throws IOException {
+ g.writeEndObject();
+ if (typeId != null) {
+ g.writeStringField(_typePropertyName, typeId);
+ }
}
- @Override
- public void writeTypePrefixForArray(Object value, JsonGenerator gen) throws IOException {
- _writeArrayPrefix(value, gen);
+ protected final void _writeArraySuffix(Object value, JsonGenerator g, String typeId) throws IOException {
+ g.writeEndArray();
+ if (typeId != null) {
+ g.writeStringField(_typePropertyName, typeId);
+ }
}
-
- @Override
- public void writeTypePrefixForArray(Object value, JsonGenerator gen, Class<?> type) throws IOException {
- _writeArrayPrefix(value, gen);
- }
-
- @Override
- public void writeTypePrefixForScalar(Object value, JsonGenerator gen) throws IOException {
- _writeScalarPrefix(value, gen);
- }
-
- @Override
- public void writeTypePrefixForScalar(Object value, JsonGenerator gen, Class<?> type) throws IOException {
- _writeScalarPrefix(value, gen);
- }
-
- /*
- /**********************************************************
- /* Writing suffixes
- /**********************************************************
- */
-
- @Override
- public void writeTypeSuffixForObject(Object value, JsonGenerator gen) throws IOException {
- _writeObjectSuffix(value, gen, idFromValue(value));
- }
-
- @Override
- public void writeTypeSuffixForArray(Object value, JsonGenerator gen) throws IOException {
- _writeArraySuffix(value, gen, idFromValue(value));
- }
-
- @Override
- public void writeTypeSuffixForScalar(Object value, JsonGenerator gen) throws IOException {
- _writeScalarSuffix(value, gen, idFromValue(value));
- }
-
- /*
- /**********************************************************
- /* Writing with custom type id
- /**********************************************************
- */
-
- @Override
- public void writeCustomTypePrefixForScalar(Object value, JsonGenerator gen, String typeId) throws IOException {
- _writeScalarPrefix(value, gen);
- }
-
- @Override
- public void writeCustomTypePrefixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
- _writeObjectPrefix(value, gen);
- }
-
- @Override
- public void writeCustomTypePrefixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
- _writeArrayPrefix(value, gen);
- }
-
- @Override
- public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator gen, String typeId) throws IOException {
- _writeScalarSuffix(value, gen, typeId);
- }
-
- @Override
- public void writeCustomTypeSuffixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
- _writeObjectSuffix(value, gen, typeId);
- }
-
- @Override
- public void writeCustomTypeSuffixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
- _writeArraySuffix(value, gen, typeId);
- }
-
- /*
- /**********************************************************
- /* Helper methods
- /**********************************************************
- */
-
- // nothing to wrap it with:
- protected final void _writeScalarPrefix(Object value, JsonGenerator gen) throws IOException { }
-
- protected final void _writeObjectPrefix(Object value, JsonGenerator gen) throws IOException {
- gen.writeStartObject();
- }
-
- protected final void _writeArrayPrefix(Object value, JsonGenerator gen) throws IOException {
- gen.writeStartArray();
- }
-
- protected final void _writeScalarSuffix(Object value, JsonGenerator gen, String typeId) throws IOException {
- if (typeId != null) {
- gen.writeStringField(_typePropertyName, typeId);
- }
- }
-
- protected final void _writeObjectSuffix(Object value, JsonGenerator gen, String typeId) throws IOException {
- gen.writeEndObject();
- if (typeId != null) {
- gen.writeStringField(_typePropertyName, typeId);
- }
- }
-
- protected final void _writeArraySuffix(Object value, JsonGenerator gen, String typeId) throws IOException {
- gen.writeEndArray();
- if (typeId != null) {
- gen.writeStringField(_typePropertyName, typeId);
- }
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java
index daf3271..9bfab80 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java
@@ -106,7 +106,8 @@
}
@SuppressWarnings("resource")
- protected Object _deserializeTypedForId(JsonParser p, DeserializationContext ctxt, TokenBuffer tb) throws IOException
+ protected Object _deserializeTypedForId(JsonParser p, DeserializationContext ctxt,
+ TokenBuffer tb) throws IOException
{
String typeId = p.getText();
JsonDeserializer<Object> deser = _findDeserializer(ctxt, typeId);
@@ -131,40 +132,50 @@
// off-lined to keep main method lean and mean...
@SuppressWarnings("resource")
- protected Object _deserializeTypedUsingDefaultImpl(JsonParser p, DeserializationContext ctxt,
- TokenBuffer tb) throws IOException
+ protected Object _deserializeTypedUsingDefaultImpl(JsonParser p,
+ DeserializationContext ctxt, TokenBuffer tb) throws IOException
{
- // As per [JACKSON-614], may have default implementation to use
+ // May have default implementation to use
JsonDeserializer<Object> deser = _findDefaultImplDeserializer(ctxt);
- if (deser != null) {
- if (tb != null) {
- tb.writeEndObject();
- p = tb.asParser(p);
- // must move to point to the first token:
- p.nextToken();
+ if (deser == null) {
+ // or, perhaps we just bumped into a "natural" value (boolean/int/double/String)?
+ Object result = TypeDeserializer.deserializeIfNatural(p, ctxt, _baseType);
+ if (result != null) {
+ return result;
}
- return deser.deserialize(p, ctxt);
- }
- // or, perhaps we just bumped into a "natural" value (boolean/int/double/String)?
- Object result = TypeDeserializer.deserializeIfNatural(p, ctxt, _baseType);
- if (result != null) {
- return result;
- }
- // or, something for which "as-property" won't work, changed into "wrapper-array" type:
- if (p.isExpectedStartArrayToken()) {
- return super.deserializeTypedFromAny(p, ctxt);
- }
- if (p.hasToken(JsonToken.VALUE_STRING)) {
- if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
- String str = p.getText().trim();
- if (str.isEmpty()) {
- return null;
+ // or, something for which "as-property" won't work, changed into "wrapper-array" type:
+ if (p.isExpectedStartArrayToken()) {
+ return super.deserializeTypedFromAny(p, ctxt);
+ }
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
+ String str = p.getText().trim();
+ if (str.isEmpty()) {
+ return null;
+ }
}
}
+ String msg = String.format("missing type id property '%s'",
+ _typePropertyName);
+ // even better, may know POJO property polymorphic value would be assigned to
+ if (_property != null) {
+ msg = String.format("%s (for POJO property '%s')", msg, _property.getName());
+ }
+ JavaType t = _handleMissingTypeId(ctxt, msg);
+ if (t == null) {
+ // 09-Mar-2017, tatu: Is this the right thing to do?
+ return null;
+ }
+ // ... would this actually work?
+ deser = ctxt.findContextualValueDeserializer(t, _property);
}
- ctxt.reportWrongTokenException(p, JsonToken.FIELD_NAME,
- "missing property '"+_typePropertyName+"' that is to contain type id (for class "+baseTypeName()+")");
- return null;
+ if (tb != null) {
+ tb.writeEndObject();
+ p = tb.asParser(p);
+ // must move to point to the first token:
+ p.nextToken();
+ }
+ return deser.deserialize(p, ctxt);
}
/* Also need to re-route "unknown" version. Need to think
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeSerializer.java
index 5da4add..3b56f91 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeSerializer.java
@@ -1,9 +1,6 @@
package com.fasterxml.jackson.databind.jsontype.impl;
-import java.io.IOException;
-
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
-import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
@@ -28,7 +25,8 @@
@Override
public AsPropertyTypeSerializer forProperty(BeanProperty prop) {
- return (_property == prop) ? this : new AsPropertyTypeSerializer(this._idResolver, prop, this._typePropertyName);
+ return (_property == prop) ? this :
+ new AsPropertyTypeSerializer(this._idResolver, prop, this._typePropertyName);
}
@Override
@@ -36,76 +34,4 @@
@Override
public As getTypeInclusion() { return As.PROPERTY; }
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator jgen) throws IOException
- {
- final String typeId = idFromValue(value);
- if (typeId == null) {
- jgen.writeStartObject();
- } else if (jgen.canWriteTypeId()) {
- jgen.writeTypeId(typeId);
- jgen.writeStartObject();
- } else {
- jgen.writeStartObject();
- jgen.writeStringField(_typePropertyName, typeId);
- }
- }
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator jgen, Class<?> type) throws IOException
- {
- final String typeId = idFromValueAndType(value, type);
- if (typeId == null) {
- jgen.writeStartObject();
- } else if (jgen.canWriteTypeId()) {
- jgen.writeTypeId(typeId);
- jgen.writeStartObject();
- } else {
- jgen.writeStartObject();
- jgen.writeStringField(_typePropertyName, typeId);
- }
- }
-
- //public void writeTypePrefixForArray(Object value, JsonGenerator jgen)
- //public void writeTypePrefixForArray(Object value, JsonGenerator jgen, Class<?> type)
- //public void writeTypePrefixForScalar(Object value, JsonGenerator jgen)
- //public void writeTypePrefixForScalar(Object value, JsonGenerator jgen, Class<?> type)
-
- @Override
- public void writeTypeSuffixForObject(Object value, JsonGenerator jgen) throws IOException {
- // always need to close, regardless of whether its native type id or not
- jgen.writeEndObject();
- }
-
- //public void writeTypeSuffixForArray(Object value, JsonGenerator jgen)
- //public void writeTypeSuffixForScalar(Object value, JsonGenerator jgen)
-
-
- /*
- /**********************************************************
- /* Writing with custom type id
- /**********************************************************
- */
-
- // Only need to override Object-variants
-
- @Override
- public void writeCustomTypePrefixForObject(Object value, JsonGenerator jgen, String typeId) throws IOException
- {
- if (typeId == null) {
- jgen.writeStartObject();
- } else if (jgen.canWriteTypeId()) {
- jgen.writeTypeId(typeId);
- jgen.writeStartObject();
- } else {
- jgen.writeStartObject();
- jgen.writeStringField(_typePropertyName, typeId);
- }
- }
-
- @Override
- public void writeCustomTypeSuffixForObject(Object value, JsonGenerator jgen, String typeId) throws IOException {
- jgen.writeEndObject();
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeDeserializer.java
index 02f26ca..643f921 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeDeserializer.java
@@ -93,11 +93,11 @@
if (t == JsonToken.START_OBJECT) {
// should always get field name, but just in case...
if (p.nextToken() != JsonToken.FIELD_NAME) {
- ctxt.reportWrongTokenException(p, JsonToken.FIELD_NAME,
+ ctxt.reportWrongTokenException(baseType(), JsonToken.FIELD_NAME,
"need JSON String that contains type id (for subtype of "+baseTypeName()+")");
}
} else if (t != JsonToken.FIELD_NAME) {
- ctxt.reportWrongTokenException(p, JsonToken.START_OBJECT,
+ ctxt.reportWrongTokenException(baseType(), JsonToken.START_OBJECT,
"need JSON Object to contain As.WRAPPER_OBJECT type information for class "+baseTypeName());
}
final String typeId = p.getText();
@@ -121,7 +121,7 @@
Object value = deser.deserialize(p, ctxt);
// And then need the closing END_OBJECT
if (p.nextToken() != JsonToken.END_OBJECT) {
- ctxt.reportWrongTokenException(p, JsonToken.END_OBJECT,
+ ctxt.reportWrongTokenException(baseType(), JsonToken.END_OBJECT,
"expected closing END_OBJECT after type information and deserialized value");
}
return value;
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java
index feab592..8f12499 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java
@@ -7,6 +7,7 @@
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Type wrapper that tries to use an extra JSON Object, with a single
@@ -29,203 +30,6 @@
@Override
public As getTypeInclusion() { return As.WRAPPER_OBJECT; }
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator g) throws IOException
- {
- String typeId = idFromValue(value);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- g.writeStartObject();
- } else {
- // wrapper
- g.writeStartObject();
- // and then JSON Object start caller wants
-
- // 28-Jan-2015, tatu: No really good answer here; can not really change
- // structure, so change null to empty String...
- g.writeObjectFieldStart(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeTypePrefixForObject(Object value, JsonGenerator g, Class<?> type) throws IOException
- {
- String typeId = idFromValueAndType(value, type);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- g.writeStartObject();
- } else {
- // wrapper
- g.writeStartObject();
- // and then JSON Object start caller wants
-
- // 28-Jan-2015, tatu: No really good answer here; can not really change
- // structure, so change null to empty String...
- g.writeObjectFieldStart(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeTypePrefixForArray(Object value, JsonGenerator g) throws IOException
- {
- String typeId = idFromValue(value);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- g.writeStartArray();
- } else {
- // can still wrap ok
- g.writeStartObject();
- g.writeArrayFieldStart(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeTypePrefixForArray(Object value, JsonGenerator g, Class<?> type) throws IOException
- {
- final String typeId = idFromValueAndType(value, type);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- g.writeStartArray();
- } else {
- // can still wrap ok
- g.writeStartObject();
- // and then JSON Array start caller wants
- g.writeArrayFieldStart(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeTypePrefixForScalar(Object value, JsonGenerator g) throws IOException {
- final String typeId = idFromValue(value);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- // can still wrap ok
- g.writeStartObject();
- g.writeFieldName(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeTypePrefixForScalar(Object value, JsonGenerator g, Class<?> type) throws IOException
- {
- final String typeId = idFromValueAndType(value, type);
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- // can still wrap ok
- g.writeStartObject();
- g.writeFieldName(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeTypeSuffixForObject(Object value, JsonGenerator g) throws IOException
- {
- // first close JSON Object caller used
- g.writeEndObject();
- if (!g.canWriteTypeId()) {
- // and then wrapper
- g.writeEndObject();
- }
- }
-
- @Override
- public void writeTypeSuffixForArray(Object value, JsonGenerator g) throws IOException
- {
- // first close array caller needed
- g.writeEndArray();
- if (!g.canWriteTypeId()) {
- // then wrapper object
- g.writeEndObject();
- }
- }
-
- @Override
- public void writeTypeSuffixForScalar(Object value, JsonGenerator g) throws IOException {
- if (!g.canWriteTypeId()) {
- // just need to close the wrapper object
- g.writeEndObject();
- }
- }
-
- /*
- /**********************************************************
- /* Writing with custom type id
- /**********************************************************
- */
-
- @Override
- public void writeCustomTypePrefixForObject(Object value, JsonGenerator g, String typeId) throws IOException {
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- g.writeStartObject();
- } else {
- g.writeStartObject();
- g.writeObjectFieldStart(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeCustomTypePrefixForArray(Object value, JsonGenerator g, String typeId) throws IOException {
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- g.writeStartArray();
- } else {
- g.writeStartObject();
- g.writeArrayFieldStart(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeCustomTypePrefixForScalar(Object value, JsonGenerator g, String typeId) throws IOException {
- if (g.canWriteTypeId()) {
- if (typeId != null) {
- g.writeTypeId(typeId);
- }
- } else {
- g.writeStartObject();
- g.writeFieldName(_validTypeId(typeId));
- }
- }
-
- @Override
- public void writeCustomTypeSuffixForObject(Object value, JsonGenerator g, String typeId) throws IOException {
- if (!g.canWriteTypeId()) {
- writeTypeSuffixForObject(value, g); // standard impl works fine
- }
- }
-
- @Override
- public void writeCustomTypeSuffixForArray(Object value, JsonGenerator g, String typeId) throws IOException {
- if (!g.canWriteTypeId()) {
- writeTypeSuffixForArray(value, g); // standard impl works fine
- }
- }
-
- @Override
- public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator g, String typeId) throws IOException {
- if (!g.canWriteTypeId()) {
- writeTypeSuffixForScalar(value, g); // standard impl works fine
- }
- }
/*
/**********************************************************
@@ -240,6 +44,14 @@
* @since 2.6
*/
protected String _validTypeId(String typeId) {
- return (typeId == null) ? "" : typeId;
+ return ClassUtil.nonNullString(typeId);
+ }
+
+ // @since 2.9
+ protected final void _writeTypeId(JsonGenerator g, String typeId) throws IOException
+ {
+ if (typeId != null) {
+ g.writeTypeId(typeId);
+ }
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java
index 2b3da1a..934cdd3 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java
@@ -28,7 +28,7 @@
public void registerSubtype(Class<?> type, String name) {
// not used with class name - based resolvers
}
-
+
@Override
public String idFromValue(Object value) {
return _idFrom(value, value.getClass(), _typeFactory);
@@ -46,43 +46,17 @@
protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException
{
- /* 30-Jan-2010, tatu: Most ids are basic class names; so let's first
- * check if any generics info is added; and only then ask factory
- * to do translation when necessary
- */
- TypeFactory tf = ctxt.getTypeFactory();
- if (id.indexOf('<') > 0) {
- // note: may want to try combining with specialization (esp for EnumMap)?
- // 17-Aug-2017, tatu: As per [databind#1735] need to ensure assignment
- // compatibility -- needed later anyway, and not doing so may open
- // security issues.
- JavaType t = tf.constructFromCanonical(id);
- if (!t.isTypeOrSubTypeOf(_baseType.getRawClass())) {
- // Probably cleaner to have a method in `TypeFactory` but can't add in patch
- throw new IllegalArgumentException(String.format(
- "Class %s not subtype of %s", t.getRawClass().getName(), _baseType));
- }
- return t;
- }
- Class<?> cls;
- try {
- cls = tf.findClass(id);
- } catch (ClassNotFoundException e) {
- // 24-May-2016, tatu: Ok, this is pretty ugly, but we should always get
- // DeserializationContext, just playing it safe
+ JavaType t = ctxt.resolveSubType(_baseType, id);
+ if (t == null) {
if (ctxt instanceof DeserializationContext) {
- DeserializationContext dctxt = (DeserializationContext) ctxt;
// First: we may have problem handlers that can deal with it?
- return dctxt.handleUnknownTypeId(_baseType, id, this, "no such class found");
+ return ((DeserializationContext) ctxt).handleUnknownTypeId(_baseType, id, this, "no such class found");
}
// ... meaning that we really should never get here.
- return null;
- } catch (Exception e) {
- throw new IllegalArgumentException("Invalid type id '"+id+"' (for id type 'Id.class'): "+e.getMessage(), e);
}
- return tf.constructSpecializedType(_baseType, cls);
+ return t;
}
-
+
/*
/**********************************************************
/* Internal methods
@@ -114,19 +88,9 @@
Class<?> valueClass = Object.class;
// not optimal: but EnumMap is not a customizable type so this is sort of ok
str = typeFactory.constructMapType(EnumMap.class, enumClass, valueClass).toCanonical();
- } else {
- // 17-Feb-2010, tatus: Another such case: result of Arrays.asList() is
- // named like so in Sun JDK... Let's just plain old ArrayList in its place.
- // ... also, other similar cases exist...
- String suffix = str.substring(JAVA_UTIL_PKG.length());
- if (isJavaUtilCollectionClass(suffix, "List")) {
- str = ArrayList.class.getName();
- } else if (isJavaUtilCollectionClass(suffix, "Map")){
- str = HashMap.class.getName();
- } else if (isJavaUtilCollectionClass(suffix, "Set")){
- str = HashSet.class.getName();
- }
}
+ // 10-Jan-2018, tatu: Up until 2.9.4 we used to have other conversions for `Collections.xxx()`
+ // and `Arrays.asList(...)`; but it was changed to be handled on receiving end instead
} else if (str.indexOf('$') >= 0) {
/* Other special handling may be needed for inner classes,
* The best way to handle would be to find 'hidden' constructor; pass parent
@@ -154,17 +118,4 @@
public String getDescForKnownTypeIds() {
return "class name used as type id";
}
-
- private static boolean isJavaUtilCollectionClass(String clz, String type)
- {
- if (clz.startsWith("Collections$")) {
- // 02-Jan-2017, tatu: As per [databind#1868], may need to leave Unmodifiable variants as is
- return (clz.indexOf(type) > 0)
- && !clz.contains("Unmodifiable");
- }
- if (clz.startsWith("Arrays$")) {
- return (clz.indexOf(type) > 0);
- }
- return false;
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java
index 0158215..b705511 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.databind.jsontype.impl;
+import java.lang.reflect.Modifier;
import java.util.*;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
@@ -21,7 +22,7 @@
protected LinkedHashSet<NamedType> _registeredSubtypes;
public StdSubtypeResolver() { }
-
+
/*
/**********************************************************
/* Subtype registration
@@ -47,6 +48,17 @@
registerSubtypes(types);
}
+ @Override // since 2.9
+ public void registerSubtypes(Collection<Class<?>> subtypes) {
+ int len = subtypes.size();
+ NamedType[] types = new NamedType[len];
+ int i = 0;
+ for (Class<?> subtype : subtypes) {
+ types[i++] = new NamedType(subtype);
+ }
+ registerSubtypes(types);
+ }
+
/*
/**********************************************************
/* Resolution by class (serialization)
@@ -67,23 +79,27 @@
for (NamedType subtype : _registeredSubtypes) {
// is it a subtype of root type?
if (rawBase.isAssignableFrom(subtype.getType())) { // yes
- AnnotatedClass curr = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), config);
+ AnnotatedClass curr = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ subtype.getType());
_collectAndResolve(curr, subtype, config, ai, collected);
}
}
}
// then annotated types for property itself
- Collection<NamedType> st = ai.findSubtypes(property);
- if (st != null) {
- for (NamedType nt : st) {
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(nt.getType(), config);
- _collectAndResolve(ac, nt, config, ai, collected);
- }
+ if (property != null) {
+ Collection<NamedType> st = ai.findSubtypes(property);
+ if (st != null) {
+ for (NamedType nt : st) {
+ AnnotatedClass ac = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ nt.getType());
+ _collectAndResolve(ac, nt, config, ai, collected);
+ }
+ }
}
-
+
NamedType rootType = new NamedType(rawBase, null);
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(rawBase, config);
+ AnnotatedClass ac = AnnotatedClassResolver.resolveWithoutSuperTypes(config, rawBase);
// and finally subtypes via annotations from base type (recursively)
_collectAndResolve(ac, rootType, config, ai, collected);
@@ -103,7 +119,8 @@
for (NamedType subtype : _registeredSubtypes) {
// is it a subtype of root type?
if (rawBase.isAssignableFrom(subtype.getType())) { // yes
- AnnotatedClass curr = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), config);
+ AnnotatedClass curr = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ subtype.getType());
_collectAndResolve(curr, subtype, config, ai, subtypes);
}
}
@@ -125,7 +142,7 @@
AnnotatedMember property, JavaType baseType)
{
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
- Class<?> rawBase = (baseType == null) ? property.getRawType() : baseType.getRawClass();
+ Class<?> rawBase = baseType.getRawClass();
// Need to keep track of classes that have been handled already
Set<Class<?>> typesHandled = new HashSet<Class<?>>();
@@ -133,52 +150,56 @@
// start with lowest-precedence, which is from type hierarchy
NamedType rootType = new NamedType(rawBase, null);
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(rawBase, config);
+ AnnotatedClass ac = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ rawBase);
_collectAndResolveByTypeId(ac, rootType, config, typesHandled, byName);
// then with definitions from property
- Collection<NamedType> st = ai.findSubtypes(property);
- if (st != null) {
- for (NamedType nt : st) {
- ac = AnnotatedClass.constructWithoutSuperTypes(nt.getType(), config);
- _collectAndResolveByTypeId(ac, nt, config, typesHandled, byName);
- }
+ if (property != null) {
+ Collection<NamedType> st = ai.findSubtypes(property);
+ if (st != null) {
+ for (NamedType nt : st) {
+ ac = AnnotatedClassResolver.resolveWithoutSuperTypes(config, nt.getType());
+ _collectAndResolveByTypeId(ac, nt, config, typesHandled, byName);
+ }
+ }
}
-
// and finally explicit type registrations (highest precedence)
if (_registeredSubtypes != null) {
for (NamedType subtype : _registeredSubtypes) {
// is it a subtype of root type?
if (rawBase.isAssignableFrom(subtype.getType())) { // yes
- AnnotatedClass curr = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), config);
+ AnnotatedClass curr = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ subtype.getType());
_collectAndResolveByTypeId(curr, subtype, config, typesHandled, byName);
}
}
}
- return _combineNamedAndUnnamed(typesHandled, byName);
+ return _combineNamedAndUnnamed(rawBase, typesHandled, byName);
}
@Override
public Collection<NamedType> collectAndResolveSubtypesByTypeId(MapperConfig<?> config,
- AnnotatedClass type)
+ AnnotatedClass baseType)
{
+ final Class<?> rawBase = baseType.getRawType();
Set<Class<?>> typesHandled = new HashSet<Class<?>>();
Map<String,NamedType> byName = new LinkedHashMap<String,NamedType>();
- NamedType rootType = new NamedType(type.getRawType(), null);
- _collectAndResolveByTypeId(type, rootType, config, typesHandled, byName);
+ NamedType rootType = new NamedType(rawBase, null);
+ _collectAndResolveByTypeId(baseType, rootType, config, typesHandled, byName);
if (_registeredSubtypes != null) {
- Class<?> rawBase = type.getRawType();
for (NamedType subtype : _registeredSubtypes) {
// is it a subtype of root type?
if (rawBase.isAssignableFrom(subtype.getType())) { // yes
- AnnotatedClass curr = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), config);
+ AnnotatedClass curr = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ subtype.getType());
_collectAndResolveByTypeId(curr, subtype, config, typesHandled, byName);
}
}
}
- return _combineNamedAndUnnamed(typesHandled, byName);
+ return _combineNamedAndUnnamed(rawBase, typesHandled, byName);
}
/*
@@ -218,7 +239,8 @@
Collection<NamedType> st = ai.findSubtypes(annotatedType);
if (st != null && !st.isEmpty()) {
for (NamedType subtype : st) {
- AnnotatedClass subtypeClass = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), config);
+ AnnotatedClass subtypeClass = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ subtype.getType());
_collectAndResolve(subtypeClass, subtype, config, ai, collectedSubtypes);
}
}
@@ -248,7 +270,8 @@
Collection<NamedType> st = ai.findSubtypes(annotatedType);
if (st != null && !st.isEmpty()) {
for (NamedType subtype : st) {
- AnnotatedClass subtypeClass = AnnotatedClass.constructWithoutSuperTypes(subtype.getType(), config);
+ AnnotatedClass subtypeClass = AnnotatedClassResolver.resolveWithoutSuperTypes(config,
+ subtype.getType());
_collectAndResolveByTypeId(subtypeClass, subtype, config, typesHandled, byName);
}
}
@@ -259,8 +282,8 @@
* Helper method used for merging explicitly named types and handled classes
* without explicit names.
*/
- protected Collection<NamedType> _combineNamedAndUnnamed(Set<Class<?>> typesHandled,
- Map<String,NamedType> byName)
+ protected Collection<NamedType> _combineNamedAndUnnamed(Class<?> rawBase,
+ Set<Class<?>> typesHandled, Map<String,NamedType> byName)
{
ArrayList<NamedType> result = new ArrayList<NamedType>(byName.values());
@@ -271,6 +294,11 @@
typesHandled.remove(t.getType());
}
for (Class<?> cls : typesHandled) {
+ // 27-Apr-2017, tatu: [databind#1616] Do not add base type itself unless
+ // it is concrete (or has explicit type name)
+ if ((cls == rawBase) && Modifier.isAbstract(cls.getModifiers())) {
+ continue;
+ }
result.add(new NamedType(cls));
}
return result;
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java
index e5a3e4d..98e9f90 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java
@@ -46,6 +46,16 @@
public StdTypeResolverBuilder() { }
+ /**
+ * @since 2.9
+ */
+ protected StdTypeResolverBuilder(JsonTypeInfo.Id idType,
+ JsonTypeInfo.As idAs, String propName) {
+ _idType = idType;
+ _includeAs = idAs;
+ _typeProperty = propName;
+ }
+
public static StdTypeResolverBuilder noTypeInfoBuilder() {
return new StdTypeResolverBuilder().init(JsonTypeInfo.Id.NONE, null);
}
@@ -55,7 +65,7 @@
{
// sanity checks
if (idType == null) {
- throw new IllegalArgumentException("idType can not be null");
+ throw new IllegalArgumentException("idType cannot be null");
}
_idType = idType;
_customIdResolver = idRes;
@@ -110,25 +120,7 @@
TypeIdResolver idRes = idResolver(config, baseType, subtypes, false, true);
- JavaType defaultImpl;
-
- if (_defaultImpl == null) {
- defaultImpl = null;
- } else {
- // 20-Mar-2016, tatu: It is important to do specialization go through
- // TypeFactory to ensure proper resolution; with 2.7 and before, direct
- // call to JavaType was used, but that can not work reliably with 2.7
- // 20-Mar-2016, tatu: Can finally add a check for type compatibility BUT
- // if so, need to add explicit checks for marker types. Not ideal, but
- // seems like a reasonable compromise.
- if ((_defaultImpl == Void.class)
- || (_defaultImpl == NoClass.class)) {
- defaultImpl = config.getTypeFactory().constructType(_defaultImpl);
- } else {
- defaultImpl = config.getTypeFactory()
- .constructSpecializedType(baseType, _defaultImpl);
- }
- }
+ JavaType defaultImpl = defineDefaultImpl(config, baseType);
// First, method for converting type info to type id:
switch (_includeAs) {
@@ -149,6 +141,50 @@
throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
}
+ protected JavaType defineDefaultImpl(DeserializationConfig config, JavaType baseType) {
+ JavaType defaultImpl;
+ if (_defaultImpl == null) {
+ //Fis of issue #955
+ if (config.isEnabled(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL) && !baseType.isAbstract()) {
+ defaultImpl = baseType;
+ } else {
+ defaultImpl = null;
+ }
+ } else {
+ // 20-Mar-2016, tatu: It is important to do specialization go through
+ // TypeFactory to ensure proper resolution; with 2.7 and before, direct
+ // call to JavaType was used, but that cannot work reliably with 2.7
+ // 20-Mar-2016, tatu: Can finally add a check for type compatibility BUT
+ // if so, need to add explicit checks for marker types. Not ideal, but
+ // seems like a reasonable compromise.
+ if ((_defaultImpl == Void.class)
+ || (_defaultImpl == NoClass.class)) {
+ defaultImpl = config.getTypeFactory().constructType(_defaultImpl);
+ } else {
+ if (baseType.hasRawClass(_defaultImpl)) { // common enough to check
+ defaultImpl = baseType;
+ } else if (baseType.isTypeOrSuperTypeOf(_defaultImpl)) {
+ // most common case with proper base type...
+ defaultImpl = config.getTypeFactory()
+ .constructSpecializedType(baseType, _defaultImpl);
+ } else {
+ // 05-Apr-2018, tatu: As [databind#1565] and [databind#1861] need to allow
+ // some cases of seemingly incompatible `defaultImpl`. Easiest to just clear
+ // the setting.
+
+ /*
+ throw new IllegalArgumentException(
+ String.format("Invalid \"defaultImpl\" (%s): not a subtype of basetype (%s)",
+ ClassUtil.nameOf(_defaultImpl), ClassUtil.nameOf(baseType.getRawClass()))
+ );
+ */
+ defaultImpl = null;
+ }
+ }
+ }
+ return defaultImpl;
+ }
+
/*
/**********************************************************
/* Construction, configuration
@@ -158,7 +194,7 @@
@Override
public StdTypeResolverBuilder inclusion(JsonTypeInfo.As includeAs) {
if (includeAs == null) {
- throw new IllegalArgumentException("includeAs can not be null");
+ throw new IllegalArgumentException("includeAs cannot be null");
}
_includeAs = includeAs;
return this;
@@ -217,7 +253,7 @@
{
// Custom id resolver?
if (_customIdResolver != null) { return _customIdResolver; }
- if (_idType == null) throw new IllegalStateException("Can not build, 'init()' not yet called");
+ if (_idType == null) throw new IllegalStateException("Cannot build, 'init()' not yet called");
switch (_idType) {
case CLASS:
return new ClassNameIdResolver(baseType, config.getTypeFactory());
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java
index c5f3e75..20bbf20 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java
@@ -4,6 +4,7 @@
import java.util.HashSet;
import java.util.Set;
+import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
@@ -48,6 +49,9 @@
// [databind#1737]; 3rd party
//s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"); // deprecated by [databind#1855]
s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");
+ // [databind#2680]
+ s.add("org.springframework.aop.config.MethodLocatingFactoryBean");
+ s.add("org.springframework.beans.factory.config.BeanReferenceFactoryBean");
// s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource"); // deprecated by [databind#1931]
// s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"); // - "" -
@@ -73,19 +77,20 @@
s.add("com.sun.deploy.security.ruleset.DRSHelper");
s.add("org.apache.axis2.jaxws.spi.handler.HandlerResolverImpl");
- // [databind#2186]: yet more 3rd party gadgets
+ // [databind#2186], [databind#2670]: yet more 3rd party gadgets
s.add("org.jboss.util.propertyeditor.DocumentEditor");
s.add("org.apache.openjpa.ee.RegistryManagedRuntime");
s.add("org.apache.openjpa.ee.JNDIManagedRuntime");
+ s.add("org.apache.openjpa.ee.WASRegistryManagedRuntime"); // [#2670] addition
s.add("org.apache.axis2.transport.jms.JMSOutTransportInfo");
- // [databind#2326]
- s.add("com.mysql.cj.jdbc.admin.MiniAdmin");
+ // [databind#2326] (2.9.9)
+ s.add("com.mysql.cj.jdbc.admin.MiniAdmin");
- // [databind#2334]: logback-core
+ // [databind#2334]: logback-core (2.9.9.1)
s.add("ch.qos.logback.core.db.DriverManagerConnectionSource");
- // [databind#2341]: jdom/jdom2
+ // [databind#2341]: jdom/jdom2 (2.9.9.1)
s.add("org.jdom.transform.XSLTransformer");
s.add("org.jdom2.transform.XSLTransformer");
@@ -137,8 +142,55 @@
// [databind#2642]: javax.swing (jdk)
s.add("javax.swing.JEditorPane");
- // [databind#2648]: shire-core
+ // [databind#2648], [databind#2653]: shire-core
s.add("org.apache.shiro.realm.jndi.JndiRealmFactory");
+ s.add("org.apache.shiro.jndi.JndiObjectFactory");
+
+ // [databind#2658]: ignite-jta (, quartz-core)
+ s.add("org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup");
+ s.add("org.apache.ignite.cache.jta.jndi.CacheJndiTmFactory");
+ s.add("org.quartz.utils.JNDIConnectionProvider");
+
+ // [databind#2659]: aries.transaction.jms
+ s.add("org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory");
+ s.add("org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory");
+
+ // [databind#2660]: caucho-quercus
+ s.add("com.caucho.config.types.ResourceRef");
+
+ // [databind#2662]: aoju/bus-proxy
+ s.add("org.aoju.bus.proxy.provider.RmiProvider");
+ s.add("org.aoju.bus.proxy.provider.remoting.RmiProvider");
+
+ // [databind#2664]: activemq-core, activemq-pool, activemq-pool-jms
+
+ s.add("org.apache.activemq.ActiveMQConnectionFactory"); // core
+ s.add("org.apache.activemq.ActiveMQXAConnectionFactory");
+ s.add("org.apache.activemq.spring.ActiveMQConnectionFactory");
+ s.add("org.apache.activemq.spring.ActiveMQXAConnectionFactory");
+ s.add("org.apache.activemq.pool.JcaPooledConnectionFactory"); // pool
+ s.add("org.apache.activemq.pool.PooledConnectionFactory");
+ s.add("org.apache.activemq.pool.XaPooledConnectionFactory");
+ s.add("org.apache.activemq.jms.pool.XaPooledConnectionFactory"); // pool-jms
+ s.add("org.apache.activemq.jms.pool.JcaPooledConnectionFactory");
+
+ // [databind#2666]: apache/commons-jms
+ s.add("org.apache.commons.proxy.provider.remoting.RmiProvider");
+
+ // [databind#2682]: commons-jelly
+ s.add("org.apache.commons.jelly.impl.Embedded");
+
+ // [databind#2688]: apache/drill
+ s.add("oadd.org.apache.xalan.lib.sql.JNDIConnectionPool");
+
+ // [databind#2698]: weblogic w/ oracle/aq-jms
+ // (note: dependency not available via Maven Central, but as part of
+ // weblogic installation, possibly fairly old version(s))
+ s.add("oracle.jms.AQjmsQueueConnectionFactory");
+ s.add("oracle.jms.AQjmsXATopicConnectionFactory");
+ s.add("oracle.jms.AQjmsTopicConnectionFactory");
+ s.add("oracle.jms.AQjmsXAQueueConnectionFactory");
+ s.add("oracle.jms.AQjmsXAConnectionFactory");
DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s);
}
@@ -154,8 +206,8 @@
public static SubTypeValidator instance() { return instance; }
- public void validateSubType(DeserializationContext ctxt, JavaType type)
- throws JsonMappingException
+ public void validateSubType(DeserializationContext ctxt, JavaType type,
+ BeanDescription beanDesc) throws JsonMappingException
{
// There are certain nasty classes that could cause problems, mostly
// via default typing -- catch them here.
@@ -197,7 +249,7 @@
return;
} while (false);
- throw JsonMappingException.from(ctxt,
- String.format("Illegal type (%s) to deserialize: prevented for security reasons", full));
+ ctxt.reportBadTypeDefinition(beanDesc,
+ "Illegal type (%s) to deserialize: prevented for security reasons", full);
}
}
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 7284b47..58f1a5f 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
@@ -40,7 +40,7 @@
/**
* Type to use as the default implementation, if type id is
- * missing or can not be resolved.
+ * missing or cannot be resolved.
*/
protected final JavaType _defaultImpl;
@@ -74,8 +74,7 @@
{
_baseType = baseType;
_idResolver = idRes;
- // 22-Dec-2015, tatu: as per [databind#1055], avoid NPE
- _typePropertyName = (typePropertyName == null) ? "" : typePropertyName;
+ _typePropertyName = ClassUtil.nonNullString(typePropertyName);
_typeIdVisible = typeIdVisible;
// defaults are fine, although shouldn't need much concurrency
_deserializers = new ConcurrentHashMap<String, JsonDeserializer<Object>>(16, 0.75f, 2);
@@ -117,9 +116,16 @@
@Override
public Class<?> getDefaultImpl() {
- return (_defaultImpl == null) ? null : _defaultImpl.getRawClass();
+ return ClassUtil.rawClass(_defaultImpl);
}
-
+
+ /**
+ * @since 2.9
+ */
+ public JavaType baseType() {
+ return _baseType;
+ }
+
@Override
public String toString()
{
@@ -142,21 +148,21 @@
{
JsonDeserializer<Object> deser = _deserializers.get(typeId);
if (deser == null) {
- /* As per [Databind#305], need to provide contextual info. But for
+ /* As per [databind#305], need to provide contextual info. But for
* backwards compatibility, let's start by only supporting this
* for base class, not via interface. Later on we can add this
* to the interface, assuming deprecation at base class helps.
*/
JavaType type = _idResolver.typeFromId(ctxt, typeId);
if (type == null) {
- // As per [JACKSON-614], use the default impl if no type id available:
+ // use the default impl if no type id available:
deser = _findDefaultImplDeserializer(ctxt);
if (deser == null) {
// 10-May-2016, tatu: We may get some help...
- JavaType actual = _handleUnknownTypeId(ctxt, typeId, _idResolver, _baseType);
+ JavaType actual = _handleUnknownTypeId(ctxt, typeId);
if (actual == null) { // what should this be taken to mean?
- // TODO: try to figure out something better
- return null;
+ // 17-Jan-2019, tatu: As per [databind#2221], better NOT return `null` but...
+ return NullifyingDeserializer.instance;
}
// ... would this actually work?
deser = ctxt.findContextualValueDeserializer(actual, _property);
@@ -166,7 +172,7 @@
* we actually now need to explicitly narrow from base type (which may have parameterization)
* using raw type.
*
- * One complication, though; can not change 'type class' (simple type to container); otherwise
+ * One complication, though; cannot change 'type class' (simple type to container); otherwise
* we may try to narrow a SimpleType (Object.class) into MapType (Map.class), losing actual
* type in process (getting SimpleType of Map.class which will not work as expected)
*/
@@ -246,8 +252,8 @@
*/
deser = _findDefaultImplDeserializer(ctxt);
if (deser == null) {
- ctxt.reportMappingException("No (native) type id found when one was expected for polymorphic type handling");
- return null;
+ return ctxt.reportInputMismatch(baseType(),
+ "No (native) type id found when one was expected for polymorphic type handling");
}
} else {
String typeIdStr = (typeId instanceof String) ? (String) typeId : String.valueOf(typeId);
@@ -257,7 +263,7 @@
}
/**
- * Helper method called when given type id can not be resolved into
+ * Helper method called when given type id cannot be resolved into
* concrete deserializer either directly (using given {@link TypeIdResolver}),
* or using default type.
* Default implementation simply throws a {@link com.fasterxml.jackson.databind.JsonMappingException} to
@@ -269,16 +275,28 @@
*
* @since 2.8
*/
- protected JavaType _handleUnknownTypeId(DeserializationContext ctxt, String typeId,
- TypeIdResolver idResolver, JavaType baseType)
+ protected JavaType _handleUnknownTypeId(DeserializationContext ctxt, String typeId)
throws IOException
{
- String extraDesc = idResolver.getDescForKnownTypeIds();
+ String extraDesc = _idResolver.getDescForKnownTypeIds();
if (extraDesc == null) {
- extraDesc = "known type ids are not statically known";
+ extraDesc = "type ids are not statically known";
} else {
extraDesc = "known type ids = " + extraDesc;
}
- return ctxt.handleUnknownTypeId(_baseType, typeId, idResolver, extraDesc);
+ if (_property != null) {
+ extraDesc = String.format("%s (for POJO property '%s')", extraDesc,
+ _property.getName());
+ }
+ return ctxt.handleUnknownTypeId(_baseType, typeId, _idResolver, extraDesc);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected JavaType _handleMissingTypeId(DeserializationContext ctxt, String extraDesc)
+ throws IOException
+ {
+ return ctxt.handleMissingTypeId(_baseType, _idResolver, extraDesc);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeSerializerBase.java
index 5397946..5b79d40 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeSerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeSerializerBase.java
@@ -1,6 +1,12 @@
package com.fasterxml.jackson.databind.jsontype.impl;
+import java.io.IOException;
+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -17,6 +23,12 @@
_property = property;
}
+ /*
+ /**********************************************************
+ /* Base implementations, simple accessors
+ /**********************************************************
+ */
+
@Override
public abstract JsonTypeInfo.As getTypeInclusion();
@@ -26,6 +38,40 @@
@Override
public TypeIdResolver getTypeIdResolver() { return _idResolver; }
+ @Override
+ public WritableTypeId writeTypePrefix(JsonGenerator g,
+ WritableTypeId idMetadata) throws IOException
+ {
+ _generateTypeId(idMetadata);
+ return g.writeTypePrefix(idMetadata);
+ }
+
+ @Override
+ public WritableTypeId writeTypeSuffix(JsonGenerator g,
+ WritableTypeId idMetadata) throws IOException
+ {
+ return g.writeTypeSuffix(idMetadata);
+ }
+
+ /**
+ * Helper method that will generate type id to use, if not already passed.
+ *
+ * @since 2.9
+ */
+ protected void _generateTypeId(WritableTypeId idMetadata) {
+ Object id = idMetadata.id;
+ if (id == null) {
+ final Object value = idMetadata.forValue;
+ Class<?> typeForId = idMetadata.forValueType;
+ if (typeForId == null) {
+ id = idFromValue(value);
+ } else {
+ id = idFromValueAndType(value, typeForId);
+ }
+ idMetadata.id = id;
+ }
+ }
+
/*
/**********************************************************
/* Helper methods for subclasses
@@ -51,8 +97,8 @@
// As per [databind#633], maybe better just not do anything...
protected void handleMissingId(Object value) {
/*
- String typeDesc = (value == null) ? "NULL" : value.getClass().getName();
- throw new IllegalArgumentException("Can not resolve type id for "
+ String typeDesc = ClassUtil.classNameOf(value, "NULL");
+ throw new IllegalArgumentException("Cannot resolve type id for "
+typeDesc+" (using "+_idResolver.getClass().getName()+")");
*/
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/package-info.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/package-info.java
index fd931b0..41874d6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/package-info.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/package-info.java
@@ -2,7 +2,7 @@
* Package that contains interfaces that define how to implement
* functionality for dynamically resolving type during deserialization.
* This is needed for complete handling of polymorphic types, where
- * actual type can not be determined statically (declared type is
+ * actual type cannot be determined statically (declared type is
* a supertype of actual polymorphic serialized types).
*/
package com.fasterxml.jackson.databind.jsontype;
diff --git a/src/main/java/com/fasterxml/jackson/databind/module/SimpleAbstractTypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/module/SimpleAbstractTypeResolver.java
index 3b0c1fd..3701950 100644
--- a/src/main/java/com/fasterxml/jackson/databind/module/SimpleAbstractTypeResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/module/SimpleAbstractTypeResolver.java
@@ -53,14 +53,14 @@
{
// Sanity checks, just in case someone tries to force typing...
if (superType == subType) {
- throw new IllegalArgumentException("Can not add mapping from class to itself");
+ throw new IllegalArgumentException("Cannot add mapping from class to itself");
}
if (!superType.isAssignableFrom(subType)) {
- throw new IllegalArgumentException("Can not add mapping from class "+superType.getName()
+ throw new IllegalArgumentException("Cannot add mapping from class "+superType.getName()
+" to "+subType.getName()+", as latter is not a subtype of former");
}
if (!Modifier.isAbstract(superType.getModifiers())) {
- throw new IllegalArgumentException("Can not add mapping from class "+superType.getName()
+ throw new IllegalArgumentException("Cannot add mapping from class "+superType.getName()
+" since it is not abstract");
}
_mappings.put(new ClassKey(superType), subType);
diff --git a/src/main/java/com/fasterxml/jackson/databind/module/SimpleDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/module/SimpleDeserializers.java
index 35c6b37..0a9f443 100644
--- a/src/main/java/com/fasterxml/jackson/databind/module/SimpleDeserializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/module/SimpleDeserializers.java
@@ -85,7 +85,7 @@
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
throws JsonMappingException
{
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(type.getRawClass()));
+ return _find(type);
}
@Override
@@ -93,7 +93,7 @@
DeserializationConfig config, BeanDescription beanDesc)
throws JsonMappingException
{
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(type.getRawClass()));
+ return _find(type);
}
@Override
@@ -103,7 +103,7 @@
JsonDeserializer<?> elementDeserializer)
throws JsonMappingException
{
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(type.getRawClass()));
+ return _find(type);
}
@Override
@@ -113,7 +113,7 @@
JsonDeserializer<?> elementDeserializer)
throws JsonMappingException
{
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(type.getRawClass()));
+ return _find(type);
}
@Override
@@ -138,9 +138,12 @@
DeserializationConfig config, BeanDescription beanDesc)
throws JsonMappingException
{
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(nodeType));
+ if (_classMappings == null) {
+ return null;
+ }
+ return _classMappings.get(new ClassKey(nodeType));
}
-
+
@Override
public JsonDeserializer<?> findReferenceDeserializer(ReferenceType refType,
DeserializationConfig config, BeanDescription beanDesc,
@@ -148,9 +151,9 @@
throws JsonMappingException {
// 21-Oct-2015, tatu: Unlikely this will really get used (reference types need more
// work, simple registration probably not sufficient). But whatever.
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(refType.getRawClass()));
+ return _find(refType);
}
-
+
@Override
public JsonDeserializer<?> findMapDeserializer(MapType type,
DeserializationConfig config, BeanDescription beanDesc,
@@ -159,7 +162,7 @@
JsonDeserializer<?> elementDeserializer)
throws JsonMappingException
{
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(type.getRawClass()));
+ return _find(type);
}
@Override
@@ -170,6 +173,13 @@
JsonDeserializer<?> elementDeserializer)
throws JsonMappingException
{
- return (_classMappings == null) ? null : _classMappings.get(new ClassKey(type.getRawClass()));
+ return _find(type);
+ }
+
+ private final JsonDeserializer<?> _find(JavaType type) {
+ if (_classMappings == null) {
+ return null;
+ }
+ return _classMappings.get(new ClassKey(type.getRawClass()));
}
}
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 f6485b8..5de340e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
+++ b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.databind.module;
+import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -24,16 +25,26 @@
* override {@link #setupModule(SetupContext)} method, if they choose
* to do so they MUST call <code>super.setupModule(context);</code>
* to ensure that registration works as expected.
+ *<p>
+ * WARNING: when registering {@link JsonSerializer}s and {@link JsonDeserializer}s,
+ * only type erased {@code Class} is compared: this means that usually you should
+ * NOT use this implementation for registering structured types such as
+ * {@link java.util.Collection}s or {@link java.util.Map}s: this because parametric
+ * type information will not be considered and you may end up having "wrong" handler
+ * for your type.
+ * What you need to do, instead, is to implement {@link com.fasterxml.jackson.databind.deser.Deserializers}
+ * and/or {@link com.fasterxml.jackson.databind.ser.Serializers} callbacks to match full type
+ * signatures (with {@link JavaType}).
*/
public class SimpleModule
- extends Module
+ extends com.fasterxml.jackson.databind.Module
implements java.io.Serializable
{
private static final long serialVersionUID = 1L; // 2.5.0
protected final String _name;
protected final Version _version;
-
+
protected SimpleSerializers _serializers = null;
protected SimpleDeserializers _deserializers = null;
@@ -251,21 +262,39 @@
/*
/**********************************************************
- /* Configuration methods
+ /* Configuration methods, adding serializers
/**********************************************************
*/
-
+
+ /**
+ * Method for adding serializer to handle type that the serializer claims to handle
+ * (see {@link JsonSerializer#handledType()}).
+ *<p>
+ * WARNING! Type matching only uses type-erased {@code Class} and should NOT
+ * be used when registering serializers for generic types like
+ * {@link java.util.Collection} and {@link java.util.Map}.
+ */
public SimpleModule addSerializer(JsonSerializer<?> ser)
{
+ _checkNotNull(ser, "serializer");
if (_serializers == null) {
_serializers = new SimpleSerializers();
}
_serializers.addSerializer(ser);
return this;
}
-
+
+ /**
+ * Method for adding serializer to handle values of specific type.
+ *<p>
+ * WARNING! Type matching only uses type-erased {@code Class} and should NOT
+ * be used when registering serializers for generic types like
+ * {@link java.util.Collection} and {@link java.util.Map}.
+ */
public <T> SimpleModule addSerializer(Class<? extends T> type, JsonSerializer<T> ser)
{
+ _checkNotNull(type, "type to register serializer for");
+ _checkNotNull(ser, "serializer");
if (_serializers == null) {
_serializers = new SimpleSerializers();
}
@@ -275,15 +304,32 @@
public <T> SimpleModule addKeySerializer(Class<? extends T> type, JsonSerializer<T> ser)
{
+ _checkNotNull(type, "type to register key serializer for");
+ _checkNotNull(ser, "key serializer");
if (_keySerializers == null) {
_keySerializers = new SimpleSerializers();
}
_keySerializers.addSerializer(type, ser);
return this;
}
+
+ /*
+ /**********************************************************
+ /* Configuration methods, adding deserializers
+ /**********************************************************
+ */
+ /**
+ * Method for adding deserializer to handle specified type.
+ *<p>
+ * WARNING! Type matching only uses type-erased {@code Class} and should NOT
+ * be used when registering serializers for generic types like
+ * {@link java.util.Collection} and {@link java.util.Map}.
+ */
public <T> SimpleModule addDeserializer(Class<T> type, JsonDeserializer<? extends T> deser)
{
+ _checkNotNull(type, "type to register deserializer for");
+ _checkNotNull(deser, "deserializer");
if (_deserializers == null) {
_deserializers = new SimpleDeserializers();
}
@@ -293,6 +339,8 @@
public SimpleModule addKeyDeserializer(Class<?> type, KeyDeserializer deser)
{
+ _checkNotNull(type, "type to register key deserializer for");
+ _checkNotNull(deser, "key deserializer");
if (_keyDeserializers == null) {
_keyDeserializers = new SimpleKeyDeserializers();
}
@@ -300,6 +348,12 @@
return this;
}
+ /*
+ /**********************************************************
+ /* Configuration methods, type mapping
+ /**********************************************************
+ */
+
/**
* Lazily-constructed resolver used for storing mappings from
* abstract classes to more specific implementing classes
@@ -308,6 +362,8 @@
public <T> SimpleModule addAbstractTypeMapping(Class<T> superType,
Class<? extends T> subType)
{
+ _checkNotNull(superType, "abstract type to map");
+ _checkNotNull(subType, "concrete type to map to");
if (_abstractTypes == null) {
_abstractTypes = new SimpleAbstractTypeResolver();
}
@@ -317,22 +373,6 @@
}
/**
- * Method for registering {@link ValueInstantiator} to use when deserializing
- * instances of type <code>beanType</code>.
- *<p>
- * Instantiator is
- * registered when module is registered for <code>ObjectMapper</code>.
- */
- public SimpleModule addValueInstantiator(Class<?> beanType, ValueInstantiator inst)
- {
- if (_valueInstantiators == null) {
- _valueInstantiators = new SimpleValueInstantiators();
- }
- _valueInstantiators = _valueInstantiators.addValueInstantiator(beanType, inst);
- return this;
- }
-
- /**
* Method for adding set of subtypes to be registered with
* {@link ObjectMapper}
* this is an alternative to using annotations in super type to indicate subtypes.
@@ -340,9 +380,10 @@
public SimpleModule registerSubtypes(Class<?> ... subtypes)
{
if (_subtypes == null) {
- _subtypes = new LinkedHashSet<NamedType>(Math.max(16, subtypes.length));
+ _subtypes = new LinkedHashSet<>();
}
for (Class<?> subtype : subtypes) {
+ _checkNotNull(subtype, "subtype to register");
_subtypes.add(new NamedType(subtype));
}
return this;
@@ -356,15 +397,59 @@
public SimpleModule registerSubtypes(NamedType ... subtypes)
{
if (_subtypes == null) {
- _subtypes = new LinkedHashSet<NamedType>(Math.max(16, subtypes.length));
+ _subtypes = new LinkedHashSet<>();
}
for (NamedType subtype : subtypes) {
+ _checkNotNull(subtype, "subtype to register");
_subtypes.add(subtype);
}
return this;
}
+
+ /**
+ * Method for adding set of subtypes (along with type name to use) to be registered with
+ * {@link ObjectMapper}
+ * this is an alternative to using annotations in super type to indicate subtypes.
+ *
+ * @since 2.9
+ */
+ public SimpleModule registerSubtypes(Collection<Class<?>> subtypes)
+ {
+ if (_subtypes == null) {
+ _subtypes = new LinkedHashSet<>();
+ }
+ for (Class<?> subtype : subtypes) {
+ _checkNotNull(subtype, "subtype to register");
+ _subtypes.add(new NamedType(subtype));
+ }
+ return this;
+ }
+
+ /*
+ /**********************************************************
+ /* Configuration methods, add other handlers
+ /**********************************************************
+ */
/**
+ * Method for registering {@link ValueInstantiator} to use when deserializing
+ * instances of type <code>beanType</code>.
+ *<p>
+ * Instantiator is
+ * registered when module is registered for <code>ObjectMapper</code>.
+ */
+ public SimpleModule addValueInstantiator(Class<?> beanType, ValueInstantiator inst)
+ {
+ _checkNotNull(beanType, "class to register value instantiator for");
+ _checkNotNull(inst, "value instantiator");
+ if (_valueInstantiators == null) {
+ _valueInstantiators = new SimpleValueInstantiators();
+ }
+ _valueInstantiators = _valueInstantiators.addValueInstantiator(beanType, inst);
+ return this;
+ }
+
+ /**
* Method for specifying that annotations define by <code>mixinClass</code>
* should be "mixed in" with annotations that <code>targetType</code>
* has (as if they were directly included on it!).
@@ -374,13 +459,15 @@
*/
public SimpleModule setMixInAnnotation(Class<?> targetType, Class<?> mixinClass)
{
+ _checkNotNull(targetType, "target type");
+ _checkNotNull(mixinClass, "mixin class");
if (_mixins == null) {
_mixins = new HashMap<Class<?>, Class<?>>();
}
_mixins.put(targetType, mixinClass);
return this;
}
-
+
/*
/**********************************************************
/* Module impl
@@ -441,4 +528,21 @@
@Override
public Version version() { return _version; }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ protected void _checkNotNull(Object thingy, String type)
+ {
+ if (thingy == null) {
+ throw new IllegalArgumentException(String.format(
+ "Cannot pass `null` as %s", type));
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java
index ac4aa8a..2485d33 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java
@@ -1,14 +1,15 @@
package com.fasterxml.jackson.databind.node;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.util.RawValue;
import java.io.IOException;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@@ -86,6 +87,11 @@
return JsonNodeType.ARRAY;
}
+ @Override
+ public boolean isArray() {
+ return true;
+ }
+
@Override public JsonToken asToken() { return JsonToken.START_ARRAY; }
@Override
@@ -156,24 +162,21 @@
for (int i = 0; i < size; ++i) { // we'll typically have array list
// For now, assuming it's either BaseJsonNode, JsonSerializable
JsonNode n = c.get(i);
- if (n instanceof BaseJsonNode) {
- ((BaseJsonNode) n).serialize(f, provider);
- } else {
- ((JsonSerializable) n).serialize(f, provider);
- }
+ ((BaseJsonNode) n).serialize(f, provider);
}
f.writeEndArray();
}
@Override
- public void serializeWithType(JsonGenerator jg, SerializerProvider provider, TypeSerializer typeSer)
+ public void serializeWithType(JsonGenerator g, SerializerProvider provider, TypeSerializer typeSer)
throws IOException
{
- typeSer.writeTypePrefixForArray(this, jg);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(this, JsonToken.START_ARRAY));
for (JsonNode n : _children) {
- ((BaseJsonNode)n).serialize(jg, provider);
+ ((BaseJsonNode)n).serialize(g, provider);
}
- typeSer.writeTypeSuffixForArray(this, jg);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
/*
@@ -355,8 +358,8 @@
*/
/**
- * Method that will construct an ArrayNode and add it as a
- * field of this ObjectNode, replacing old value, if any.
+ * Method that will construct an ArrayNode and add it at the end
+ * of this array node.
*
* @return Newly constructed ArrayNode
*/
@@ -521,6 +524,20 @@
}
/**
+ * Method for adding specified number at the end of this array.
+ *
+ * @return This array node, to allow chaining
+ *
+ * @since 2.9
+ */
+ public ArrayNode add(BigInteger v) {
+ if (v == null) {
+ return addNull();
+ }
+ return _add(numberNode(v));
+ }
+
+ /**
* Method for adding specified String value at the end of this array.
*
* @return This array node, to allow chaining
@@ -729,6 +746,21 @@
}
/**
+ * Method that will insert specified numeric value
+ * at specified position in this array.
+ *
+ * @return This array node, to allow chaining
+ *
+ * @since 2.9
+ */
+ public ArrayNode insert(int index, BigInteger v) {
+ if (v == null) {
+ return insertNull(index);
+ }
+ return _insert(index, numberNode(v));
+ }
+
+ /**
* Method that will insert specified String
* at specified position in this array.
*
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/BooleanNode.java b/src/main/java/com/fasterxml/jackson/databind/node/BooleanNode.java
index fa8cdee..ac3b390 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/BooleanNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/BooleanNode.java
@@ -20,8 +20,12 @@
public final static BooleanNode FALSE = new BooleanNode(false);
private final boolean _value;
-
- private BooleanNode(boolean v) { _value = v; }
+
+ /**
+ *<p>
+ * NOTE: visibility raised to `protected` in 2.9.3 to allow custom subtypes.
+ */
+ protected BooleanNode(boolean v) { _value = v; }
public static BooleanNode getTrue() { return TRUE; }
public static BooleanNode getFalse() { return FALSE; }
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ContainerNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ContainerNode.java
index f2eb608..fcfb23a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/ContainerNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/ContainerNode.java
@@ -96,18 +96,16 @@
return _nodeFactory.numberNode(v);
}
- // was missing from 2.2 and before
- @Override
- public final NumericNode numberNode(BigInteger v) { return _nodeFactory.numberNode(v); }
-
@Override
public final NumericNode numberNode(float v) { return _nodeFactory.numberNode(v); }
@Override
public final NumericNode numberNode(double v) { return _nodeFactory.numberNode(v); }
- @Override
- public final NumericNode numberNode(BigDecimal v) { return (_nodeFactory.numberNode(v)); }
- // // Wrapper types, missing from 2.2 and before
+ @Override
+ public final ValueNode numberNode(BigInteger v) { return _nodeFactory.numberNode(v); }
+ @Override
+ public final ValueNode numberNode(BigDecimal v) { return (_nodeFactory.numberNode(v)); }
+
@Override
public final ValueNode numberNode(Byte v) { return _nodeFactory.numberNode(v); }
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/DoubleNode.java b/src/main/java/com/fasterxml/jackson/databind/node/DoubleNode.java
index f268b85..ae14bdf 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/DoubleNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/DoubleNode.java
@@ -74,7 +74,7 @@
@Override
public float floatValue() { return (float) _value; }
-
+
@Override
public double doubleValue() { return _value; }
@@ -91,11 +91,15 @@
return NumberOutput.toString(_value);
}
+ // @since 2.9
@Override
- public final void serialize(JsonGenerator jg, SerializerProvider provider)
- throws IOException, JsonProcessingException
- {
- jg.writeNumber(_value);
+ public boolean isNaN() {
+ return Double.isNaN(_value) || Double.isInfinite(_value);
+ }
+
+ @Override
+ public final void serialize(JsonGenerator g, SerializerProvider provider) throws IOException {
+ g.writeNumber(_value);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/FloatNode.java b/src/main/java/com/fasterxml/jackson/databind/node/FloatNode.java
index b4dc9fb..2d8a182 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/FloatNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/FloatNode.java
@@ -5,6 +5,7 @@
import java.math.BigInteger;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.io.NumberOutput;
import com.fasterxml.jackson.databind.SerializerProvider;
/**
@@ -74,7 +75,7 @@
@Override
public float floatValue() { return _value; }
-
+
@Override
public double doubleValue() { return _value; }
@@ -88,16 +89,18 @@
@Override
public String asText() {
- // As per [jackson-databind#707]
-// return NumberOutput.toString(_value);
- // TODO: in 2.7, call `NumberOutput.toString (added in 2.6); not yet for backwards compat
- return Float.toString(_value);
+ return NumberOutput.toString(_value);
+ }
+
+ // @since 2.9
+ @Override
+ public boolean isNaN() {
+ return Float.isNaN(_value) || Float.isInfinite(_value);
}
@Override
- public final void serialize(JsonGenerator jg, SerializerProvider provider) throws IOException
- {
- jg.writeNumber(_value);
+ public final void serialize(JsonGenerator g, SerializerProvider provider) throws IOException {
+ g.writeNumber(_value);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/JsonNodeFactory.java b/src/main/java/com/fasterxml/jackson/databind/node/JsonNodeFactory.java
index 70ec2f7..e933bc8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/JsonNodeFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/JsonNodeFactory.java
@@ -183,11 +183,11 @@
* {@link NumericNode}, but just {@link ValueNode}.
*/
@Override
- public ValueNode numberNode(Long value) {
- if (value == null) {
+ public ValueNode numberNode(Long v) {
+ if (v == null) {
return nullNode();
}
- return LongNode.valueOf(value.longValue());
+ return LongNode.valueOf(v.longValue());
}
/**
@@ -195,7 +195,12 @@
* that expresses given unlimited range integer value
*/
@Override
- public NumericNode numberNode(BigInteger v) { return BigIntegerNode.valueOf(v); }
+ public ValueNode numberNode(BigInteger v) {
+ if (v == null) {
+ return nullNode();
+ }
+ return BigIntegerNode.valueOf(v);
+ }
/**
* Factory method for getting an instance of JSON numeric value
@@ -244,8 +249,12 @@
* @see #JsonNodeFactory(boolean)
*/
@Override
- public NumericNode numberNode(BigDecimal v)
+ public ValueNode numberNode(BigDecimal v)
{
+ if (v == null) {
+ return nullNode();
+ }
+
/*
* If the user wants the exact representation of this big decimal,
* return the value directly
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java b/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java
index 4e18fff..65509fe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java
@@ -24,7 +24,16 @@
{
private final static MissingNode instance = new MissingNode();
- private MissingNode() { }
+ /**
+ *<p>
+ * NOTE: visibility raised to `protected` in 2.9.3 to allow custom subtypes.
+ */
+ protected MissingNode() { }
+
+ @Override
+ public boolean isMissingNode() {
+ return true;
+ }
// Immutable: no need to copy
@SuppressWarnings("unchecked")
@@ -61,7 +70,7 @@
/* Nothing to output... should we signal an error tho?
* Chances are, this is an erroneous call. For now, let's
* not do that; serialize as explicit null. Why? Because we
- * can not just omit a value as JSON Object field name may have
+ * cannot just omit a value as JSON Object field name may have
* been written out.
*/
jg.writeNull();
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/NullNode.java b/src/main/java/com/fasterxml/jackson/databind/node/NullNode.java
index 70a01ea..1a22243 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/NullNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/NullNode.java
@@ -17,7 +17,11 @@
public final static NullNode instance = new NullNode();
- private NullNode() { }
+ /**
+ *<p>
+ * NOTE: visibility raised to `protected` in 2.9.3 to allow custom subtypes.
+ */
+ protected NullNode() { }
public static NullNode getInstance() { return instance; }
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/NumericNode.java b/src/main/java/com/fasterxml/jackson/databind/node/NumericNode.java
index 3a80971..a70a6b1 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/NumericNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/NumericNode.java
@@ -33,13 +33,13 @@
@Override public abstract boolean canConvertToInt();
@Override public abstract boolean canConvertToLong();
-
+
/*
/**********************************************************
/* General type coercions
/**********************************************************
*/
-
+
@Override
public abstract String asText();
@@ -47,6 +47,7 @@
public final int asInt() {
return intValue();
}
+
@Override
public final int asInt(int defaultValue) {
return intValue();
@@ -56,17 +57,37 @@
public final long asLong() {
return longValue();
}
+
@Override
public final long asLong(long defaultValue) {
return longValue();
}
-
+
@Override
public final double asDouble() {
return doubleValue();
}
+
@Override
public final double asDouble(double defaultValue) {
return doubleValue();
}
+
+ /*
+ /**********************************************************
+ /* Other
+ /**********************************************************
+ */
+
+ /**
+ * Convenience method for checking whether this node is a
+ * {@link FloatNode} or {@link DoubleNode} that contains
+ * "not-a-number" (NaN) value.
+ *
+ * @since 2.9
+ */
+ public boolean isNaN() {
+ return false;
+ }
+
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
index 387943e..c80d3ad 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
@@ -1,12 +1,14 @@
package com.fasterxml.jackson.databind.node;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.util.RawValue;
import java.io.IOException;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.*;
/**
@@ -76,6 +78,11 @@
return JsonNodeType.OBJECT;
}
+ @Override
+ public final boolean isObject() {
+ return true;
+ }
+
@Override public JsonToken asToken() { return JsonToken.START_OBJECT; }
@Override
@@ -314,7 +321,9 @@
@SuppressWarnings("deprecation")
boolean trimEmptyArray = (provider != null) &&
!provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
- typeSer.writeTypePrefixForObject(this, g);
+
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(this, JsonToken.START_OBJECT));
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
BaseJsonNode value = (BaseJsonNode) en.getValue();
@@ -328,7 +337,7 @@
g.writeFieldName(en.getKey());
value.serialize(g, provider);
}
- typeSer.writeTypeSuffixForObject(this, g);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
/*
@@ -761,6 +770,18 @@
}
/**
+ * Method for setting value of a field to specified numeric value.
+ *
+ * @return This node (to allow chaining)
+ *
+ * @since 2.9
+ */
+ public ObjectNode put(String fieldName, BigInteger v) {
+ return _put(fieldName, (v == null) ? nullNode()
+ : numberNode(v));
+ }
+
+ /**
* Method for setting value of a field to specified String value.
*
* @return This node (to allow chaining)
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java b/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java
index df81fdb..06c315c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java
@@ -102,14 +102,16 @@
*/
@Override
- public final void serialize(JsonGenerator gen, SerializerProvider serializers) throws IOException
+ public final void serialize(JsonGenerator gen, SerializerProvider ctxt) throws IOException
{
if (_value == null) {
- serializers.defaultSerializeNull(gen);
+ ctxt.defaultSerializeNull(gen);
} else if (_value instanceof JsonSerializable) {
- ((JsonSerializable) _value).serialize(gen, serializers);
+ ((JsonSerializable) _value).serialize(gen, ctxt);
} else {
- gen.writeObject(_value);
+ // 25-May-2018, tatu: [databind#1991] do not call via generator but through context;
+ // this to preserve contextual information
+ ctxt.defaultSerializeValue(_value, gen);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java b/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java
index a692776..26a7f91 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java
@@ -6,7 +6,9 @@
import com.fasterxml.jackson.core.io.CharTypes;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
+
import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
/**
* Value node that contains a text value.
@@ -60,93 +62,16 @@
@SuppressWarnings("resource")
public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
{
- ByteArrayBuilder builder = new ByteArrayBuilder(100);
- final String str = _value;
- int ptr = 0;
- int len = str.length();
-
- main_loop:
- while (ptr < len) {
- // first, we'll skip preceding white space, if any
- char ch;
- do {
- ch = str.charAt(ptr++);
- if (ptr >= len) {
- break main_loop;
- }
- } while (ch <= ' ');
- int bits = b64variant.decodeBase64Char(ch);
- if (bits < 0) {
- _reportInvalidBase64(b64variant, ch, 0);
- }
- int decodedData = bits;
- // then second base64 char; can't get padding yet, nor ws
- if (ptr >= len) {
- _reportBase64EOF();
- }
- ch = str.charAt(ptr++);
- bits = b64variant.decodeBase64Char(ch);
- if (bits < 0) {
- _reportInvalidBase64(b64variant, ch, 1);
- }
- decodedData = (decodedData << 6) | bits;
- // third base64 char; can be padding, but not ws
- if (ptr >= len) {
- // but as per [JACKSON-631] can be end-of-input, iff not using padding
- if (!b64variant.usesPadding()) {
- // Got 12 bits, only need 8, need to shift
- decodedData >>= 4;
- builder.append(decodedData);
- break;
- }
- _reportBase64EOF();
- }
- ch = str.charAt(ptr++);
- bits = b64variant.decodeBase64Char(ch);
-
- // First branch: can get padding (-> 1 byte)
- if (bits < 0) {
- if (bits != Base64Variant.BASE64_VALUE_PADDING) {
- _reportInvalidBase64(b64variant, ch, 2);
- }
- // Ok, must get padding
- if (ptr >= len) {
- _reportBase64EOF();
- }
- ch = str.charAt(ptr++);
- if (!b64variant.usesPaddingChar(ch)) {
- _reportInvalidBase64(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
- }
- // Got 12 bits, only need 8, need to shift
- decodedData >>= 4;
- builder.append(decodedData);
- continue;
- }
- // Nope, 2 or 3 bytes
- decodedData = (decodedData << 6) | bits;
- // fourth and last base64 char; can be padding, but not ws
- if (ptr >= len) {
- // but as per [JACKSON-631] can be end-of-input, iff not using padding
- if (!b64variant.usesPadding()) {
- decodedData >>= 2;
- builder.appendTwoBytes(decodedData);
- break;
- }
- _reportBase64EOF();
- }
- ch = str.charAt(ptr++);
- bits = b64variant.decodeBase64Char(ch);
- if (bits < 0) {
- if (bits != Base64Variant.BASE64_VALUE_PADDING) {
- _reportInvalidBase64(b64variant, ch, 3);
- }
- decodedData >>= 2;
- builder.appendTwoBytes(decodedData);
- } else {
- // otherwise, our triple is now complete
- decodedData = (decodedData << 6) | bits;
- builder.appendThreeBytes(decodedData);
- }
+ final String str = _value.trim();
+ ByteArrayBuilder builder = new ByteArrayBuilder(4 + ((str.length() * 3) << 2));
+ try {
+ b64variant.decode(str, builder);
+ } catch (IllegalArgumentException e) {
+ throw InvalidFormatException.from(null,
+ String.format(
+"Cannot access contents of TextNode as binary due to broken Base64 encoding: %s",
+e.getMessage()),
+ str, byte[].class);
}
return builder.toByteArray();
}
@@ -155,7 +80,7 @@
public byte[] binaryValue() throws IOException {
return getBinaryValue(Base64Variants.getDefaultVariant());
}
-
+
/*
/**********************************************************
/* General type coercions
@@ -258,44 +183,4 @@
CharTypes.appendQuoted(sb, content);
sb.append('"');
}
-
- /*
- /**********************************************************
- /* Helper methods
- /**********************************************************
- */
-
- protected void _reportInvalidBase64(Base64Variant b64variant, char ch, int bindex)
- throws JsonParseException
- {
- _reportInvalidBase64(b64variant, ch, bindex, null);
- }
-
- /**
- * @param bindex Relative index within base64 character unit; between 0
- * and 3 (as unit has exactly 4 characters)
- */
- protected void _reportInvalidBase64(Base64Variant b64variant, char ch, int bindex, String msg)
- throws JsonParseException
- {
- String base;
- if (ch <= ' ') {
- base = "Illegal white space character (code 0x"+Integer.toHexString(ch)+") as character #"+(bindex+1)+" of 4-char base64 unit: can only used between units";
- } else if (b64variant.usesPaddingChar(ch)) {
- base = "Unexpected padding character ('"+b64variant.getPaddingChar()+"') as character #"+(bindex+1)+" of 4-char base64 unit: padding only legal as 3rd or 4th character";
- } else if (!Character.isDefined(ch) || Character.isISOControl(ch)) {
- // Not sure if we can really get here... ? (most illegal xml chars are caught at lower level)
- base = "Illegal character (code 0x"+Integer.toHexString(ch)+") in base64 content";
- } else {
- base = "Illegal character '"+ch+"' (code 0x"+Integer.toHexString(ch)+") in base64 content";
- }
- if (msg != null) {
- base = base + ": " + msg;
- }
- throw new JsonParseException(null, base, JsonLocation.NA);
- }
-
- protected void _reportBase64EOF() throws JsonParseException {
- throw new JsonParseException(null, "Unexpected end-of-String when base64 content");
- }
}
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 9ede6bd..40bf3d3 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/TreeTraversingParser.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/TreeTraversingParser.java
@@ -334,6 +334,17 @@
return null;
}
+ @Override
+ public boolean isNaN() {
+ if (!_closed) {
+ JsonNode n = currentNode();
+ if (n instanceof NumericNode) {
+ return ((NumericNode) n).isNaN();
+ }
+ }
+ return false;
+ }
+
/*
/**********************************************************
/* Public API, typed binary (base64) access
@@ -346,19 +357,13 @@
{
// Multiple possibilities...
JsonNode n = currentNode();
- if (n != null) { // binary node?
- byte[] data = n.binaryValue();
- // (or TextNode, which can also convert automatically!)
- if (data != null) {
- return data;
+ if (n != null) {
+ // [databind#2096]: although `binaryValue()` works for real binary node
+ // and embedded "POJO" node, coercion from TextNode may require variant, so:
+ if (n instanceof TextNode) {
+ return ((TextNode) n).getBinaryValue(b64variant);
}
- // Or maybe byte[] as POJO?
- if (n.isPojo()) {
- Object ob = ((POJONode) n).getPojo();
- if (ob instanceof byte[]) {
- return (byte[]) ob;
- }
- }
+ return n.binaryValue();
}
// otherwise return null to mark we have no binary content
return null;
@@ -396,7 +401,7 @@
JsonNode n = currentNode();
if (n == null || !n.isNumber()) {
JsonToken t = (n == null) ? null : n.asToken();
- throw _constructError("Current token ("+t+") not numeric, can not use numeric value accessors");
+ throw _constructError("Current token ("+t+") not numeric, cannot use numeric value accessors");
}
return n;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java
index 86eb129..363d59e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java
@@ -4,6 +4,7 @@
import java.util.List;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -24,7 +25,7 @@
// (base class checks for direct match)
return MissingNode.getInstance();
}
-
+
/**
* All current value nodes are immutable, so we can just return
* them as is.
@@ -36,13 +37,14 @@
@Override public abstract JsonToken asToken();
@Override
- public void serializeWithType(JsonGenerator jg, SerializerProvider provider,
+ public void serializeWithType(JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
- throws IOException, JsonProcessingException
+ throws IOException
{
- typeSer.writeTypePrefixForScalar(this, jg);
- serialize(jg, provider);
- typeSer.writeTypeSuffixForScalar(this, jg);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(this, asToken()));
+ serialize(g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java
index bedf67e..045adea 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java
@@ -38,7 +38,7 @@
}
/**
- * @since 0.8.3
+ * @since 2.8.3
*/
public void fixAccess(SerializationConfig config) {
_accessor.fixAccess(
@@ -53,8 +53,9 @@
return;
}
if (!(value instanceof Map<?,?>)) {
- provider.reportMappingProblem("Value returned by 'any-getter' %s() not java.util.Map but %s",
- _accessor.getName(), value.getClass().getName());
+ provider.reportBadDefinition(_property.getType(), String.format(
+ "Value returned by 'any-getter' %s() not java.util.Map but %s",
+ _accessor.getName(), value.getClass().getName()));
}
// 23-Feb-2015, tatu: Nasty, but has to do (for now)
if (_mapSerializer != null) {
@@ -69,25 +70,27 @@
*/
public void getAndFilter(Object bean, JsonGenerator gen, SerializerProvider provider,
PropertyFilter filter)
- throws Exception
+ throws Exception
{
Object value = _accessor.getValue(bean);
if (value == null) {
return;
}
if (!(value instanceof Map<?,?>)) {
- provider.reportMappingProblem("Value returned by 'any-getter' (%s()) not java.util.Map but %s",
- _accessor.getName(), value.getClass().getName());
+ provider.reportBadDefinition(_property.getType(),
+ String.format("Value returned by 'any-getter' (%s()) not java.util.Map but %s",
+ _accessor.getName(), value.getClass().getName()));
}
// 19-Oct-2014, tatu: Should we try to support @JsonInclude options here?
if (_mapSerializer != null) {
- _mapSerializer.serializeFilteredFields((Map<?,?>) value, gen, provider, filter, null);
+ _mapSerializer.serializeFilteredAnyProperties(provider, gen, bean,(Map<?,?>) value,
+ filter, null);
return;
}
// ... not sure how custom handler would do it
_serializer.serialize(value, gen, provider);
}
-
+
// Note: NOT part of ResolvableSerializer...
@SuppressWarnings("unchecked")
public void resolve(SerializerProvider provider) throws JsonMappingException
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 29c4a49..a3d422f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java
@@ -1,12 +1,12 @@
package com.fasterxml.jackson.databind.ser;
-import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -57,7 +57,7 @@
* not instances
*/
protected final static HashMap<String, Class<? extends JsonSerializer<?>>> _concreteLazy;
-
+
static {
HashMap<String, Class<? extends JsonSerializer<?>>> concLazy
= new HashMap<String, Class<? extends JsonSerializer<?>>>();
@@ -94,12 +94,10 @@
Object value = en.getValue();
if (value instanceof JsonSerializer<?>) {
concrete.put(en.getKey().getName(), (JsonSerializer<?>) value);
- } else if (value instanceof Class<?>) {
+ } else {
@SuppressWarnings("unchecked")
Class<? extends JsonSerializer<?>> cls = (Class<? extends JsonSerializer<?>>) value;
concLazy.put(en.getKey().getName(), cls);
- } else { // should never happen, but:
- throw new IllegalStateException("Internal error: unrecognized value of type "+en.getClass().getName());
}
}
@@ -226,14 +224,14 @@
// As per [databind#47], also need to support @JsonValue
if (ser == null) {
beanDesc = config.introspect(keyType);
- AnnotatedMethod am = beanDesc.findJsonValueMethod();
+ AnnotatedMember am = beanDesc.findJsonValueAccessor();
if (am != null) {
- final Class<?> rawType = am.getRawReturnType();
+ final Class<?> rawType = am.getRawType();
JsonSerializer<?> delegate = StdKeySerializers.getStdKeySerializer(config,
rawType, true);
- Method m = am.getAnnotated();
if (config.canOverrideAccessModifiers()) {
- ClassUtil.checkAndFixAccess(m, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
+ ClassUtil.checkAndFixAccess(am.getMember(),
+ config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
ser = new JsonValueSerializer(am, delegate);
} else {
@@ -310,12 +308,10 @@
if (ser == null) {
Class<? extends JsonSerializer<?>> serClass = _concreteLazy.get(clsName);
if (serClass != null) {
- try {
- return serClass.newInstance();
- } catch (Exception e) {
- throw new IllegalStateException("Failed to instantiate standard serializer (of type "+serClass.getName()+"): "
- +e.getMessage(), e);
- }
+ // 07-Jan-2017, tatu: Should never fail (since we control constructors),
+ // but if it does will throw `IllegalArgumentException` with description,
+ // which we could catch, re-title.
+ return ClassUtil.createInstance(serClass, false);
}
}
return ser;
@@ -347,14 +343,14 @@
return SerializableSerializer.instance;
}
// Second: @JsonValue for any type
- AnnotatedMethod valueMethod = beanDesc.findJsonValueMethod();
- if (valueMethod != null) {
- Method m = valueMethod.getAnnotated();
+ AnnotatedMember valueAccessor = beanDesc.findJsonValueAccessor();
+ if (valueAccessor != null) {
if (prov.canOverrideAccessModifiers()) {
- ClassUtil.checkAndFixAccess(m, prov.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
+ ClassUtil.checkAndFixAccess(valueAccessor.getMember(),
+ prov.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
- JsonSerializer<Object> ser = findSerializerFromAnnotation(prov, valueMethod);
- return new JsonValueSerializer(valueMethod, ser);
+ JsonSerializer<Object> ser = findSerializerFromAnnotation(prov, valueAccessor);
+ return new JsonValueSerializer(valueAccessor, ser);
}
// No well-known annotations...
return null;
@@ -393,7 +389,7 @@
// 28-Apr-2015, tatu: TypeFactory does it all for us already so
JavaType kt = mapEntryType.containedTypeOrUnknown(0);
JavaType vt = mapEntryType.containedTypeOrUnknown(1);
- return buildMapEntrySerializer(prov.getConfig(), type, beanDesc, staticTyping, kt, vt);
+ return buildMapEntrySerializer(prov, type, beanDesc, staticTyping, kt, vt);
}
if (ByteBuffer.class.isAssignableFrom(raw)) {
return new ByteBufferSerializer();
@@ -544,7 +540,7 @@
* leave it as is; no clean way to make it work.
*/
if (!staticTyping && type.useStaticType()) {
- if (!type.isContainerType() || type.getContentType().getRawClass() != Object.class) {
+ if (!type.isContainerType() || !type.getContentType().isJavaLangObject()) {
staticTyping = true;
}
}
@@ -554,7 +550,7 @@
TypeSerializer elementTypeSerializer = createTypeSerializer(config,
elementType);
- // if elements have type serializer, can not force static typing:
+ // if elements have type serializer, cannot force static typing:
if (elementTypeSerializer != null) {
staticTyping = false;
}
@@ -660,7 +656,7 @@
// 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 != null) && format.getShape() == JsonFormat.Shape.OBJECT) {
return null;
}
Class<?> raw = type.getRawClass();
@@ -677,7 +673,7 @@
if (isIndexedList(raw)) {
if (elementRaw == String.class) {
// [JACKSON-829] Must NOT use if we have custom serializer
- if (elementValueSerializer == null || ClassUtil.isJacksonStdImpl(elementValueSerializer)) {
+ if (ClassUtil.isJacksonStdImpl(elementValueSerializer)) {
ser = IndexedStringListSerializer.instance;
}
} else {
@@ -686,7 +682,7 @@
}
} else if (elementRaw == String.class) {
// [JACKSON-829] Must NOT use if we have custom serializer
- if (elementValueSerializer == null || ClassUtil.isJacksonStdImpl(elementValueSerializer)) {
+ if (ClassUtil.isJacksonStdImpl(elementValueSerializer)) {
ser = StringCollectionSerializer.instance;
}
}
@@ -721,6 +717,7 @@
boolean staticTyping, TypeSerializer vts, JsonSerializer<Object> valueSerializer) {
return new IndexedListSerializer(elemType, staticTyping, vts, valueSerializer);
}
+
public ContainerSerializer<?> buildCollectionSerializer(JavaType elemType,
boolean staticTyping, TypeSerializer vts, JsonSerializer<Object> valueSerializer) {
return new CollectionSerializer(elemType, staticTyping, vts, valueSerializer);
@@ -735,7 +732,7 @@
/* Factory methods, for Maps
/**********************************************************
*/
-
+
/**
* Helper method that handles configuration details when constructing serializers for
* {@link java.util.Map} types.
@@ -746,7 +743,13 @@
TypeSerializer elementTypeSerializer, JsonSerializer<Object> elementValueSerializer)
throws JsonMappingException
{
- final SerializationConfig config = prov.getConfig();
+ // [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) {
+ return null;
+ }
+
JsonSerializer<?> ser = null;
// Order of lookups:
@@ -754,6 +757,7 @@
// 2. Annotations (@JsonValue, @JsonDeserialize)
// 3. Defaults
+ final SerializationConfig config = prov.getConfig();
for (Serializers serializers : customSerializers()) { // (1) Custom
ser = serializers.findMapSerializer(config, type, beanDesc,
keySerializer, elementTypeSerializer, elementValueSerializer);
@@ -774,12 +778,7 @@
MapSerializer mapSer = MapSerializer.construct(ignored,
type, staticTyping, elementTypeSerializer,
keySerializer, elementValueSerializer, filterId);
- Object suppressableValue = findSuppressableContentValue(config,
- type.getContentType(), beanDesc);
- if (suppressableValue != null) {
- mapSer = mapSer.withContentInclusion(suppressableValue);
- }
- ser = mapSer;
+ ser = _checkMapContentInclusion(prov, beanDesc, mapSer);
}
}
// [databind#120]: Allow post-processing
@@ -792,44 +791,176 @@
}
/**
- *<p>
- * NOTE: although return type is left opaque, it really needs to be
- * <code>JsonInclude.Include</code> for things to work as expected.
+ * Helper method that does figures out content inclusion value to use, if any,
+ * and construct re-configured {@link MapSerializer} appropriately.
*
- * @since 2.5
+ * @since 2.9
*/
- protected Object findSuppressableContentValue(SerializationConfig config,
- JavaType contentType, BeanDescription beanDesc)
+ @SuppressWarnings("deprecation")
+ protected MapSerializer _checkMapContentInclusion(SerializerProvider prov,
+ BeanDescription beanDesc, MapSerializer mapSer)
throws JsonMappingException
{
- /* 16-Apr-2016, tatu: Should this consider possible property-config overrides?
- * Quite possibly yes, but would need to carefully check that content type being
- * used is appropriate.
- */
- JsonInclude.Value inclV = beanDesc.findPropertyInclusion(config.getDefaultPropertyInclusion());
+ final JavaType contentType = mapSer.getContentType();
+ JsonInclude.Value inclV = _findInclusionWithContent(prov, beanDesc,
+ contentType, Map.class);
- if (inclV == null) {
- return null;
+ // Need to support global legacy setting, for now:
+ JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
+ if (incl == JsonInclude.Include.USE_DEFAULTS
+ || incl == JsonInclude.Include.ALWAYS) {
+ if (!prov.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) {
+ return mapSer.withContentInclusion(null, true);
+ }
+ return mapSer;
}
- JsonInclude.Include incl = inclV.getContentInclusion();
+
+ // NOTE: mostly copied from `PropertyBuilder`; would be nice to refactor
+ // but code is not identical nor are these types related
+ Object valueToSuppress;
+ boolean suppressNulls = true; // almost always, but possibly not with CUSTOM
+
switch (incl) {
- case USE_DEFAULTS: // means "dunno"
- return null;
case NON_DEFAULT:
- // 19-Oct-2014, tatu: Not sure what this'd mean; so take it to mean "NON_EMPTY"...
- // 11-Nov-2015, tatu: With 2.6, we did indeed revert to "NON_EMPTY", but that did
- // not go well, so with 2.7, we'll do this instead...
- // But not 100% sure if we ought to call new `JsonSerializer.findDefaultValue()`;
- // to do that, would need to locate said serializer
-// incl = JsonInclude.Include.NON_EMPTY;
+ valueToSuppress = BeanUtil.getDefaultValue(contentType);
+ if (valueToSuppress != null) {
+ if (valueToSuppress.getClass().isArray()) {
+ valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
+ }
+ }
break;
- default:
- // all other modes actually good as is, unless we'll find better ways
+ case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals
+ // and for referential types, also "empty", which in their case means "absent"
+ valueToSuppress = contentType.isReferenceType()
+ ? MapSerializer.MARKER_FOR_EMPTY : null;
+ break;
+ case NON_EMPTY:
+ valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
+ break;
+ case CUSTOM: // new with 2.9
+ valueToSuppress = prov.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
+ case NON_NULL:
+ default: // should not matter but...
+ valueToSuppress = null;
break;
}
- return incl;
+ return mapSer.withContentInclusion(valueToSuppress, suppressNulls);
}
+ /**
+ * @since 2.9
+ */
+ protected JsonSerializer<?> buildMapEntrySerializer(SerializerProvider prov,
+ JavaType type, BeanDescription beanDesc, boolean staticTyping,
+ JavaType keyType, JavaType valueType)
+ throws JsonMappingException
+ {
+ // [databind#865]: Allow serialization "as POJO" -- note: to undo, declare
+ // serialization as `Shape.NATURAL` instead; that's JSON Object too.
+ JsonFormat.Value formatOverride = prov.getDefaultPropertyFormat(Map.Entry.class);
+ JsonFormat.Value formatFromAnnotation = beanDesc.findExpectedFormat(null);
+ JsonFormat.Value format = JsonFormat.Value.merge(formatFromAnnotation, formatOverride);
+ if (format.getShape() == JsonFormat.Shape.OBJECT) {
+ return null;
+ }
+ MapEntrySerializer ser = new MapEntrySerializer(valueType, keyType, valueType,
+ staticTyping, createTypeSerializer(prov.getConfig(), valueType), null);
+
+ final JavaType contentType = ser.getContentType();
+ JsonInclude.Value inclV = _findInclusionWithContent(prov, beanDesc,
+ contentType, Map.Entry.class);
+
+ // Need to support global legacy setting, for now:
+ JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
+ if (incl == JsonInclude.Include.USE_DEFAULTS
+ || incl == JsonInclude.Include.ALWAYS) {
+ return ser;
+ }
+
+ // NOTE: mostly copied from `PropertyBuilder`; would be nice to refactor
+ // but code is not identical nor are these types related
+ Object valueToSuppress;
+ boolean suppressNulls = true; // almost always, but possibly not with CUSTOM
+
+ switch (incl) {
+ case NON_DEFAULT:
+ valueToSuppress = BeanUtil.getDefaultValue(contentType);
+ if (valueToSuppress != null) {
+ if (valueToSuppress.getClass().isArray()) {
+ valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
+ }
+ }
+ break;
+ case NON_ABSENT:
+ valueToSuppress = contentType.isReferenceType()
+ ? MapSerializer.MARKER_FOR_EMPTY : null;
+ break;
+ case NON_EMPTY:
+ valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
+ break;
+ case CUSTOM:
+ valueToSuppress = prov.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
+ case NON_NULL:
+ default: // should not matter but...
+ valueToSuppress = null;
+ break;
+ }
+ return ser.withContentInclusion(valueToSuppress, suppressNulls);
+ }
+
+ /**
+ * Helper method used for finding inclusion definitions for structured
+ * container types like <code>Map</code>s and referential types
+ * (like <code>AtomicReference</code>).
+ *
+ * @param contentType Declared full content type of container
+ * @param configType Raw base type under which `configOverride`, if any, needs to be defined
+ */
+ protected JsonInclude.Value _findInclusionWithContent(SerializerProvider prov,
+ BeanDescription beanDesc,
+ JavaType contentType, Class<?> configType)
+ throws JsonMappingException
+ {
+ final SerializationConfig config = prov.getConfig();
+
+ // Defaulting gets complicated because we might have two distinct
+ // axis to consider: Container type itself , and then value (content) type.
+ // Start with Container-defaults, then use more-specific value override, if any.
+
+ // Start by getting global setting, overridden by Map-type-override
+ JsonInclude.Value inclV = beanDesc.findPropertyInclusion(config.getDefaultPropertyInclusion());
+ inclV = config.getDefaultPropertyInclusion(configType, inclV);
+
+ // and then merge content-type overrides, if any. But note that there's
+ // content-to-value inclusion shift we have to do
+ JsonInclude.Value valueIncl = config.getDefaultPropertyInclusion(contentType.getRawClass(), null);
+
+ if (valueIncl != null) {
+ switch (valueIncl.getValueInclusion()) {
+ case USE_DEFAULTS:
+ break;
+ case CUSTOM:
+ inclV = inclV.withContentFilter(valueIncl.getContentFilter());
+ break;
+ default:
+ inclV = inclV.withContentInclusion(valueIncl.getValueInclusion());
+ }
+ }
+ return inclV;
+ }
+
/*
/**********************************************************
/* Factory methods, for Arrays
@@ -847,7 +978,7 @@
throws JsonMappingException
{
// 25-Jun-2015, tatu: Note that unlike with Collection(Like) and Map(Like) types, array
- // types can not be annotated (in theory I guess we could have mix-ins but... ?)
+ // types cannot be annotated (in theory I guess we could have mix-ins but... ?)
// so we need not do primary annotation lookup here.
// So all we need is (1) Custom, (2) Default array serializers
SerializationConfig config = prov.getConfig();
@@ -888,6 +1019,96 @@
/*
/**********************************************************
+ /* Factory methods for Reference types
+ /* (demoted from BeanSF down here in 2.9)
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.7
+ */
+ public JsonSerializer<?> findReferenceSerializer(SerializerProvider prov, ReferenceType refType,
+ BeanDescription beanDesc, boolean staticTyping)
+ throws JsonMappingException
+ {
+ JavaType contentType = refType.getContentType();
+ TypeSerializer contentTypeSerializer = contentType.getTypeHandler();
+ final SerializationConfig config = prov.getConfig();
+ if (contentTypeSerializer == null) {
+ contentTypeSerializer = createTypeSerializer(config, contentType);
+ }
+ JsonSerializer<Object> contentSerializer = contentType.getValueHandler();
+ for (Serializers serializers : customSerializers()) {
+ JsonSerializer<?> ser = serializers.findReferenceSerializer(config, refType, beanDesc,
+ contentTypeSerializer, contentSerializer);
+ if (ser != null) {
+ return ser;
+ }
+ }
+ if (refType.isTypeOrSubTypeOf(AtomicReference.class)) {
+ return buildAtomicReferenceSerializer(prov, refType, beanDesc, staticTyping,
+ contentTypeSerializer, contentSerializer);
+ }
+ return null;
+ }
+
+ protected JsonSerializer<?> buildAtomicReferenceSerializer(SerializerProvider prov,
+ ReferenceType refType, BeanDescription beanDesc, boolean staticTyping,
+ TypeSerializer contentTypeSerializer, JsonSerializer<Object> contentSerializer)
+ throws JsonMappingException
+ {
+ final JavaType contentType = refType.getReferencedType();
+ JsonInclude.Value inclV = _findInclusionWithContent(prov, beanDesc,
+ contentType, AtomicReference.class);
+
+ // Need to support global legacy setting, for now:
+ JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
+ Object valueToSuppress;
+ boolean suppressNulls;
+
+ if (incl == JsonInclude.Include.USE_DEFAULTS
+ || incl == JsonInclude.Include.ALWAYS) {
+ valueToSuppress = null;
+ suppressNulls = false;
+ } else {
+ suppressNulls = true;
+ switch (incl) {
+ case NON_DEFAULT:
+ valueToSuppress = BeanUtil.getDefaultValue(contentType);
+ if (valueToSuppress != null) {
+ if (valueToSuppress.getClass().isArray()) {
+ valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
+ }
+ }
+ break;
+ case NON_ABSENT:
+ valueToSuppress = contentType.isReferenceType()
+ ? MapSerializer.MARKER_FOR_EMPTY : null;
+ break;
+ case NON_EMPTY:
+ valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
+ break;
+ case CUSTOM:
+ valueToSuppress = prov.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
+ case NON_NULL:
+ default: // should not matter but...
+ valueToSuppress = null;
+ break;
+ }
+ }
+ AtomicReferenceSerializer ser = new AtomicReferenceSerializer(refType, staticTyping,
+ contentTypeSerializer, contentSerializer);
+ return ser.withContentInclusion(valueToSuppress, suppressNulls);
+ }
+
+ /*
+ /**********************************************************
/* Factory methods, for non-container types
/**********************************************************
*/
@@ -914,18 +1135,6 @@
return new IterableSerializer(valueType, staticTyping, createTypeSerializer(config, valueType));
}
- /**
- * @since 2.5
- */
- protected JsonSerializer<?> buildMapEntrySerializer(SerializationConfig config,
- JavaType type, BeanDescription beanDesc, boolean staticTyping,
- JavaType keyType, JavaType valueType)
- throws JsonMappingException
- {
- return new MapEntrySerializer(valueType, keyType, valueType,
- staticTyping, createTypeSerializer(config, valueType), null);
- }
-
protected JsonSerializer<?> buildEnumSerializer(SerializationConfig config,
JavaType type, BeanDescription beanDesc)
throws JsonMappingException
@@ -1013,7 +1222,7 @@
protected boolean usesStaticTyping(SerializationConfig config,
BeanDescription beanDesc, TypeSerializer typeSer)
{
- /* 16-Aug-2010, tatu: If there is a (value) type serializer, we can not force
+ /* 16-Aug-2010, tatu: If there is a (value) type serializer, we cannot force
* static typing; that would make it impossible to handle expected subtypes
*/
if (typeSer != null) {
@@ -1027,6 +1236,8 @@
return config.isEnabled(MapperFeature.USE_STATIC_TYPING);
}
+ // Commented out in 2.9
+ /*
protected Class<?> _verifyAsClass(Object src, String methodName, Class<?> noneClass)
{
if (src == null) {
@@ -1041,4 +1252,5 @@
}
return cls;
}
+ */
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java
index 49c87af..51cb1c2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java
@@ -21,6 +21,7 @@
import com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
import com.fasterxml.jackson.databind.util.Annotations;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.NameTransformer;
/**
@@ -134,7 +135,7 @@
*/
/**
- * Serializer to use for writing out the value: null if it can not be known
+ * Serializer to use for writing out the value: null if it cannot be known
* statically; non-null if it can.
*/
protected JsonSerializer<Object> _serializer;
@@ -201,19 +202,23 @@
/***********************************************************
*/
+ /**
+ * @since 2.9 (added `includeInViews` since 2.8)
+ */
@SuppressWarnings("unchecked")
public BeanPropertyWriter(BeanPropertyDefinition propDef,
AnnotatedMember member, Annotations contextAnnotations,
- JavaType declaredType, JsonSerializer<?> ser,
- TypeSerializer typeSer, JavaType serType, boolean suppressNulls,
- Object suppressableValue) {
+ JavaType declaredType,
+ JsonSerializer<?> ser, TypeSerializer typeSer, JavaType serType,
+ boolean suppressNulls, Object suppressableValue,
+ Class<?>[] includeInViews)
+ {
super(propDef);
_member = member;
_contextAnnotations = contextAnnotations;
_name = new SerializedString(propDef.getName());
_wrapperName = propDef.getWrapperName();
- _includeInViews = propDef.findViews();
_declaredType = declaredType;
_serializer = (JsonSerializer<Object>) ser;
@@ -239,6 +244,19 @@
// this will be resolved later on, unless nulls are to be suppressed
_nullSerializer = null;
+ _includeInViews = includeInViews;
+ }
+
+ @Deprecated // Since 2.9
+ public BeanPropertyWriter(BeanPropertyDefinition propDef,
+ AnnotatedMember member, Annotations contextAnnotations,
+ JavaType declaredType,
+ JsonSerializer<?> ser, TypeSerializer typeSer, JavaType serType,
+ boolean suppressNulls, Object suppressableValue)
+ {
+ this(propDef, member, contextAnnotations, declaredType,
+ ser, typeSer, serType, suppressNulls, suppressableValue,
+ null);
}
/**
@@ -372,8 +390,10 @@
*/
public void assignSerializer(JsonSerializer<Object> ser) {
// may need to disable check in future?
- if (_serializer != null && _serializer != ser) {
- throw new IllegalStateException("Can not override serializer");
+ if ((_serializer != null) && (_serializer != ser)) {
+ throw new IllegalStateException(String.format(
+ "Cannot override _serializer: had a %s, trying to set to %s",
+ ClassUtil.classNameOf(_serializer), ClassUtil.classNameOf(ser)));
}
_serializer = ser;
}
@@ -384,7 +404,9 @@
public void assignNullSerializer(JsonSerializer<Object> nullSer) {
// may need to disable check in future?
if ((_nullSerializer != null) && (_nullSerializer != nullSer)) {
- throw new IllegalStateException("Can not override null serializer");
+ throw new IllegalStateException(String.format(
+ "Cannot override _nullSerializer: had a %s, trying to set to %s",
+ ClassUtil.classNameOf(_nullSerializer), ClassUtil.classNameOf(nullSer)));
}
_nullSerializer = nullSer;
}
@@ -605,6 +627,7 @@
return _cfgSerializationType;
}
+ @Deprecated // since 2.9
public Class<?> getRawSerializationType() {
return (_cfgSerializationType == null) ? null : _cfgSerializationType
.getRawClass();
@@ -662,7 +685,7 @@
SerializerProvider prov) throws Exception {
// inlined 'get()'
final Object value = (_accessorMethod == null) ? _field.get(bean)
- : _accessorMethod.invoke(bean);
+ : _accessorMethod.invoke(bean, (Object[]) null);
// Null handling is bit different, check that first
if (value == null) {
@@ -734,7 +757,7 @@
SerializerProvider prov) throws Exception {
// inlined 'get()'
final Object value = (_accessorMethod == null) ? _field.get(bean)
- : _accessorMethod.invoke(bean);
+ : _accessorMethod.invoke(bean, (Object[]) null);
if (value == null) { // nulls need specialized handling
if (_nullSerializer != null) {
_nullSerializer.serialize(null, gen, prov);
@@ -890,7 +913,7 @@
*/
public final Object get(Object bean) throws Exception {
return (_accessorMethod == null) ? _field.get(bean) : _accessorMethod
- .invoke(bean);
+ .invoke(bean, (Object[]) null);
}
/**
@@ -918,7 +941,7 @@
// (something
// OTHER than {@link BeanSerializerBase}
if (ser instanceof BeanSerializerBase) {
- prov.reportMappingProblem("Direct self-reference leading to cycle");
+ prov.reportBadDefinition(getType(), "Direct self-reference leading to cycle");
}
}
return false;
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java
index 7dc0d17..9813e4b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java
@@ -26,7 +26,7 @@
public class BeanSerializer
extends BeanSerializerBase
{
- private static final long serialVersionUID = -3618164443537292758L;
+ private static final long serialVersionUID = 29; // as per jackson 2.9
/*
/**********************************************************
@@ -112,7 +112,7 @@
@Override
protected BeanSerializerBase asArraySerializer()
{
- /* Can not:
+ /* Cannot:
*
* - have Object Id (may be allowed in future)
* - have "any getter"
@@ -156,7 +156,7 @@
}
gen.writeEndObject();
}
-
+
/*
/**********************************************************
/* Standard methods
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerBuilder.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerBuilder.java
index 95613e1..c89d380 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerBuilder.java
@@ -37,7 +37,7 @@
/**
* Bean properties, in order of serialization
*/
- protected List<BeanPropertyWriter> _properties;
+ protected List<BeanPropertyWriter> _properties = Collections.emptyList();
/**
* Optional array of filtered property writers; if null, no
@@ -66,13 +66,13 @@
* type, if any.
*/
protected ObjectIdWriter _objectIdWriter;
-
+
/*
/**********************************************************
/* Construction and setter methods
/**********************************************************
*/
-
+
public BeanSerializerBuilder(BeanDescription beanDesc) {
_beanDesc = beanDesc;
}
@@ -105,10 +105,22 @@
_properties = properties;
}
+ /**
+ * @param properties Number and order of properties here MUST match that
+ * of "regular" properties set earlier using {@link #setProperties(List)}; if not,
+ * an {@link IllegalArgumentException} will be thrown
+ */
public void setFilteredProperties(BeanPropertyWriter[] properties) {
+ if (properties != null) {
+ if (properties.length != _properties.size()) { // as per [databind#1612]
+ throw new IllegalArgumentException(String.format(
+ "Trying to set %d filtered properties; must match length of non-filtered `properties` (%d)",
+ properties.length, _properties.size()));
+ }
+ }
_filteredProperties = properties;
}
-
+
public void setAnyGetter(AnyGetterWriter anyGetter) {
_anyGetter = anyGetter;
}
@@ -185,6 +197,14 @@
}
}
}
+ // 27-Apr-2017, tatu: Verify that filtered-properties settings are compatible
+ if (_filteredProperties != null) {
+ if (_filteredProperties.length != _properties.size()) {
+ throw new IllegalStateException(String.format(
+"Mismatch between `properties` size (%d), `filteredProperties` (%s): should have as many (or `null` for latter)",
+_properties.size(), _filteredProperties.length));
+ }
+ }
if (_anyGetter != null) {
_anyGetter.fixAccess(_config);
}
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 921d67e..0040256 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java
@@ -1,7 +1,6 @@
package com.fasterxml.jackson.databind.ser;
import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
@@ -9,7 +8,6 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.cfg.ConfigOverride;
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.NamedType;
@@ -18,7 +16,6 @@
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.std.AtomicReferenceSerializer;
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
import com.fasterxml.jackson.databind.type.ReferenceType;
@@ -100,7 +97,7 @@
*/
if (getClass() != BeanSerializerFactory.class) {
throw new IllegalStateException("Subtype of BeanSerializerFactory ("+getClass().getName()
- +") has not properly overridden method 'withAdditionalSerializers': can not instantiate subtype with "
+ +") has not properly overridden method 'withAdditionalSerializers': cannot instantiate subtype with "
+"additional serializer definitions");
}
return new BeanSerializerFactory(config);
@@ -110,7 +107,7 @@
protected Iterable<Serializers> customSerializers() {
return _factoryConfig.serializers();
}
-
+
/*
/**********************************************************
/* SerializerFactory impl
@@ -283,34 +280,6 @@
}
/**
- * @since 2.7
- */
- public JsonSerializer<?> findReferenceSerializer(SerializerProvider prov, ReferenceType refType,
- BeanDescription beanDesc, boolean staticTyping)
- throws JsonMappingException
- {
- JavaType contentType = refType.getContentType();
- TypeSerializer contentTypeSerializer = contentType.getTypeHandler();
- final SerializationConfig config = prov.getConfig();
- if (contentTypeSerializer == null) {
- contentTypeSerializer = createTypeSerializer(config, contentType);
- }
- JsonSerializer<Object> contentSerializer = contentType.getValueHandler();
- for (Serializers serializers : customSerializers()) {
- JsonSerializer<?> ser = serializers.findReferenceSerializer(config, refType, beanDesc,
- contentTypeSerializer, contentSerializer);
- if (ser != null) {
- return ser;
- }
- }
- if (refType.isTypeOrSubTypeOf(AtomicReference.class)) {
- return new AtomicReferenceSerializer(refType, staticTyping,
- contentTypeSerializer, contentSerializer);
- }
- return null;
- }
-
- /**
* Method called to create a type information serializer for values of given
* non-container property
* if one is needed. If not needed (no polymorphic handling configured), should
@@ -389,7 +358,7 @@
// 05-Jul-2012, tatu: ... but we should be able to just return "unknown type" serializer, right?
if (beanDesc.getBeanClass() == Object.class) {
return prov.getUnknownTypeSerializer(Object.class);
-// throw new IllegalArgumentException("Can not create bean serializer for Object.class");
+// throw new IllegalArgumentException("Cannot create bean serializer for Object.class");
}
final SerializationConfig config = prov.getConfig();
BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
@@ -423,10 +392,9 @@
}
}
- /* And if Object Id is needed, some preparation for that as well: better
- * do before view handling, mostly for the custom id case which needs
- * access to a property
- */
+ // And if Object Id is needed, some preparation for that as well: better
+ // do before view handling, mostly for the custom id case which needs
+ // access to a property
builder.setObjectIdWriter(constructObjectIdHandler(prov, beanDesc, props));
builder.setProperties(props);
@@ -450,7 +418,7 @@
// TODO: can we find full PropertyName?
PropertyName name = PropertyName.construct(anyGetter.getName());
BeanProperty.Std anyProp = new BeanProperty.Std(name, valueType, null,
- beanDesc.getClassAnnotations(), anyGetter, PropertyMetadata.STD_OPTIONAL);
+ anyGetter, PropertyMetadata.STD_OPTIONAL);
builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, anySer));
}
// Next: need to gather view information, if any:
@@ -463,7 +431,13 @@
}
}
- JsonSerializer<Object> ser = (JsonSerializer<Object>) builder.build();
+ JsonSerializer<Object> ser = null;
+ try {
+ ser = (JsonSerializer<Object>) builder.build();
+ } catch (RuntimeException e) {
+ prov.reportBadTypeDefinition(beanDesc, "Failed to construct BeanSerializer for %s: (%s) %s",
+ beanDesc.getType(), e.getClass().getName(), e.getMessage());
+ }
if (ser == null) {
// If we get this far, there were no properties found, so no regular BeanSerializer
// would be constructed. But, couple of exceptions.
@@ -494,14 +468,13 @@
for (int i = 0, len = props.size() ;; ++i) {
if (i == len) {
throw new IllegalArgumentException("Invalid Object Id definition for "+beanDesc.getBeanClass().getName()
- +": can not find property with name '"+propName+"'");
+ +": cannot find property with name '"+propName+"'");
}
BeanPropertyWriter prop = props.get(i);
if (propName.equals(prop.getName())) {
idProp = prop;
- /* Let's force it to be the first property to output
- * (although it may still get rearranged etc)
- */
+ // Let's force it to be the first property to output
+ // (although it may still get rearranged etc)
if (i > 0) {
props.remove(i);
props.add(0, idProp);
@@ -553,7 +526,7 @@
/**
* Helper method used to skip processing for types that we know
- * can not be (i.e. are never consider to be) beans:
+ * cannot be (i.e. are never consider to be) beans:
* things like primitives, Arrays, Enums, and proxy types.
*<p>
* Note that usually we shouldn't really be getting these sort of
@@ -689,6 +662,7 @@
* Method that will apply by-type limitations (as per [JACKSON-429]);
* by default this is based on {@link com.fasterxml.jackson.annotation.JsonIgnoreType}
* annotation but can be supplied by module-provided introspectors too.
+ * Starting with 2.8 there are also "Config overrides" to consider.
*/
protected void removeIgnorableTypes(SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyDefinition> properties)
@@ -699,18 +673,19 @@
while (it.hasNext()) {
BeanPropertyDefinition property = it.next();
AnnotatedMember accessor = property.getAccessor();
+ /* 22-Oct-2016, tatu: Looks like this removal is an important part of
+ * processing, as taking it out will result in a few test failures...
+ * But should probably be done somewhere else, not here?
+ */
if (accessor == null) {
it.remove();
continue;
}
- Class<?> type = accessor.getRawType();
+ Class<?> type = property.getRawPrimaryType();
Boolean result = ignores.get(type);
if (result == null) {
// 21-Apr-2016, tatu: For 2.8, can specify config overrides
- ConfigOverride override = config.findConfigOverride(type);
- if (override != null) {
- result = override.getIsIgnoredType();
- }
+ result = config.getConfigOverride(type).getIsIgnoredType();
if (result == null) {
BeanDescription desc = config.introspectClassAnnotations(type);
AnnotatedClass ac = desc.getClassInfo();
@@ -793,7 +768,7 @@
final PropertyName name = propDef.getFullName();
JavaType type = accessor.getType();
BeanProperty.Std property = new BeanProperty.Std(name, type, propDef.getWrapperName(),
- pb.getClassAnnotations(), accessor, propDef.getMetadata());
+ accessor, propDef.getMetadata());
// Does member specify a serializer? If so, let's use it.
JsonSerializer<?> annotatedSerializer = findSerializerFromAnnotation(prov,
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java
index 637150a..c7d4bea 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/ContainerSerializer.java
@@ -90,16 +90,7 @@
/**********************************************************
*/
- /* Overridden as abstract, to force re-implementation; necessary for all
- * collection types.
- */
- @Override
- @Deprecated
- public boolean isEmpty(T value) {
- return isEmpty(null, value);
- }
-
- // since 2.5: should be declared abstract in future (2.6)
+// since 2.5: should be declared abstract in future (2.9?)
// @Override
// public abstract boolean isEmpty(SerializerProvider prov, T value);
@@ -111,6 +102,10 @@
* like "getElementCount()" method, this would not work well for
* containers that do not keep track of size (like linked lists may
* not).
+ *<p>
+ * Note, too, that as of now (2.9) this method is only called by serializer
+ * itself; and specifically is not used for non-array/collection types
+ * like <code>Map</code> or <code>Map.Entry</code> instances.
*/
public abstract boolean hasSingleElement(T value);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/ContextualSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/ContextualSerializer.java
index 6f7091d..b8be372 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/ContextualSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/ContextualSerializer.java
@@ -27,7 +27,7 @@
* @param prov Serializer provider to use for accessing config, other serializers
* @param property Method or field that represents the property
* (and is used to access value to serialize).
- * Should be available; but there may be cases where caller can not provide it and
+ * Should be available; but there may be cases where caller cannot provide it and
* null is passed instead (in which case impls usually pass 'this' serializer as is)
*
* @return Serializer to use for serializing values of specified property;
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java
index ed8e965..2d52142 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java
@@ -9,6 +9,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.introspect.Annotated;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonschema.SchemaAware;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -100,7 +101,8 @@
*/
@Override
- public JsonSerializer<Object> serializerInstance(Annotated annotated, Object serDef) throws JsonMappingException
+ public JsonSerializer<Object> serializerInstance(Annotated annotated, Object serDef)
+ throws JsonMappingException
{
if (serDef == null) {
return null;
@@ -110,11 +112,11 @@
if (serDef instanceof JsonSerializer) {
ser = (JsonSerializer<?>) serDef;
} 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 (!(serDef instanceof Class)) {
- throw new IllegalStateException("AnnotationIntrospector returned serializer definition of type "
+ reportBadDefinition(annotated.getType(),
+ "AnnotationIntrospector returned serializer definition of type "
+serDef.getClass().getName()+"; expected type JsonSerializer or Class<JsonSerializer> instead");
}
Class<?> serClass = (Class<?>)serDef;
@@ -123,7 +125,8 @@
return null;
}
if (!JsonSerializer.class.isAssignableFrom(serClass)) {
- throw new IllegalStateException("AnnotationIntrospector returned Class "
+ reportBadDefinition(annotated.getType(),
+ "AnnotationIntrospector returned Class "
+serClass.getName()+"; expected Class<JsonSerializer>");
}
HandlerInstantiator hi = _config.getHandlerInstantiator();
@@ -136,6 +139,41 @@
return (JsonSerializer<Object>) _handleResolvable(ser);
}
+ @Override
+ public Object includeFilterInstance(BeanPropertyDefinition forProperty,
+ Class<?> filterClass)
+ {
+ if (filterClass == null) {
+ return null;
+ }
+ HandlerInstantiator hi = _config.getHandlerInstantiator();
+ Object filter = (hi == null) ? null : hi.includeFilterInstance(_config, forProperty, filterClass);
+ if (filter == null) {
+ filter = ClassUtil.createInstance(filterClass,
+ _config.canOverrideAccessModifiers());
+ }
+ return filter;
+ }
+
+ @Override
+ public boolean includeFilterSuppressNulls(Object filter) throws JsonMappingException
+ {
+ if (filter == null) {
+ return true;
+ }
+ // should let filter decide what to do with nulls:
+ // But just case, let's handle unexpected (from our perspective) problems explicitly
+ try {
+ return filter.equals(null);
+ } catch (Throwable t) {
+ String msg = String.format(
+"Problem determining whether filter of type '%s' should filter out `null` values: (%s) %s",
+filter.getClass().getName(), t.getClass().getName(), ClassUtil.exceptionMessage(t));
+ reportBadDefinition(filter.getClass(), msg, t);
+ return false; // never gets here
+ }
+ }
+
/*
/**********************************************************
/* Object Id handling
@@ -265,43 +303,20 @@
_serializeNull(gen);
return;
}
- Class<?> cls = value.getClass();
+ final Class<?> cls = value.getClass();
// true, since we do want to cache root-level typed serializers (ditto for null property)
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
-
- // Ok: should we wrap result in an additional property ("root name")?
- final boolean wrap;
PropertyName rootName = _config.getFullRootName();
-
if (rootName == null) { // not explicitly specified
- wrap = _config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE);
- if (wrap) {
- gen.writeStartObject();
- PropertyName pname = _config.findRootName(value.getClass());
- gen.writeFieldName(pname.simpleAsEncoded(_config));
+ if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
+ _serialize(gen, value, ser, _config.findRootName(cls));
+ return;
}
- } else if (rootName.isEmpty()) {
- wrap = false;
- } else { // [JACKSON-764]
- // empty String means explicitly disabled; non-empty that it is enabled
- wrap = true;
- gen.writeStartObject();
- gen.writeFieldName(rootName.getSimpleName());
+ } else if (!rootName.isEmpty()) {
+ _serialize(gen, value, ser, rootName);
+ return;
}
- try {
- ser.serialize(value, gen, this);
- if (wrap) {
- gen.writeEndObject();
- }
- } catch (IOException ioe) { // As per [JACKSON-99], pass IOException and subtypes as-is
- throw ioe;
- } catch (Exception e) { // but wrap RuntimeExceptions, to get path information
- String msg = e.getMessage();
- if (msg == null) {
- msg = "[no message for "+e.getClass().getName()+"]";
- }
- throw new JsonMappingException(gen, msg, e);
- }
+ _serialize(gen, value, ser);
}
/**
@@ -328,39 +343,17 @@
}
// root value, not reached via property:
JsonSerializer<Object> ser = findTypedValueSerializer(rootType, true, null);
-
- // Ok: should we wrap result in an additional property ("root name")?
- final boolean wrap;
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
- wrap = _config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE);
- if (wrap) {
- gen.writeStartObject();
- PropertyName pname = _config.findRootName(value.getClass());
- gen.writeFieldName(pname.simpleAsEncoded(_config));
+ if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
+ _serialize(gen, value, ser, _config.findRootName(rootType));
+ return;
}
- } else if (rootName.isEmpty()) {
- wrap = false;
- } else {
- // empty String means explicitly disabled; non-empty that it is enabled
- wrap = true;
- gen.writeStartObject();
- gen.writeFieldName(rootName.getSimpleName());
+ } else if (!rootName.isEmpty()) {
+ _serialize(gen, value, ser, rootName);
+ return;
}
- try {
- ser.serialize(value, gen, this);
- if (wrap) {
- gen.writeEndObject();
- }
- } catch (IOException ioe) { // no wrapping for IO (and derived)
- throw ioe;
- } catch (Exception e) { // but others do need to be, to get path etc
- String msg = e.getMessage();
- if (msg == null) {
- msg = "[no message for "+e.getClass().getName()+"]";
- }
- reportMappingProblem(e, msg);
- }
+ _serialize(gen, value, ser);
}
/**
@@ -391,41 +384,20 @@
if (ser == null) {
ser = findTypedValueSerializer(rootType, true, null);
}
- // Ok: should we wrap result in an additional property ("root name")?
- final boolean wrap;
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
- // [JACKSON-163]
- wrap = _config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE);
- if (wrap) {
- gen.writeStartObject();
- PropertyName pname = (rootType == null)
+ if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
+ rootName = (rootType == null)
? _config.findRootName(value.getClass())
: _config.findRootName(rootType);
- gen.writeFieldName(pname.simpleAsEncoded(_config));
+ _serialize(gen, value, ser, rootName);
+ return;
}
- } else if (rootName.isEmpty()) {
- wrap = false;
- } else { // [JACKSON-764]
- // empty String means explicitly disabled; non-empty that it is enabled
- wrap = true;
- gen.writeStartObject();
- gen.writeFieldName(rootName.getSimpleName());
+ } else if (!rootName.isEmpty()) {
+ _serialize(gen, value, ser, rootName);
+ return;
}
- try {
- ser.serialize(value, gen, this);
- if (wrap) {
- gen.writeEndObject();
- }
- } catch (IOException ioe) { // no wrapping for IO (and derived)
- throw ioe;
- } catch (Exception e) { // but others do need to be, to get path etc
- String msg = e.getMessage();
- if (msg == null) {
- msg = "[no message for "+e.getClass().getName()+"]";
- }
- reportMappingProblem(e, msg);
- }
+ _serialize(gen, value, ser);
}
/**
@@ -481,26 +453,34 @@
if (wrap) {
gen.writeEndObject();
}
- } catch (IOException ioe) { // no wrapping for IO (and derived)
- throw ioe;
- } catch (Exception e) { // but others do need to be, to get path etc
- String msg = e.getMessage();
- if (msg == null) {
- msg = "[no message for "+e.getClass().getName()+"]";
- }
- reportMappingProblem(e, msg);
+ } catch (Exception e) {
+ throw _wrapAsIOE(gen, e);
}
}
- /**
- * @deprecated since 2.6; remove from 2.7 or later
- */
- @Deprecated
- public void serializePolymorphic(JsonGenerator gen, Object value, TypeSerializer typeSer)
- throws IOException
+ private final void _serialize(JsonGenerator gen, Object value,
+ JsonSerializer<Object> ser, PropertyName rootName)
+ throws IOException
{
- JavaType t = (value == null) ? null : _config.constructType(value.getClass());
- serializePolymorphic(gen, value, t, null, typeSer);
+ try {
+ gen.writeStartObject();
+ gen.writeFieldName(rootName.simpleAsEncoded(_config));
+ ser.serialize(value, gen, this);
+ gen.writeEndObject();
+ } catch (Exception e) {
+ throw _wrapAsIOE(gen, e);
+ }
+ }
+
+ private final void _serialize(JsonGenerator gen, Object value,
+ JsonSerializer<Object> ser)
+ throws IOException
+ {
+ try {
+ ser.serialize(value, gen, this);
+ } catch (Exception e) {
+ throw _wrapAsIOE(gen, e);
+ }
}
/**
@@ -513,16 +493,22 @@
JsonSerializer<Object> ser = getDefaultNullValueSerializer();
try {
ser.serialize(null, gen, this);
- } catch (IOException ioe) { // no wrapping for IO (and derived)
- throw ioe;
- } catch (Exception e) { // but others do need to be, to get path etc
- String msg = e.getMessage();
- if (msg == null) {
- msg = "[no message for "+e.getClass().getName()+"]";
- }
- reportMappingProblem(e, msg);
+ } catch (Exception e) {
+ throw _wrapAsIOE(gen, e);
}
}
+
+ private IOException _wrapAsIOE(JsonGenerator g, Exception e) {
+ if (e instanceof IOException) {
+ return (IOException) e;
+ }
+ String msg = ClassUtil.exceptionMessage(e);
+ if (msg == null) {
+ msg = "[no message for "+e.getClass().getName()+"]";
+ }
+ return new JsonMappingException(g, msg, e);
+ }
+
/*
/********************************************************
/* Access to caching details
@@ -593,9 +579,6 @@
public com.fasterxml.jackson.databind.jsonschema.JsonSchema generateJsonSchema(Class<?> type)
throws JsonMappingException
{
- if (type == null) {
- throw new IllegalArgumentException("A class must be provided");
- }
/* no need for embedded type information for JSON schema generation (all
* type information it needs is accessible via "untyped" serializer)
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java
index 6363421..957af4c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java
@@ -48,7 +48,7 @@
* @since 2.8
*/
final protected boolean _useRealPropertyDefaults;
-
+
public PropertyBuilder(SerializationConfig config, BeanDescription beanDesc)
{
_config = config;
@@ -56,8 +56,10 @@
// 08-Sep-2016, tatu: This gets tricky, with 3 levels of definitions:
// (a) global default inclusion
// (b) per-type default inclusion (from annotation or config overrides;
- // latter having precedence
- // Cc) per-property override
+ // config override having precedence)
+ // (c) per-property override (from annotation on specific property or
+ // config overrides per type of property;
+ // annotation having precedence)
//
// and not only requiring merging, but also considering special handling
// for NON_DEFAULT in case of (b) (vs (a) or (c))
@@ -86,7 +88,6 @@
* to use for contained values (only used for properties that are
* of container type)
*/
- @SuppressWarnings("deprecation")
protected BeanPropertyWriter buildWriter(SerializerProvider prov,
BeanPropertyDefinition propDef, JavaType declaredType, JsonSerializer<?> ser,
TypeSerializer typeSer, TypeSerializer contentTypeSer,
@@ -98,15 +99,17 @@
try {
serializationType = findSerializationType(am, defaultUseStaticTyping, declaredType);
} catch (JsonMappingException e) {
- return prov.reportBadPropertyDefinition(_beanDesc, propDef, e.getMessage());
+ if (propDef == null) {
+ return prov.reportBadDefinition(declaredType, ClassUtil.exceptionMessage(e));
+ }
+ return prov.reportBadPropertyDefinition(_beanDesc, propDef, ClassUtil.exceptionMessage(e));
}
// Container types can have separate type serializers for content (value / element) type
if (contentTypeSer != null) {
- /* 04-Feb-2010, tatu: Let's force static typing for collection, if there is
- * type information for contents. Should work well (for JAXB case); can be
- * revisited if this causes problems.
- */
+ // 04-Feb-2010, tatu: Let's force static typing for collection, if there is
+ // type information for contents. Should work well (for JAXB case); can be
+ // revisited if this causes problems.
if (serializationType == null) {
// serializationType = TypeFactory.type(am.getGenericType(), _beanDesc.getType());
serializationType = declaredType;
@@ -127,21 +130,29 @@
// 12-Jul-2016, tatu: [databind#1256] Need to make sure we consider type refinement
JavaType actualType = (serializationType == null) ? declaredType : serializationType;
+ // 17-Mar-2017: [databind#1522] Allow config override per property type
+ AnnotatedMember accessor = propDef.getAccessor();
+ if (accessor == null) {
+ // neither Setter nor ConstructorParameter are expected here
+ return prov.reportBadPropertyDefinition(_beanDesc, propDef,
+ "could not determine property type");
+ }
+ Class<?> rawPropertyType = accessor.getRawType();
+
// 17-Aug-2016, tatu: Default inclusion covers global default (for all types), as well
// as type-default for enclosing POJO. What we need, then, is per-type default (if any)
// for declared property type... and finally property annotation overrides
- JsonInclude.Value inclV = _config.getDefaultPropertyInclusion(actualType.getRawClass(),
- _defaultInclusion);
+ JsonInclude.Value inclV = _config.getDefaultInclusion(actualType.getRawClass(),
+ rawPropertyType, _defaultInclusion);
// property annotation override
inclV = inclV.withOverrides(propDef.findInclusion());
- JsonInclude.Include inclusion = inclV.getValueInclusion();
+ JsonInclude.Include inclusion = inclV.getValueInclusion();
if (inclusion == JsonInclude.Include.USE_DEFAULTS) { // should not occur but...
inclusion = JsonInclude.Include.ALWAYS;
}
-
switch (inclusion) {
case NON_DEFAULT:
// 11-Nov-2015, tatu: This is tricky because semantics differ between cases,
@@ -151,7 +162,7 @@
// First: case of class/type specifying it; try to find POJO property defaults
Object defaultBean;
- // 16-Oct-2016, tatu: Note: if we can not for some reason create "default instance",
+ // 16-Oct-2016, tatu: Note: if we cannot for some reason create "default instance",
// revert logic to the case of general/per-property handling, so both
// type-default AND null are to be excluded.
// (as per [databind#1417]
@@ -166,7 +177,7 @@
_throwWrapped(e, propDef.getName(), defaultBean);
}
} else {
- valueToSuppress = getDefaultValue(actualType);
+ valueToSuppress = BeanUtil.getDefaultValue(actualType);
suppressNulls = true;
}
if (valueToSuppress == null) {
@@ -191,21 +202,33 @@
// but possibly also 'empty' values:
valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
break;
+ case CUSTOM: // new with 2.9
+ valueToSuppress = prov.includeFilterInstance(propDef, inclV.getValueFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
case NON_NULL:
suppressNulls = true;
// fall through
case ALWAYS: // default
default:
- // we may still want to suppress empty collections, as per [JACKSON-254]:
+ // we may still want to suppress empty collections
if (actualType.isContainerType()
&& !_config.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)) {
valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
}
break;
}
+ Class<?>[] views = propDef.findViews();
+ if (views == null) {
+ views = _beanDesc.findDefaultViews();
+ }
BeanPropertyWriter bpw = new BeanPropertyWriter(propDef,
am, _beanDesc.getClassAnnotations(), declaredType,
- ser, typeSer, serializationType, suppressNulls, valueToSuppress);
+ ser, typeSer, serializationType, suppressNulls, valueToSuppress, views);
// How about custom null serializer?
Object serDef = _annotationIntrospector.findNullSerializer(am);
@@ -294,7 +317,7 @@
// 06-Nov-2015, tatu: As per [databind#998], do not fail.
/*
Class<?> cls = _beanDesc.getClassInfo().getAnnotated();
- throw new IllegalArgumentException("Class "+cls.getName()+" has no default constructor; can not instantiate default bean value to support 'properties=JsonSerialize.Inclusion.NON_DEFAULT' annotation");
+ throw new IllegalArgumentException("Class "+cls.getName()+" has no default constructor; cannot instantiate default bean value to support 'properties=JsonSerialize.Inclusion.NON_DEFAULT' annotation");
*/
// And use a marker
@@ -317,10 +340,10 @@
* {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_EMPTY} requires special handling.
*
* @since 2.7
- * @deprecated Since 2.8.5 since this will not allow determining difference between "no default instance"
+ * @deprecated Since 2.9 since this will not allow determining difference between "no default instance"
* case and default being `null`.
*/
- @Deprecated // since 2.8.5
+ @Deprecated // since 2.9
protected Object getPropertyDefaultValue(String name, AnnotatedMember member,
JavaType type)
{
@@ -336,37 +359,13 @@
}
/**
- * Accessor used to find out "default value" to use for comparing values to
- * serialize, to determine whether to exclude value from serialization with
- * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
- *<p>
- * Default logic is such that for primitives and wrapper types for primitives, expected
- * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String,
- * and for structured (Maps, Collections, arrays) and reference types, criteria
- * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}
- * is used.
- *
- * @since 2.7
+ * @deprecated Since 2.9
*/
- protected Object getDefaultValue(JavaType type)
- {
- // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
- // handling for primitives since they are never passed as nulls.
- Class<?> cls = type.getRawClass();
-
- Class<?> prim = ClassUtil.primitiveType(cls);
- if (prim != null) {
- return ClassUtil.defaultValue(prim);
- }
- if (type.isContainerType() || type.isReferenceType()) {
- return JsonInclude.Include.NON_EMPTY;
- }
- if (cls == String.class) {
- return "";
- }
- return null;
+ @Deprecated // since 2.9
+ protected Object getDefaultValue(JavaType type) {
+ return BeanUtil.getDefaultValue(type);
}
-
+
/*
/**********************************************************
/* Helper methods for exception handling
@@ -379,8 +378,8 @@
while (t.getCause() != null) {
t = t.getCause();
}
- if (t instanceof Error) throw (Error) t;
- if (t instanceof RuntimeException) throw (RuntimeException) t;
+ ClassUtil.throwIfError(t);
+ ClassUtil.throwIfRTE(t);
throw new IllegalArgumentException("Failed to get property '"+propName+"' of default "+defaultBean.getClass().getName()+" instance");
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyFilter.java b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyFilter.java
index a77c540..89f32ef 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyFilter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyFilter.java
@@ -34,17 +34,17 @@
* Typical implementation is something like:
*<pre>
* if (include(writer)) {
- * writer.serializeAsField(pojo, jgen, prov);
+ * writer.serializeAsField(pojo, gen, prov);
* }
*</pre>
*
* @param pojo Object that contains property value to serialize
- * @param jgen Generator use for serializing value
+ * @param gen Generator use for serializing value
* @param prov Provider that can be used for accessing dynamic aspects of serialization
* processing
* @param writer Object called to do actual serialization of the field, if not filtered out
*/
- public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider prov,
+ public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider prov,
PropertyWriter writer)
throws Exception;
@@ -58,17 +58,17 @@
* Typical implementation is something like:
*<pre>
* if (include(writer)) {
- * writer.serializeAsElement(pojo, jgen, prov);
+ * writer.serializeAsElement(pojo, gen, prov);
* }
*</pre>
*
* @param elementValue Element value being serializerd
- * @param jgen Generator use for serializing value
+ * @param gen Generator use for serializing value
* @param prov Provider that can be used for accessing dynamic aspects of serialization
* processing
* @param writer Object called to do actual serialization of the field, if not filtered out
*/
- public void serializeAsElement(Object elementValue, JsonGenerator jgen, SerializerProvider prov,
+ public void serializeAsElement(Object elementValue, JsonGenerator gen, SerializerProvider prov,
PropertyWriter writer)
throws Exception;
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java
index 54b9d72..ad6cd35 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java
@@ -52,13 +52,23 @@
protected VirtualBeanPropertyWriter(BeanPropertyDefinition propDef,
Annotations contextAnnotations, JavaType declaredType,
JsonSerializer<?> ser, TypeSerializer typeSer, JavaType serType,
- JsonInclude.Value inclusion)
+ JsonInclude.Value inclusion, Class<?>[] includeInViews)
{
super(propDef, propDef.getPrimaryMember(), contextAnnotations, declaredType,
ser, typeSer, serType,
- _suppressNulls(inclusion), _suppressableValue(inclusion));
+ _suppressNulls(inclusion), _suppressableValue(inclusion),
+ includeInViews);
}
+ @Deprecated // since 2.8
+ protected VirtualBeanPropertyWriter(BeanPropertyDefinition propDef,
+ Annotations contextAnnotations, JavaType declaredType,
+ JsonSerializer<?> ser, TypeSerializer typeSer, JavaType serType,
+ JsonInclude.Value inclusion)
+ {
+ this(propDef, contextAnnotations, declaredType, ser, typeSer, serType, inclusion, null);
+ }
+
protected VirtualBeanPropertyWriter(VirtualBeanPropertyWriter base) {
super(base);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/AttributePropertyWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/AttributePropertyWriter.java
index a1cd172..477f9ee 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/AttributePropertyWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/AttributePropertyWriter.java
@@ -41,7 +41,9 @@
{
super(propDef, contextAnnotations, declaredType,
/* value serializer */ null, /* type serializer */ null, /* ser type */ null,
- inclusion);
+ inclusion,
+ // 10-Oct-2016, tatu: Could enable per-view settings too in future
+ null);
_attrName = attrName;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java
index 3f764ca..64ced81 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java
@@ -4,6 +4,8 @@
import java.util.Set;
import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
@@ -48,7 +50,7 @@
/**
* Serializer that would produce JSON Object version; used in
- * cases where array output can not be used.
+ * cases where array output cannot be used.
*/
protected final BeanSerializerBase _defaultSerializer;
@@ -134,18 +136,11 @@
_serializeWithObjectId(bean, gen, provider, typeSer);
return;
}
- String typeStr = (_typeId == null) ? null : _customTypeId(bean);
- if (typeStr == null) {
- typeSer.writeTypePrefixForArray(bean, gen);
- } else {
- typeSer.writeCustomTypePrefixForArray(bean, gen, typeStr);
- }
+ gen.setCurrentValue(bean);
+ WritableTypeId typeIdDef = _typeIdDef(typeSer, bean, JsonToken.START_ARRAY);
+ typeSer.writeTypePrefix(gen, typeIdDef);
serializeAsArray(bean, gen, provider);
- if (typeStr == null) {
- typeSer.writeTypeSuffixForArray(bean, gen);
- } else {
- typeSer.writeCustomTypeSuffixForArray(bean, gen, typeStr);
- }
+ typeSer.writeTypeSuffix(gen, typeIdDef);
}
/**
@@ -209,7 +204,7 @@
prop.serializeAsElement(bean, gen, provider);
}
}
- // NOTE: any getters can not be supported either
+ // NOTE: any getters cannot be supported either
//if (_anyGetterWriter != null) {
// _anyGetterWriter.getAndSerialize(bean, gen, provider);
//}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/FilteredBeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/FilteredBeanPropertyWriter.java
index ef7e635..e21d8ff 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/FilteredBeanPropertyWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/FilteredBeanPropertyWriter.java
@@ -61,26 +61,26 @@
}
@Override
- public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider prov)
+ public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov)
throws Exception
{
Class<?> activeView = prov.getActiveView();
if (activeView == null || _view.isAssignableFrom(activeView)) {
- _delegate.serializeAsField(bean, jgen, prov);
+ _delegate.serializeAsField(bean, gen, prov);
} else {
- _delegate.serializeAsOmittedField(bean, jgen, prov);
+ _delegate.serializeAsOmittedField(bean, gen, prov);
}
}
@Override
- public void serializeAsElement(Object bean, JsonGenerator jgen, SerializerProvider prov)
+ public void serializeAsElement(Object bean, JsonGenerator gen, SerializerProvider prov)
throws Exception
{
Class<?> activeView = prov.getActiveView();
if (activeView == null || _view.isAssignableFrom(activeView)) {
- _delegate.serializeAsElement(bean, jgen, prov);
+ _delegate.serializeAsElement(bean, gen, prov);
} else {
- _delegate.serializeAsPlaceholder(bean, jgen, prov);
+ _delegate.serializeAsPlaceholder(bean, gen, prov);
}
}
@@ -127,58 +127,48 @@
}
@Override
- public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider prov)
+ public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov)
throws Exception
{
- final Class<?> activeView = prov.getActiveView();
- if (activeView != null) {
- int i = 0, len = _views.length;
- for (; i < len; ++i) {
- if (_views[i].isAssignableFrom(activeView)) break;
- }
- // not included, bail out:
- if (i == len) {
- _delegate.serializeAsOmittedField(bean, jgen, prov);
- return;
- }
+ if (_inView(prov.getActiveView())) {
+ _delegate.serializeAsField(bean, gen, prov);
+ return;
}
- _delegate.serializeAsField(bean, jgen, prov);
+ _delegate.serializeAsOmittedField(bean, gen, prov);
}
@Override
- public void serializeAsElement(Object bean, JsonGenerator jgen, SerializerProvider prov)
+ public void serializeAsElement(Object bean, JsonGenerator gen, SerializerProvider prov)
throws Exception
{
- final Class<?> activeView = prov.getActiveView();
- if (activeView != null) {
- int i = 0, len = _views.length;
- for (; i < len; ++i) {
- if (_views[i].isAssignableFrom(activeView)) break;
- }
- // not included, bail out:
- if (i == len) {
- _delegate.serializeAsPlaceholder(bean, jgen, prov);
- return;
- }
+ if (_inView(prov.getActiveView())) {
+ _delegate.serializeAsElement(bean, gen, prov);
+ return;
}
- _delegate.serializeAsElement(bean, jgen, prov);
+ _delegate.serializeAsPlaceholder(bean, gen, prov);
}
@Override
public void depositSchemaProperty(JsonObjectFormatVisitor v,
SerializerProvider provider) throws JsonMappingException
{
- Class<?> activeView = provider.getActiveView();
- if (activeView != null) {
- int i = 0, len = _views.length;
- for (; i < len; ++i) {
- if (_views[i].isAssignableFrom(activeView)) break;
- }
- if (i == len) { // not match? Just don't deposit
- return;
+ if (_inView(provider.getActiveView())) {
+ super.depositSchemaProperty(v, provider);
+ }
+ }
+
+ private final boolean _inView(Class<?> activeView)
+ {
+ if (activeView == null) {
+ return true;
+ }
+ final int len = _views.length;
+ for (int i = 0; i < len; ++i) {
+ if (_views[i].isAssignableFrom(activeView)) {
+ return true;
}
}
- super.depositSchemaProperty(v, provider);
+ return false;
}
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java
index ffa5742..44f5c87 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java
@@ -13,7 +13,7 @@
/**
* This is an optimized serializer for Lists that can be efficiently
* traversed by index (as opposed to others, such as {@link LinkedList}
- * that can not}.
+ * that cannot}.
*/
@JacksonStdImpl
public final class IndexedListSerializer
@@ -48,7 +48,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, List<?> value) {
- return (value == null) || value.isEmpty();
+ return value.isEmpty();
}
@Override
@@ -81,15 +81,15 @@
}
@Override
- public void serializeContents(List<?> value, JsonGenerator jgen, SerializerProvider provider)
+ public void serializeContents(List<?> value, JsonGenerator g, SerializerProvider provider)
throws IOException
{
if (_elementSerializer != null) {
- serializeContentsUsing(value, jgen, provider, _elementSerializer);
+ serializeContentsUsing(value, g, provider, _elementSerializer);
return;
}
if (_valueTypeSerializer != null) {
- serializeTypedContents(value, jgen, provider);
+ serializeTypedContents(value, g, provider);
return;
}
final int len = value.size();
@@ -102,7 +102,7 @@
for (; i < len; ++i) {
Object elem = value.get(i);
if (elem == null) {
- provider.defaultSerializeNull(jgen);
+ provider.defaultSerializeNull(g);
} else {
Class<?> cc = elem.getClass();
JsonSerializer<Object> serializer = serializers.serializerFor(cc);
@@ -116,7 +116,7 @@
}
serializers = _dynamicSerializers;
}
- serializer.serialize(elem, jgen, provider);
+ serializer.serialize(elem, g, provider);
}
}
} catch (Exception e) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java
index e95dbaf..fbe88a6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java
@@ -4,6 +4,7 @@
import java.util.*;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
@@ -37,14 +38,13 @@
}
public IndexedStringListSerializer(IndexedStringListSerializer src,
- JsonSerializer<?> ser, Boolean unwrapSingle) {
- super(src, ser, unwrapSingle);
+ Boolean unwrapSingle) {
+ super(src, unwrapSingle);
}
@Override
- public JsonSerializer<?> _withResolved(BeanProperty prop,
- JsonSerializer<?> ser, Boolean unwrapSingle) {
- return new IndexedStringListSerializer(this, ser, unwrapSingle);
+ public JsonSerializer<?> _withResolved(BeanProperty prop, Boolean unwrapSingle) {
+ return new IndexedStringListSerializer(this, unwrapSingle);
}
@Override protected JsonNode contentSchema() { return createSchemaNode("string", true); }
@@ -61,7 +61,7 @@
*/
@Override
- public void serialize(List<String> value, JsonGenerator gen,
+ public void serialize(List<String> value, JsonGenerator g,
SerializerProvider provider) throws IOException
{
final int len = value.size();
@@ -69,75 +69,38 @@
if (((_unwrapSingle == null) &&
provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- _serializeUnwrapped(value, gen, provider);
+ serializeContents(value, g, provider, 1);
return;
}
}
-
- gen.writeStartArray(len);
- if (_serializer == null) {
- serializeContents(value, gen, provider, len);
- } else {
- serializeUsingCustom(value, gen, provider, len);
- }
- gen.writeEndArray();
+ g.writeStartArray(len);
+ serializeContents(value, g, provider, len);
+ g.writeEndArray();
}
- private final void _serializeUnwrapped(List<String> value, JsonGenerator gen,
- SerializerProvider provider) throws IOException
- {
- if (_serializer == null) {
- serializeContents(value, gen, provider, 1);
- } else {
- serializeUsingCustom(value, gen, provider, 1);
- }
- }
-
@Override
- public void serializeWithType(List<String> value, JsonGenerator gen,
- SerializerProvider provider,
- TypeSerializer typeSer) throws IOException
+ public void serializeWithType(List<String> value, JsonGenerator g, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException
{
- final int len = value.size();
- typeSer.writeTypePrefixForArray(value, gen);
- if (_serializer == null) {
- serializeContents(value, gen, provider, len);
- } else {
- serializeUsingCustom(value, gen, provider, len);
- }
- typeSer.writeTypeSuffixForArray(value, gen);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.START_ARRAY));
+ serializeContents(value, g, provider, value.size());
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
- private final void serializeContents(List<String> value, JsonGenerator gen,
+ private final void serializeContents(List<String> value, JsonGenerator g,
SerializerProvider provider, int len) throws IOException
{
+ g.setCurrentValue(value);
int i = 0;
try {
for (; i < len; ++i) {
String str = value.get(i);
if (str == null) {
- provider.defaultSerializeNull(gen);
+ provider.defaultSerializeNull(g);
} else {
- gen.writeString(str);
- }
- }
- } catch (Exception e) {
- wrapAndThrow(provider, e, value, i);
- }
- }
-
- private final void serializeUsingCustom(List<String> value, JsonGenerator gen,
- SerializerProvider provider, int len) throws IOException
- {
- int i = 0;
- try {
- final JsonSerializer<String> ser = _serializer;
- for (i = 0; i < len; ++i) {
- String str = value.get(i);
- if (str == null) {
- provider.defaultSerializeNull(gen);
- } else {
- ser.serialize(str, gen, provider);
+ g.writeString(str);
}
}
} catch (Exception e) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java
index 818e3bd..cf756db 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java
@@ -27,7 +27,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, Iterator<?> value) {
- return (value == null) || !value.hasNext();
+ return !value.hasNext();
}
@Override
@@ -52,6 +52,8 @@
public final void serialize(Iterator<?> value, JsonGenerator gen,
SerializerProvider provider) throws IOException
{
+ // 02-Dec-2016, tatu: As per comments above, can't determine single element so...
+ /*
if (((_unwrapSingle == null) &&
provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
@@ -60,43 +62,64 @@
return;
}
}
+ */
gen.writeStartArray();
serializeContents(value, gen, provider);
gen.writeEndArray();
}
@Override
- public void serializeContents(Iterator<?> value, JsonGenerator gen,
+ public void serializeContents(Iterator<?> value, JsonGenerator g,
SerializerProvider provider) throws IOException
{
- if (value.hasNext()) {
- final TypeSerializer typeSer = _valueTypeSerializer;
- JsonSerializer<Object> prevSerializer = null;
- Class<?> prevClass = null;
- do {
- Object elem = value.next();
- if (elem == null) {
- provider.defaultSerializeNull(gen);
- continue;
- }
- JsonSerializer<Object> currSerializer = _elementSerializer;
- if (currSerializer == null) {
- // Minor optimization to avoid most lookups:
- Class<?> cc = elem.getClass();
- if (cc == prevClass) {
- currSerializer = prevSerializer;
- } else {
- currSerializer = provider.findValueSerializer(cc, _property);
- prevSerializer = currSerializer;
- prevClass = cc;
- }
- }
- if (typeSer == null) {
- currSerializer.serialize(elem, gen, provider);
- } else {
- currSerializer.serializeWithType(elem, gen, provider, typeSer);
- }
- } while (value.hasNext());
+ if (!value.hasNext()) {
+ return;
}
+ JsonSerializer<Object> serializer = _elementSerializer;
+ if (serializer == null) {
+ _serializeDynamicContents(value, g, provider);
+ return;
+ }
+ final TypeSerializer typeSer = _valueTypeSerializer;
+ do {
+ Object elem = value.next();
+ if (elem == null) {
+ provider.defaultSerializeNull(g);
+ } else if (typeSer == null) {
+ serializer.serialize(elem, g, provider);
+ } else {
+ serializer.serializeWithType(elem, g, provider, typeSer);
+ }
+ } while (value.hasNext());
+ }
+
+ protected void _serializeDynamicContents(Iterator<?> value, JsonGenerator g,
+ SerializerProvider provider) throws IOException
+ {
+ final TypeSerializer typeSer = _valueTypeSerializer;
+ PropertySerializerMap serializers = _dynamicSerializers;
+ do {
+ Object elem = value.next();
+ if (elem == null) {
+ provider.defaultSerializeNull(g);
+ continue;
+ }
+ Class<?> cc = elem.getClass();
+ JsonSerializer<Object> serializer = serializers.serializerFor(cc);
+ if (serializer == null) {
+ if (_elementType.hasGenericTypes()) {
+ serializer = _findAndAddDynamic(serializers,
+ provider.constructSpecializedType(_elementType, cc), provider);
+ } else {
+ serializer = _findAndAddDynamic(serializers, cc, provider);
+ }
+ serializers = _dynamicSerializers;
+ }
+ if (typeSer == null) {
+ serializer.serialize(elem, g, provider);
+ } else {
+ serializer.serializeWithType(elem, g, provider, typeSer);
+ }
+ } while (value.hasNext());
}
}
\ No newline at end of file
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java
index 7deb535..27fc700 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/MapEntrySerializer.java
@@ -4,14 +4,19 @@
import java.util.Map;
import java.util.Map.Entry;
-import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.fasterxml.jackson.databind.util.ArrayBuilders;
+import com.fasterxml.jackson.databind.util.BeanUtil;
/**
* @since 2.5
@@ -23,6 +28,11 @@
implements ContextualSerializer
{
/**
+ * @since 2.9
+ */
+ public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY;
+
+ /**
* Map-valued property being serialized with this instance
*/
protected final BeanProperty _property;
@@ -35,11 +45,17 @@
protected final JavaType _entryType, _keyType, _valueType;
+ /*
+ /**********************************************************
+ /* Serializers used
+ /**********************************************************
+ */
+
/**
* Key serializer to use, if it can be statically determined
*/
protected JsonSerializer<Object> _keySerializer;
-
+
/**
* Value serializer to use, if it can be statically determined
*/
@@ -51,17 +67,43 @@
protected final TypeSerializer _valueTypeSerializer;
/**
- * If value type can not be statically determined, mapping from
+ * If value type cannot be statically determined, mapping from
* runtime value types to serializers are stored in this object.
*/
protected PropertySerializerMap _dynamicValueSerializers;
/*
/**********************************************************
+ /* Config settings, filtering
+ /**********************************************************
+ */
+
+ /**
+ * Value that indicates suppression mechanism to use for <b>values contained</b>;
+ * either "filter" (of which <code>equals()</code> is called), or marker
+ * value of {@link #MARKER_FOR_EMPTY}, or null to indicate no filtering for
+ * non-null values.
+ * Note that inclusion value for Map instance itself is handled by caller (POJO
+ * property that refers to the Map value).
+ *
+ * @since 2.5
+ */
+ protected final Object _suppressableValue;
+
+ /**
+ * Flag that indicates what to do with `null` values, distinct from
+ * handling of {@link #_suppressableValue}
+ *
+ * @since 2.9
+ */
+ protected final boolean _suppressNulls;
+
+ /*
+ /**********************************************************
/* Construction, initialization
/**********************************************************
*/
-
+
public MapEntrySerializer(JavaType type, JavaType keyType, JavaType valueType,
boolean staticTyping, TypeSerializer vts,
BeanProperty property)
@@ -74,13 +116,25 @@
_valueTypeSerializer = vts;
_property = property;
_dynamicValueSerializers = PropertySerializerMap.emptyForProperties();
+ _suppressableValue = null;
+ _suppressNulls = false;
}
- @SuppressWarnings("unchecked")
+ @Deprecated // since 2.9
protected MapEntrySerializer(MapEntrySerializer src, BeanProperty property,
TypeSerializer vts,
JsonSerializer<?> keySer, JsonSerializer<?> valueSer)
{
+ this(src, property, vts, keySer, valueSer,
+ src._suppressableValue, src._suppressNulls);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected MapEntrySerializer(MapEntrySerializer src, BeanProperty property,
+ TypeSerializer vts,
+ JsonSerializer<?> keySer, JsonSerializer<?> valueSer,
+ Object suppressableValue, boolean suppressNulls)
+ {
super(Map.class, false);
_entryType = src._entryType;
_keyType = src._keyType;
@@ -89,18 +143,40 @@
_valueTypeSerializer = src._valueTypeSerializer;
_keySerializer = (JsonSerializer<Object>) keySer;
_valueSerializer = (JsonSerializer<Object>) valueSer;
- _dynamicValueSerializers = src._dynamicValueSerializers;
+ // [databind#2181]: may not be safe to reuse, start from empty
+ _dynamicValueSerializers = PropertySerializerMap.emptyForProperties();
_property = src._property;
+ _suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
@Override
public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
- return new MapEntrySerializer(this, _property, vts, _keySerializer, _valueSerializer);
+ return new MapEntrySerializer(this, _property, vts, _keySerializer, _valueSerializer,
+ _suppressableValue, _suppressNulls);
}
+ /**
+ * @since 2.9
+ */
public MapEntrySerializer withResolved(BeanProperty property,
- JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer) {
- return new MapEntrySerializer(this, property, _valueTypeSerializer, keySerializer, valueSerializer);
+ JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
+ Object suppressableValue, boolean suppressNulls) {
+ return new MapEntrySerializer(this, property, _valueTypeSerializer,
+ keySerializer, valueSerializer, suppressableValue, suppressNulls);
+ }
+
+ /**
+ * @since 2.9
+ */
+ public MapEntrySerializer withContentInclusion(Object suppressableValue,
+ boolean suppressNulls) {
+ if ((_suppressableValue == suppressableValue)
+ && (_suppressNulls == suppressNulls)) {
+ return this;
+ }
+ return new MapEntrySerializer(this, _property, _valueTypeSerializer,
+ _keySerializer, _valueSerializer, suppressableValue, suppressNulls);
}
@Override
@@ -127,7 +203,7 @@
ser = _valueSerializer;
}
// [databind#124]: May have a content converter
- ser = findConvertingContentSerializer(provider, property, ser);
+ ser = findContextualConvertingSerializer(provider, property, ser);
if (ser == null) {
// 30-Sep-2012, tatu: One more thing -- if explicit content type is annotated,
// we can consider it a static case as well.
@@ -135,8 +211,6 @@
if (_valueTypeIsStatic && !_valueType.isJavaLangObject()) {
ser = provider.findValueSerializer(_valueType, property);
}
- } else {
- ser = provider.handleSecondaryContextualization(ser, property);
}
if (keySer == null) {
keySer = _keySerializer;
@@ -146,8 +220,59 @@
} else {
keySer = provider.handleSecondaryContextualization(keySer, property);
}
- MapEntrySerializer mser = withResolved(property, keySer, ser);
- // but note: no filtering, ignored entries or sorting (unlike Maps)
+
+ Object valueToSuppress = _suppressableValue;
+ boolean suppressNulls = _suppressNulls;
+ if (property != null) {
+ JsonInclude.Value inclV = property.findPropertyInclusion(provider.getConfig(), null);
+ if (inclV != null) {
+ JsonInclude.Include incl = inclV.getContentInclusion();
+ if (incl != JsonInclude.Include.USE_DEFAULTS) {
+ switch (incl) {
+ case NON_DEFAULT:
+ valueToSuppress = BeanUtil.getDefaultValue(_valueType);
+ suppressNulls = true;
+ if (valueToSuppress != null) {
+ if (valueToSuppress.getClass().isArray()) {
+ valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
+ }
+ }
+ break;
+ case NON_ABSENT:
+ suppressNulls = true;
+ valueToSuppress = _valueType.isReferenceType() ? MARKER_FOR_EMPTY : null;
+ break;
+ case NON_EMPTY:
+ suppressNulls = true;
+ valueToSuppress = MARKER_FOR_EMPTY;
+ break;
+ case CUSTOM:
+ valueToSuppress = provider.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = provider.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
+ case NON_NULL:
+ valueToSuppress = null;
+ suppressNulls = true;
+ break;
+ case ALWAYS: // default
+ default:
+ valueToSuppress = null;
+ // 30-Sep-2016, tatu: Should not need to check global flags here,
+ // if inclusion forced to be ALWAYS
+ suppressNulls = false;
+ break;
+ }
+ }
+ }
+ }
+
+ MapEntrySerializer mser = withResolved(property, keySer, ser,
+ valueToSuppress, suppressNulls);
+ // but note: no (full) filtering or sorting (unlike Maps)
return mser;
}
@@ -173,8 +298,33 @@
}
@Override
- public boolean isEmpty(SerializerProvider prov, Entry<?, ?> value) {
- return (value == null);
+ public boolean isEmpty(SerializerProvider prov, Entry<?, ?> entry)
+ {
+ Object value = entry.getValue();
+ if (value == null) {
+ return _suppressNulls;
+ }
+ if (_suppressableValue == null) {
+ return false;
+ }
+ JsonSerializer<Object> valueSer = _valueSerializer;
+ if (valueSer == null) {
+ // Let's not worry about generic types here, actually;
+ // unlikely to make any difference, but does add significant overhead
+ Class<?> cc = value.getClass();
+ valueSer = _dynamicValueSerializers.serializerFor(cc);
+ if (valueSer == null) {
+ try {
+ valueSer = _findAndAddDynamic(_dynamicValueSerializers, cc, prov);
+ } catch (JsonMappingException e) { // Ugh... cannot just throw as-is, so...
+ return false;
+ }
+ }
+ }
+ if (_suppressableValue == MARKER_FOR_EMPTY) {
+ return valueSer.isEmpty(prov, value);
+ }
+ return _suppressableValue.equals(value);
}
/*
@@ -188,112 +338,79 @@
throws IOException
{
gen.writeStartObject(value);
- if (_valueSerializer != null) {
- serializeUsing(value, gen, provider, _valueSerializer);
- } else {
- serializeDynamic(value, gen, provider);
- }
+ serializeDynamic(value, gen, provider);
gen.writeEndObject();
}
@Override
- public void serializeWithType(Map.Entry<?, ?> value, JsonGenerator gen, SerializerProvider provider,
- TypeSerializer typeSer) throws IOException
+ public void serializeWithType(Map.Entry<?, ?> value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException
{
- typeSer.writeTypePrefixForObject(value, gen);
// [databind#631]: Assign current value, to be accessible by custom serializers
- gen.setCurrentValue(value);
- if (_valueSerializer != null) {
- serializeUsing(value, gen, provider, _valueSerializer);
- } else {
- serializeDynamic(value, gen, provider);
- }
- typeSer.writeTypeSuffixForObject(value, gen);
+ g.setCurrentValue(value);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.START_OBJECT));
+ serializeDynamic(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
- protected void serializeDynamic(Map.Entry<?, ?> value, JsonGenerator jgen, SerializerProvider provider)
+ protected void serializeDynamic(Map.Entry<?, ?> value, JsonGenerator gen,
+ SerializerProvider provider)
throws IOException
{
- final JsonSerializer<Object> keySerializer = _keySerializer;
- final boolean skipNulls = !provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES);
final TypeSerializer vts = _valueTypeSerializer;
+ final Object keyElem = value.getKey();
- PropertySerializerMap serializers = _dynamicValueSerializers;
-
- Object valueElem = value.getValue();
- Object keyElem = value.getKey();
+ JsonSerializer<Object> keySerializer;
if (keyElem == null) {
- provider.findNullKeySerializer(_keyType, _property).serialize(null, jgen, provider);
+ keySerializer = provider.findNullKeySerializer(_keyType, _property);
} else {
- // [JACKSON-314] skip entries with null values?
- if (skipNulls && valueElem == null) return;
- keySerializer.serialize(keyElem, jgen, provider);
+ keySerializer = _keySerializer;
}
+ // or by value; nulls often suppressed
+ final Object valueElem = value.getValue();
+ JsonSerializer<Object> valueSer;
// And then value
if (valueElem == null) {
- provider.defaultSerializeNull(jgen);
- } else {
- Class<?> cc = valueElem.getClass();
- JsonSerializer<Object> ser = serializers.serializerFor(cc);
- if (ser == null) {
- if (_valueType.hasGenericTypes()) {
- ser = _findAndAddDynamic(serializers,
- provider.constructSpecializedType(_valueType, cc), provider);
- } else {
- ser = _findAndAddDynamic(serializers, cc, provider);
- }
- serializers = _dynamicValueSerializers;
+ if (_suppressNulls) {
+ return;
}
- try {
- if (vts == null) {
- ser.serialize(valueElem, jgen, provider);
- } else {
- ser.serializeWithType(valueElem, jgen, provider, vts);
+ valueSer = provider.getDefaultNullValueSerializer();
+ } else {
+ valueSer = _valueSerializer;
+ if (valueSer == null) {
+ Class<?> cc = valueElem.getClass();
+ valueSer = _dynamicValueSerializers.serializerFor(cc);
+ if (valueSer == null) {
+ if (_valueType.hasGenericTypes()) {
+ valueSer = _findAndAddDynamic(_dynamicValueSerializers,
+ provider.constructSpecializedType(_valueType, cc), provider);
+ } else {
+ valueSer = _findAndAddDynamic(_dynamicValueSerializers, cc, provider);
+ }
}
- } catch (Exception e) {
- // [JACKSON-55] Need to add reference information
- String keyDesc = ""+keyElem;
- wrapAndThrow(provider, e, value, keyDesc);
+ }
+ // also may need to skip non-empty values:
+ if (_suppressableValue != null) {
+ if (_suppressableValue == MARKER_FOR_EMPTY) {
+ if (valueSer.isEmpty(provider, valueElem)) {
+ return;
+ }
+ } if (_suppressableValue.equals(valueElem)) {
+ return;
+ }
}
}
- }
-
- /**
- * Method called to serialize fields, when the value type is statically known,
- * so that value serializer is passed and does not need to be fetched from
- * provider.
- */
- protected void serializeUsing(Map.Entry<?, ?> value, JsonGenerator jgen, SerializerProvider provider,
- JsonSerializer<Object> ser)
- throws IOException, JsonGenerationException
- {
- final JsonSerializer<Object> keySerializer = _keySerializer;
- final TypeSerializer vts = _valueTypeSerializer;
- final boolean skipNulls = !provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES);
-
- Object valueElem = value.getValue();
- Object keyElem = value.getKey();
- if (keyElem == null) {
- provider.findNullKeySerializer(_keyType, _property).serialize(null, jgen, provider);
- } else {
- // [JACKSON-314] also may need to skip entries with null values
- if (skipNulls && valueElem == null) return;
- keySerializer.serialize(keyElem, jgen, provider);
- }
- if (valueElem == null) {
- provider.defaultSerializeNull(jgen);
- } else {
- try {
- if (vts == null) {
- ser.serialize(valueElem, jgen, provider);
- } else {
- ser.serializeWithType(valueElem, jgen, provider, vts);
- }
- } catch (Exception e) {
- // [JACKSON-55] Need to add reference information
- String keyDesc = ""+keyElem;
- wrapAndThrow(provider, e, value, keyDesc);
+ keySerializer.serialize(keyElem, gen, provider);
+ try {
+ if (vts == null) {
+ valueSer.serialize(valueElem, gen, provider);
+ } else {
+ valueSer.serializeWithType(valueElem, gen, provider, vts);
}
+ } catch (Exception e) {
+ String keyDesc = ""+keyElem;
+ wrapAndThrow(provider, e, value, keyDesc);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/ObjectIdWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/ObjectIdWriter.java
index 42b4972..e532ad0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/ObjectIdWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/ObjectIdWriter.java
@@ -68,14 +68,7 @@
ObjectIdGenerator<?> generator, boolean alwaysAsId)
{
String simpleName = (propName == null) ? null : propName.getSimpleName();
- return construct(idType, simpleName, generator, alwaysAsId);
- }
-
- @Deprecated // since 2.3
- public static ObjectIdWriter construct(JavaType idType, String propName,
- ObjectIdGenerator<?> generator, boolean alwaysAsId)
- {
- SerializableString serName = (propName == null) ? null : new SerializedString(propName);
+ SerializableString serName = (simpleName == null) ? null : new SerializedString(simpleName);
return new ObjectIdWriter(idType, serName, generator, null, alwaysAsId);
}
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 b44ad4b..14f1802 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
@@ -42,7 +42,7 @@
/**
* Main lookup method. Takes a "raw" type since usage is always from
- * place where parameterization is fixed such that there can not be
+ * place where parameterization is fixed such that there cannot be
* type-parametric variations.
*/
public abstract JsonSerializer<Object> serializerFor(Class<?> type);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java
index bbd9e23..1011bab 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java
@@ -107,11 +107,9 @@
ser = _elementSerializer;
}
// May have a content converter
- ser = findConvertingContentSerializer(provider, property, ser);
+ ser = findContextualConvertingSerializer(provider, property, ser);
if (ser == null) {
ser = provider.findValueSerializer(String.class, property);
- } else {
- ser = provider.handleSecondaryContextualization(ser, property);
}
// Optimization: default serializer just writes String, so we can avoid a call:
if (isDefaultSerializer(ser)) {
@@ -142,7 +140,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, String[] value) {
- return (value == null) || (value.length == 0);
+ return (value.length == 0);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java
index b45d8e9..b936eac 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java
@@ -4,6 +4,7 @@
import java.util.*;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
@@ -36,17 +37,16 @@
}
protected StringCollectionSerializer(StringCollectionSerializer src,
- JsonSerializer<?> ser, Boolean unwrapSingle)
+ Boolean unwrapSingle)
{
- super(src, ser, unwrapSingle);
+ super(src, unwrapSingle);
}
@Override
- public JsonSerializer<?> _withResolved(BeanProperty prop,
- JsonSerializer<?> ser, Boolean unwrapSingle) {
- return new StringCollectionSerializer(this, ser, unwrapSingle);
+ public JsonSerializer<?> _withResolved(BeanProperty prop, Boolean unwrapSingle) {
+ return new StringCollectionSerializer(this, unwrapSingle);
}
-
+
@Override protected JsonNode contentSchema() {
return createSchemaNode("string", true);
}
@@ -64,88 +64,53 @@
*/
@Override
- public void serialize(Collection<String> value, JsonGenerator gen,
+ public void serialize(Collection<String> value, JsonGenerator g,
SerializerProvider provider) throws IOException
{
+ g.setCurrentValue(value);
final int len = value.size();
if (len == 1) {
if (((_unwrapSingle == null) &&
provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- _serializeUnwrapped(value, gen, provider);
+ serializeContents(value, g, provider);
return;
}
- }
- gen.writeStartArray(len);
- if (_serializer == null) {
- serializeContents(value, gen, provider);
- } else {
- serializeUsingCustom(value, gen, provider);
}
- gen.writeEndArray();
- }
-
- private final void _serializeUnwrapped(Collection<String> value, JsonGenerator gen,
- SerializerProvider provider) throws IOException
- {
- if (_serializer == null) {
- serializeContents(value, gen, provider);
- } else {
- serializeUsingCustom(value, gen, provider);
- }
+ g.writeStartArray(len);
+ serializeContents(value, g, provider);
+ g.writeEndArray();
}
@Override
- public void serializeWithType(Collection<String> value, JsonGenerator jgen, SerializerProvider provider,
- TypeSerializer typeSer)
- throws IOException, JsonGenerationException
+ public void serializeWithType(Collection<String> value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer)
+ throws IOException
{
- typeSer.writeTypePrefixForArray(value, jgen);
- if (_serializer == null) {
- serializeContents(value, jgen, provider);
- } else {
- serializeUsingCustom(value, jgen, provider);
- }
- typeSer.writeTypeSuffixForArray(value, jgen);
+ g.setCurrentValue(value);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.START_ARRAY));
+ serializeContents(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
- private final void serializeContents(Collection<String> value, JsonGenerator jgen, SerializerProvider provider)
- throws IOException, JsonGenerationException
+ private final void serializeContents(Collection<String> value, JsonGenerator g,
+ SerializerProvider provider)
+ throws IOException
{
- if (_serializer != null) {
- serializeUsingCustom(value, jgen, provider);
- return;
- }
int i = 0;
- for (String str : value) {
- try {
+
+ try {
+ for (String str : value) {
if (str == null) {
- provider.defaultSerializeNull(jgen);
+ provider.defaultSerializeNull(g);
} else {
- jgen.writeString(str);
+ g.writeString(str);
}
++i;
- } catch (Exception e) {
- wrapAndThrow(provider, e, value, i);
}
+ } catch (Exception e) {
+ wrapAndThrow(provider, e, value, i);
}
}
-
- private void serializeUsingCustom(Collection<String> value, JsonGenerator jgen, SerializerProvider provider)
- throws IOException, JsonGenerationException
- {
- final JsonSerializer<String> ser = _serializer;
- int i = 0;
- for (String str : value) {
- try {
- if (str == null) {
- provider.defaultSerializeNull(jgen);
- } else {
- ser.serialize(str, jgen, provider);
- }
- } catch (Exception e) {
- wrapAndThrow(provider, e, value, i);
- }
- }
- }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/TypeWrappedSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/TypeWrappedSerializer.java
index e3837a6..62ad61d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/TypeWrappedSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/TypeWrappedSerializer.java
@@ -3,10 +3,9 @@
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
-
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
* Simple serializer that will call configured type serializer, passing
@@ -15,6 +14,7 @@
*/
public final class TypeWrappedSerializer
extends JsonSerializer<Object>
+ implements ContextualSerializer // since 2.9
{
final protected TypeSerializer _typeSerializer;
final protected JsonSerializer<Object> _serializer;
@@ -28,25 +28,45 @@
}
@Override
- public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
- _serializer.serializeWithType(value, jgen, provider, _typeSerializer);
+ public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException {
+ _serializer.serializeWithType(value, g, provider, _typeSerializer);
}
@Override
- public void serializeWithType(Object value, JsonGenerator jgen, SerializerProvider provider,
+ public void serializeWithType(Object value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer) throws IOException
{
- /* Is this an erroneous call? For now, let's assume it is not, and
- * that type serializer is just overridden if so
- */
- _serializer.serializeWithType(value, jgen, provider, typeSer);
+ // Is this an erroneous call? For now, let's assume it is not, and
+ // that type serializer is just overridden if so
+ _serializer.serializeWithType(value, g, provider, typeSer);
}
-
+
@Override
public Class<Object> handledType() { return Object.class; }
/*
/**********************************************************
+ /* ContextualDeserializer
+ /**********************************************************
+ */
+
+ @Override // since 2.9
+ public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)
+ throws JsonMappingException
+ {
+ // 13-Mar-2017, tatu: Should we call `TypeSerializer.forProperty()`?
+ JsonSerializer<?> ser = _serializer;
+ if (ser instanceof ContextualSerializer) {
+ ser = provider.handleSecondaryContextualization(ser, property);
+ }
+ if (ser == _serializer) {
+ return this;
+ }
+ return new TypeWrappedSerializer(_typeSerializer, ser);
+ }
+
+ /*
+ /**********************************************************
/* Extended API for other core classes
/**********************************************************
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnknownSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnknownSerializer.java
index 6c1530b..70b53e0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnknownSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnknownSerializer.java
@@ -4,6 +4,7 @@
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -43,8 +44,9 @@
if (provider.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS)) {
failForEmpty(provider, value);
}
- typeSer.writeTypePrefixForObject(value, gen);
- typeSer.writeTypeSuffixForObject(value, gen);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
+ typeSer.typeId(value, JsonToken.START_OBJECT));
+ typeSer.writeTypeSuffix(gen, typeIdDef);
}
@Override
@@ -66,7 +68,8 @@
protected void failForEmpty(SerializerProvider prov, Object value)
throws JsonMappingException {
- prov.reportMappingProblem("No serializer found for class %s and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)",
- value.getClass().getName());
+ prov.reportBadDefinition(handledType(), String.format(
+ "No serializer found for class %s and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)",
+ value.getClass().getName()));
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanPropertyWriter.java
index 9b558ce..c6498b0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanPropertyWriter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanPropertyWriter.java
@@ -134,14 +134,17 @@
@Override
public void assignSerializer(JsonSerializer<Object> ser)
{
- super.assignSerializer(ser);
- if (_serializer != null) {
+ if (ser != null) {
NameTransformer t = _nameTransformer;
- if (_serializer.isUnwrappingSerializer()) {
- t = NameTransformer.chainedTransformer(t, ((UnwrappingBeanSerializer) _serializer)._nameTransformer);
+ if (ser.isUnwrappingSerializer()
+ // as per [databind#2060], need to also check this, in case someone writes
+ // custom implementation that does not extend standard implementation:
+ && (ser instanceof UnwrappingBeanSerializer)) {
+ t = NameTransformer.chainedTransformer(t, ((UnwrappingBeanSerializer) ser)._nameTransformer);
}
- _serializer = _serializer.unwrappingSerializer(t);
+ ser = ser.unwrappingSerializer(t);
}
+ super.assignSerializer(ser);
}
/*
@@ -210,8 +213,11 @@
serializer = provider.findValueSerializer(type, this);
}
NameTransformer t = _nameTransformer;
- if (serializer.isUnwrappingSerializer()) {
- t = NameTransformer.chainedTransformer(t, ((UnwrappingBeanSerializer) serializer)._nameTransformer);
+ if (serializer.isUnwrappingSerializer()
+ // as per [databind#2060], need to also check this, in case someone writes
+ // custom implementation that does not extend standard implementation:
+ && (serializer instanceof UnwrappingBeanSerializer)) {
+ t = NameTransformer.chainedTransformer(t, ((UnwrappingBeanSerializer) serializer)._nameTransformer);
}
serializer = serializer.unwrappingSerializer(t);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java
index 834e03a..3b91926 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java
@@ -87,7 +87,7 @@
}
/**
- * JSON Array output can not be done if unwrapping operation is
+ * JSON Array output cannot be done if unwrapping operation is
* requested; so implementation will simply return 'this'.
*/
@Override
@@ -126,7 +126,8 @@
TypeSerializer typeSer) throws IOException
{
if (provider.isEnabled(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS)) {
- provider.reportMappingProblem("Unwrapped property requires use of type information: can not serialize without disabling `SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS`");
+ provider.reportBadDefinition(handledType(),
+ "Unwrapped property requires use of type information: cannot serialize without disabling `SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS`");
}
gen.setCurrentValue(bean); // [databind#631]
if (_objectIdWriter != null) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/WritableObjectId.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/WritableObjectId.java
index d842694..af891b3 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/WritableObjectId.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/WritableObjectId.java
@@ -64,12 +64,17 @@
// 03-Aug-2013, tatu: Prefer Native Object Ids if available
if (gen.canWriteObjectId()) {
// Need to assume String(ified) ids, for now... could add 'long' variant?
- gen.writeObjectId(String.valueOf(id));
+ // 05-Feb-2019, tatu: But in special case of `null` we should not coerce -- whether
+ // we should even call is an open question, but for now do pass to let generator
+ // decide what to do, if anything.
+ String idStr = (id == null) ? null : String.valueOf(id);
+ gen.writeObjectId(idStr);
return;
}
-
+
SerializableString name = w.propertyName;
if (name != null) {
+ // 05-Feb-2019, tatu: How about `null` id? For now, write
gen.writeFieldName(name);
w.serializer.serialize(id, gen, provider);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java
index c89409c..69af378 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java
@@ -4,6 +4,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.*;
@@ -29,7 +30,7 @@
* @since 2.6
*/
protected final Boolean _unwrapSingle;
-
+
protected ArraySerializerBase(Class<T> cls)
{
super(cls);
@@ -111,9 +112,7 @@
@Override
public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException
{
- if (((_unwrapSingle == null) &&
- provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
- || (_unwrapSingle == Boolean.TRUE)) {
+ if (_shouldUnwrapSingle(provider)) {
if (hasSingleElement(value)) {
serializeContents(value, gen, provider);
return;
@@ -127,17 +126,28 @@
}
@Override
- public final void serializeWithType(T value, JsonGenerator gen, SerializerProvider provider,
+ public final void serializeWithType(T value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException
{
- typeSer.writeTypePrefixForArray(value, gen);
// [databind#631]: Assign current value, to be accessible by custom serializers
- gen.setCurrentValue(value);
- serializeContents(value, gen, provider);
- typeSer.writeTypeSuffixForArray(value, gen);
+ g.setCurrentValue(value);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.START_ARRAY));
+ serializeContents(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
-
+
protected abstract void serializeContents(T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException;
+
+ /**
+ * @since 2.9
+ */
+ protected final boolean _shouldUnwrapSingle(SerializerProvider provider) {
+ if (_unwrapSingle == null) {
+ return provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);
+ }
+ return _unwrapSingle.booleanValue();
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java
index a1bd6b9..87647ba 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java
@@ -6,7 +6,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.*;
-
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
@@ -56,7 +56,7 @@
protected final JsonSerializer<Object> _elementSerializer;
/**
- * If element type can not be statically determined, mapping from
+ * If element type cannot be statically determined, mapping from
* runtime type to serializer is handled using this object
*/
protected PropertySerializerMap _dynamicSerializers;
@@ -118,7 +118,8 @@
_valueTypeSerializer = vts;
_property = property;
_elementSerializer = (JsonSerializer<Object>) elementSerializer;
- _dynamicSerializers = src._dynamicSerializers;
+ // [databind#2181]: may not be safe to reuse, start from empty
+ _dynamicSerializers = PropertySerializerMap.emptyForProperties();
_unwrapSingle = unwrapSingle;
}
@@ -191,7 +192,7 @@
ser = _elementSerializer;
}
// 18-Feb-2013, tatu: May have a content converter:
- ser = findConvertingContentSerializer(serializers, property, ser);
+ ser = findContextualConvertingSerializer(serializers, property, ser);
if (ser == null) {
// 30-Sep-2012, tatu: One more thing -- if explicit content type is annotated,
// we can consider it a static case as well.
@@ -200,8 +201,6 @@
ser = serializers.findValueSerializer(_elementType, property);
}
}
- } else {
- ser = serializers.handleSecondaryContextualization(ser, property);
}
if ((ser != _elementSerializer)
|| (property != _property)
@@ -253,15 +252,15 @@
}
@Override
- public void serializeWithType(T value, JsonGenerator gen, SerializerProvider provider,
+ public void serializeWithType(T value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer) throws IOException
{
- // note: let's NOT consider [JACKSON-805] here; gets too complicated, and probably just won't work
- typeSer.writeTypePrefixForArray(value, gen);
// [databind#631]: Assign current value, to be accessible by custom serializers
- gen.setCurrentValue(value);
- serializeContents(value, gen, provider);
- typeSer.writeTypeSuffixForArray(value, gen);
+ g.setCurrentValue(value);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.START_ARRAY));
+ serializeContents(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
protected abstract void serializeContents(T value, JsonGenerator gen, SerializerProvider provider)
@@ -273,15 +272,10 @@
throws JsonMappingException
{
ObjectNode o = createSchemaNode("array", true);
- JavaType contentType = _elementType;
- if (contentType != null) {
+ if (_elementSerializer != null) {
JsonNode schemaNode = null;
- // 15-Oct-2010, tatu: We can't serialize plain Object.class; but what should it produce here? Untyped?
- if (contentType.getRawClass() != Object.class) {
- JsonSerializer<Object> ser = provider.findValueSerializer(contentType, _property);
- if (ser instanceof SchemaAware) {
- schemaNode = ((SchemaAware) ser).getSchema(provider, null);
- }
+ if (_elementSerializer instanceof SchemaAware) {
+ schemaNode = ((SchemaAware) _elementSerializer).getSchema(provider, null);
}
if (schemaNode == null) {
schemaNode = com.fasterxml.jackson.databind.jsonschema.JsonSchema.getDefaultSchemaNode();
@@ -297,7 +291,11 @@
{
JsonSerializer<?> valueSer = _elementSerializer;
if (valueSer == null) {
- valueSer = visitor.getProvider().findValueSerializer(_elementType, _property);
+ // 19-Oct-2016, tatu: Apparently we get null for untyped/raw `EnumSet`s... not 100%
+ // sure what'd be the clean way but let's try this for now:
+ if (_elementType != null) {
+ valueSer = visitor.getProvider().findValueSerializer(_elementType, _property);
+ }
}
visitArrayFormat(visitor, typeHint, valueSer, _elementType);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java
index 7726bb0..d44d9af 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java
@@ -2,8 +2,6 @@
import java.util.concurrent.atomic.AtomicReference;
-import com.fasterxml.jackson.annotation.JsonInclude;
-
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.type.ReferenceType;
@@ -29,23 +27,28 @@
protected AtomicReferenceSerializer(AtomicReferenceSerializer base, BeanProperty property,
TypeSerializer vts, JsonSerializer<?> valueSer,
NameTransformer unwrapper,
- JsonInclude.Include contentIncl)
+ Object suppressableValue, boolean suppressNulls)
{
- super(base, property, vts, valueSer, unwrapper, contentIncl);
+ super(base, property, vts, valueSer, unwrapper,
+ suppressableValue, suppressNulls);
}
@Override
- protected AtomicReferenceSerializer withResolved(BeanProperty prop,
+ protected ReferenceTypeSerializer<AtomicReference<?>> withResolved(BeanProperty prop,
TypeSerializer vts, JsonSerializer<?> valueSer,
- NameTransformer unwrapper,
- JsonInclude.Include contentIncl)
+ NameTransformer unwrapper)
{
- if ((_property == prop) && (contentIncl == _contentInclusion)
- && (_valueTypeSerializer == vts) && (_valueSerializer == valueSer)
- && (_unwrapper == unwrapper)) {
- return this;
- }
- return new AtomicReferenceSerializer(this, prop, vts, valueSer, unwrapper, contentIncl);
+ return new AtomicReferenceSerializer(this, prop, vts, valueSer, unwrapper,
+ _suppressableValue, _suppressNulls);
+ }
+
+ @Override
+ public ReferenceTypeSerializer<AtomicReference<?>> withContentInclusion(Object suppressableValue,
+ boolean suppressNulls)
+ {
+ return new AtomicReferenceSerializer(this, _property, _valueTypeSerializer,
+ _valueSerializer, _unwrapper,
+ suppressableValue, suppressNulls);
}
/*
@@ -55,8 +58,8 @@
*/
@Override
- protected boolean _isValueEmpty(AtomicReference<?> value) {
- return value.get() == null;
+ protected boolean _isValuePresent(AtomicReference<?> value) {
+ return value.get() != null;
}
@Override
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 ab0850e..a95d812 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
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
@@ -17,6 +18,7 @@
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.*;
+import com.fasterxml.jackson.databind.ser.impl.MapEntrySerializer;
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
import com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator;
import com.fasterxml.jackson.databind.ser.impl.WritableObjectId;
@@ -37,7 +39,7 @@
JsonFormatVisitable, SchemaAware
{
protected final static PropertyName NAME_FOR_OBJECT_REF = new PropertyName("#object-ref");
-
+
final protected static BeanPropertyWriter[] NO_PROPS = new BeanPropertyWriter[0];
/*
@@ -47,6 +49,11 @@
*/
/**
+ * @since 2.9
+ */
+ final protected JavaType _beanType;
+
+ /**
* Writers used for outputting actual property values
*/
final protected BeanPropertyWriter[] _props;
@@ -103,6 +110,7 @@
BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties)
{
super(type);
+ _beanType = type;
_props = properties;
_filteredProps = filteredProperties;
if (builder == null) { // mostly for testing
@@ -125,6 +133,7 @@
BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties)
{
super(src._handledType);
+ _beanType = src._beanType;
_props = properties;
_filteredProps = filteredProperties;
@@ -148,6 +157,7 @@
ObjectIdWriter objectIdWriter, Object filterId)
{
super(src._handledType);
+ _beanType = src._beanType;
_props = src._props;
_filteredProps = src._filteredProps;
@@ -168,6 +178,7 @@
{
super(src._handledType);
+ _beanType = src._beanType;
final BeanPropertyWriter[] propsIn = src._props;
final BeanPropertyWriter[] fpropsIn = src._filteredProps;
final int len = propsIn.length;
@@ -318,9 +329,6 @@
// It not, we can use declared return type if and only if declared type is final:
// if not, we don't really know the actual type until we get the instance.
if (type == null) {
- // 30-Oct-2015, tatu: Not sure why this was used
-// type = provider.constructType(prop.getGenericPropertyType());
- // but this looks better
type = prop.getType();
if (!type.isFinal()) {
if (type.isContainerType() || type.containedTypeCount() > 0) {
@@ -346,14 +354,18 @@
}
}
}
- prop.assignSerializer(ser);
- // and maybe replace filtered property too? (see [JACKSON-364])
+ // and maybe replace filtered property too?
if (i < filteredCount) {
BeanPropertyWriter w2 = _filteredProps[i];
if (w2 != null) {
w2.assignSerializer(ser);
+ // 17-Mar-2017, tatu: Typically will lead to chained call to original property,
+ // which would lead to double set. Not a problem itself, except... unwrapping
+ // may require work to be done, which does lead to an actual issue.
+ continue;
}
}
+ prop.assignSerializer(ser);
}
// also, any-getter may need to be resolved
@@ -418,11 +430,27 @@
case NUMBER_INT:
// 12-Oct-2014, tatu: May need to introspect full annotations... but
// for now, just do class ones
- BeanDescription desc = config.introspectClassAnnotations(_handledType);
- JsonSerializer<?> ser = EnumSerializer.construct(_handledType,
+ BeanDescription desc = config.introspectClassAnnotations(_beanType);
+ JsonSerializer<?> ser = EnumSerializer.construct(_beanType.getRawClass(),
provider.getConfig(), desc, format);
return provider.handlePrimaryContextualization(ser, property);
}
+ // 16-Oct-2016, tatu: Ditto for `Map`, `Map.Entry` subtypes
+ } else if (shape == JsonFormat.Shape.NATURAL) {
+ if (_beanType.isMapLikeType() && Map.class.isAssignableFrom(_handledType)) {
+ ;
+ } else if (Map.Entry.class.isAssignableFrom(_handledType)) {
+ JavaType mapEntryType = _beanType.findSuperType(Map.Entry.class);
+
+ JavaType kt = mapEntryType.containedTypeOrUnknown(0);
+ JavaType vt = mapEntryType.containedTypeOrUnknown(1);
+
+ // 16-Oct-2016, tatu: could have problems with type handling, as we do not
+ // see if "static" typing is needed, nor look for `TypeSerializer` yet...
+ JsonSerializer<?> ser = new MapEntrySerializer(_beanType, kt, vt,
+ false, null, property);
+ return provider.handlePrimaryContextualization(ser, property);
+ }
}
}
}
@@ -461,17 +489,17 @@
String propName = objectIdInfo.getPropertyName().getSimpleName();
BeanPropertyWriter idProp = null;
- for (int i = 0, len = _props.length ;; ++i) {
+ for (int i = 0, len = _props.length; ; ++i) {
if (i == len) {
- throw new IllegalArgumentException("Invalid Object Id definition for "+_handledType.getName()
- +": can not find property with name '"+propName+"'");
+ provider.reportBadDefinition(_beanType, String.format(
+ "Invalid Object Id definition for %s: cannot find property with name '%s'",
+ handledType().getName(), propName));
}
BeanPropertyWriter prop = _props[i];
if (propName.equals(prop.getName())) {
idProp = prop;
- /* Let's force it to be the first property to output
- * (although it may still get rearranged etc)
- */
+ // Let's force it to be the first property to output
+ // (although it may still get rearranged etc)
if (i > 0) { // note: must shuffle both regular properties and filtered
System.arraycopy(_props, 0, _props, 1, i);
_props[0] = idProp;
@@ -493,7 +521,6 @@
objectIdInfo.getAlwaysAsId());
}
}
-
// Or change Filter Id in use?
Object filterId = intr.findFilterId(accessor);
if (filterId != null) {
@@ -522,6 +549,7 @@
if (shape == null) {
shape = _serializationShape;
}
+ // last but not least; may need to transmute into as-array serialization
if (shape == JsonFormat.Shape.ARRAY) {
return contextual.asArraySerializer();
}
@@ -567,23 +595,15 @@
return;
}
- String typeStr = (_typeId == null) ? null : _customTypeId(bean);
- if (typeStr == null) {
- typeSer.writeTypePrefixForObject(bean, gen);
- } else {
- typeSer.writeCustomTypePrefixForObject(bean, gen, typeStr);
- }
gen.setCurrentValue(bean); // [databind#631]
+ WritableTypeId typeIdDef = _typeIdDef(typeSer, bean, JsonToken.START_OBJECT);
+ typeSer.writeTypePrefix(gen, typeIdDef);
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, gen, provider);
} else {
serializeFields(bean, gen, provider);
}
- if (typeStr == null) {
- typeSer.writeTypeSuffixForObject(bean, gen);
- } else {
- typeSer.writeCustomTypeSuffixForObject(bean, gen, typeStr);
- }
+ typeSer.writeTypeSuffix(gen, typeIdDef);
}
protected final void _serializeWithObjectId(Object bean, JsonGenerator gen, SerializerProvider provider,
@@ -630,33 +650,43 @@
w.serializer.serialize(id, gen, provider);
return;
}
-
_serializeObjectId(bean, gen, provider, typeSer, objectId);
}
- protected void _serializeObjectId(Object bean, JsonGenerator gen,SerializerProvider provider,
+ protected void _serializeObjectId(Object bean, JsonGenerator g,
+ SerializerProvider provider,
TypeSerializer typeSer, WritableObjectId objectId) throws IOException
{
final ObjectIdWriter w = _objectIdWriter;
- String typeStr = (_typeId == null) ? null :_customTypeId(bean);
- if (typeStr == null) {
- typeSer.writeTypePrefixForObject(bean, gen);
- } else {
- typeSer.writeCustomTypePrefixForObject(bean, gen, typeStr);
- }
- objectId.writeAsField(gen, provider, w);
+ WritableTypeId typeIdDef = _typeIdDef(typeSer, bean, JsonToken.START_OBJECT);
+
+ typeSer.writeTypePrefix(g, typeIdDef);
+ objectId.writeAsField(g, provider, w);
if (_propertyFilterId != null) {
- serializeFieldsFiltered(bean, gen, provider);
+ serializeFieldsFiltered(bean, g, provider);
} else {
- serializeFields(bean, gen, provider);
+ serializeFields(bean, g, provider);
}
- if (typeStr == null) {
- typeSer.writeTypeSuffixForObject(bean, gen);
- } else {
- typeSer.writeCustomTypeSuffixForObject(bean, gen, typeStr);
- }
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
-
+
+ /**
+ * @since 2.9
+ */
+ protected final WritableTypeId _typeIdDef(TypeSerializer typeSer,
+ Object bean, JsonToken valueShape) {
+ if (_typeId == null) {
+ return typeSer.typeId(bean, valueShape);
+ }
+ Object typeId = _typeId.getValue(bean);
+ if (typeId == null) {
+ // 28-Jun-2017, tatu: Is this really needed? Unchanged from 2.8 but...
+ typeId = "";
+ }
+ return typeSer.typeId(bean, valueShape, typeId);
+ }
+
+ @Deprecated // since 2.9
protected final String _customTypeId(Object bean)
{
final Object typeId = _typeId.getValue(bean);
@@ -665,7 +695,7 @@
}
return (typeId instanceof String) ? (String) typeId : typeId.toString();
}
-
+
/*
/**********************************************************
/* Field serialization methods
@@ -696,10 +726,9 @@
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
wrapAndThrow(provider, e, bean, name);
} catch (StackOverflowError e) {
- /* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
- * have many stack frames to spare... just one or two; can't
- * make many calls.
- */
+ // 04-Sep-2009, tatu: Dealing with this is tricky, since we don't have many
+ // stack frames to spare... just one or two; can't make many calls.
+
// 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly:
//JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e);
JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/BooleanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/BooleanSerializer.java
index a69a88c..135add0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/BooleanSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/BooleanSerializer.java
@@ -3,25 +3,32 @@
import java.io.IOException;
import java.lang.reflect.Type;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
-
+import com.fasterxml.jackson.core.JsonParser.NumberType;
+import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
* Serializer used for primitive boolean, as well as java.util.Boolean
* wrapper type.
*<p>
- * Since this is one of "native" types, no type information is ever
- * included on serialization (unlike for most scalar types as of 1.5)
+ * Since this is one of "natural" (aka "native") types, no type information is ever
+ * included on serialization (unlike for most other scalar types)
*/
@JacksonStdImpl
public final class BooleanSerializer
- extends NonTypedScalarSerializerBase<Boolean>
+//In 2.9, removed use of intermediate type `NonTypedScalarSerializerBase`
+ extends StdScalarSerializer<Object>
+ implements ContextualSerializer
{
private static final long serialVersionUID = 1L;
@@ -32,25 +39,106 @@
protected final boolean _forPrimitive;
public BooleanSerializer(boolean forPrimitive) {
- super(Boolean.class);
+ super(forPrimitive ? Boolean.TYPE : Boolean.class, false);
_forPrimitive = forPrimitive;
}
@Override
- public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
- jgen.writeBoolean(value.booleanValue());
+ public JsonSerializer<?> createContextual(SerializerProvider serializers,
+ BeanProperty property) throws JsonMappingException
+ {
+ JsonFormat.Value format = findFormatOverrides(serializers,
+ property, Boolean.class);
+ if (format != null) {
+ JsonFormat.Shape shape = format.getShape();
+ if (shape.isNumeric()) {
+ return new AsNumber(_forPrimitive);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException {
+ g.writeBoolean(Boolean.TRUE.equals(value));
+ }
+
+ @Override
+ public final void serializeWithType(Object value, JsonGenerator g, SerializerProvider provider,
+ TypeSerializer typeSer) throws IOException
+ {
+ g.writeBoolean(Boolean.TRUE.equals(value));
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode("boolean", !_forPrimitive);
}
-
+
@Override
- public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
+ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
+ visitor.expectBooleanFormat(typeHint);
+ }
+
+ /**
+ * Alternate implementation that is used when values are to be serialized
+ * as numbers <code>0</code> (false) or <code>1</code> (true).
+ *
+ * @since 2.9
+ */
+ final static class AsNumber
+ extends StdScalarSerializer<Object>
+ implements ContextualSerializer
{
- if (visitor != null) {
- visitor.expectBooleanFormat(typeHint);
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Whether type serialized is primitive (boolean) or wrapper
+ * (java.lang.Boolean); if true, former, if false, latter.
+ */
+ protected final boolean _forPrimitive;
+
+ public AsNumber(boolean forPrimitive) {
+ super(forPrimitive ? Boolean.TYPE : Boolean.class, false);
+ _forPrimitive = forPrimitive;
+ }
+
+ @Override
+ public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException {
+ g.writeNumber((Boolean.FALSE.equals(value)) ? 0 : 1);
+ }
+
+ @Override
+ public final void serializeWithType(Object value, JsonGenerator g, SerializerProvider provider,
+ TypeSerializer typeSer) throws IOException
+ {
+ // 27-Mar-2017, tatu: Actually here we CAN NOT serialize as number without type,
+ // since with natural types that would map to number, not boolean. So choice
+ // comes to between either add type id, or serialize as boolean. Choose
+ // latter at this point
+ g.writeBoolean(Boolean.TRUE.equals(value));
+ }
+
+ @Override
+ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
+ // 27-Mar-2017, tatu: As usual, bit tricky but... seems like we should call
+ // visitor for actual representation
+ visitIntFormat(visitor, typeHint, NumberType.INT);
+ }
+
+ @Override
+ public JsonSerializer<?> createContextual(SerializerProvider serializers,
+ BeanProperty property) throws JsonMappingException
+ {
+ JsonFormat.Value format = findFormatOverrides(serializers,
+ property, Boolean.class);
+ if (format != null) {
+ JsonFormat.Shape shape = format.getShape();
+ if (!shape.isNumeric()) {
+ return new BooleanSerializer(_forPrimitive);
+ }
+ }
+ return this;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteArraySerializer.java
index e19ba93..69c2ae6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteArraySerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteArraySerializer.java
@@ -4,6 +4,8 @@
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -20,7 +22,7 @@
* as numbers. Instead, we assume that it would make more sense to output content
* as base64 encoded bytes (using default base64 encoding).
*<p>
- * NOTE: since it is NOT serialized as an array, can not use AsArraySerializer as base
+ * NOTE: since it is NOT serialized as an array, cannot use AsArraySerializer as base
*<p>
* NOTE: since 2.6, has been a main-level class; earlier was embedded in
* {@link StdArraySerializers}.
@@ -36,7 +38,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, byte[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -52,12 +54,21 @@
TypeSerializer typeSer)
throws IOException
{
+ // most likely scalar
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.VALUE_EMBEDDED_OBJECT));
+ g.writeBinary(provider.getConfig().getBase64Variant(),
+ value, 0, value.length);
+ typeSer.writeTypeSuffix(g, typeIdDef);
+
+ /* OLD impl
typeSer.writeTypePrefixForScalar(value, g);
g.writeBinary(provider.getConfig().getBase64Variant(),
value, 0, value.length);
typeSer.writeTypeSuffixForScalar(value, g);
+ */
}
-
+
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
@@ -65,7 +76,7 @@
ObjectNode itemSchema = createSchemaNode("byte"); //binary values written as strings?
return o.set("items", itemSchema);
}
-
+
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException
@@ -76,11 +87,9 @@
//
// TODO: for 2.8, make work either as String/base64, or array of numbers,
// with a qualifier that can be used to determine it's byte[]
- if (visitor != null) {
- JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
- if (v2 != null) {
- v2.itemsFormat(JsonFormatTypes.INTEGER);
- }
+ JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
+ if (v2 != null) {
+ v2.itemsFormat(JsonFormatTypes.INTEGER);
}
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteBufferSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteBufferSerializer.java
index e838001..302b4ed 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteBufferSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteBufferSerializer.java
@@ -5,6 +5,9 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream;
@SuppressWarnings("serial")
@@ -17,7 +20,7 @@
{
// first, simple case when wrapping an array...
if (bbuf.hasArray()) {
- gen.writeBinary(bbuf.array(), 0, bbuf.limit());
+ gen.writeBinary(bbuf.array(), bbuf.arrayOffset(), bbuf.limit());
return;
}
// the other case is more complicated however. Best to handle with InputStream wrapper.
@@ -30,4 +33,15 @@
gen.writeBinary(in, copy.remaining());
in.close();
}
+
+ @Override // since 2.9
+ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
+ throws JsonMappingException
+ {
+ // 31-Mar-2017, tatu: Use same type as `ByteArraySerializer`: not optimal but has to do
+ JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
+ if (v2 != null) {
+ v2.itemsFormat(JsonFormatTypes.INTEGER);
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java
index ff93ba3..3140352 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java
@@ -37,19 +37,12 @@
}
@Override
- public void serialize(Calendar value, JsonGenerator jgen, SerializerProvider provider) throws IOException
+ public void serialize(Calendar value, JsonGenerator g, SerializerProvider provider) throws IOException
{
if (_asTimestamp(provider)) {
- jgen.writeNumber(_timestamp(value));
- } else if (_customFormat != null) {
- // 21-Feb-2011, tatu: not optimal, but better than alternatives:
- synchronized (_customFormat) {
- // _customformat cannot parse Calendar, so Date should be passed
- jgen.writeString(_customFormat.format(value.getTime()));
- }
- } else {
- provider.defaultSerializeDateValue(value.getTime(), jgen);
+ g.writeNumber(_timestamp(value));
+ return;
}
+ _serializeAsString(value.getTime(), g, provider);
}
-
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java
index 7cb1885..6dec6a7 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java
@@ -77,17 +77,12 @@
@Override
public boolean isEmpty(SerializerProvider prov, Collection<?> value) {
- return (value == null) || value.isEmpty();
+ return value.isEmpty();
}
@Override
public boolean hasSingleElement(Collection<?> value) {
- Iterator<?> it = value.iterator();
- if (!it.hasNext()) {
- return false;
- }
- it.next();
- return !it.hasNext();
+ return value.size() == 1;
}
/*
@@ -97,27 +92,28 @@
*/
@Override
- public final void serialize(Collection<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException
+ public final void serialize(Collection<?> value, JsonGenerator g, SerializerProvider provider) throws IOException
{
final int len = value.size();
if (len == 1) {
if (((_unwrapSingle == null) &&
provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, jgen, provider);
+ serializeContents(value, g, provider);
return;
}
}
- jgen.writeStartArray(len);
- serializeContents(value, jgen, provider);
- jgen.writeEndArray();
+ g.writeStartArray(len);
+ serializeContents(value, g, provider);
+ g.writeEndArray();
}
@Override
- public void serializeContents(Collection<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException
+ public void serializeContents(Collection<?> value, JsonGenerator g, SerializerProvider provider) throws IOException
{
+ g.setCurrentValue(value);
if (_elementSerializer != null) {
- serializeContentsUsing(value, jgen, provider, _elementSerializer);
+ serializeContentsUsing(value, g, provider, _elementSerializer);
return;
}
Iterator<?> it = value.iterator();
@@ -132,7 +128,7 @@
do {
Object elem = it.next();
if (elem == null) {
- provider.defaultSerializeNull(jgen);
+ provider.defaultSerializeNull(g);
} else {
Class<?> cc = elem.getClass();
JsonSerializer<Object> serializer = serializers.serializerFor(cc);
@@ -146,9 +142,9 @@
serializers = _dynamicSerializers;
}
if (typeSer == null) {
- serializer.serialize(elem, jgen, provider);
+ serializer.serialize(elem, g, provider);
} else {
- serializer.serializeWithType(elem, jgen, provider, typeSer);
+ serializer.serializeWithType(elem, g, provider, typeSer);
}
}
++i;
@@ -158,9 +154,8 @@
}
}
- public void serializeContentsUsing(Collection<?> value, JsonGenerator jgen, SerializerProvider provider,
- JsonSerializer<Object> ser)
- throws IOException, JsonGenerationException
+ public void serializeContentsUsing(Collection<?> value, JsonGenerator g, SerializerProvider provider,
+ JsonSerializer<Object> ser) throws IOException
{
Iterator<?> it = value.iterator();
if (it.hasNext()) {
@@ -170,12 +165,12 @@
Object elem = it.next();
try {
if (elem == null) {
- provider.defaultSerializeNull(jgen);
+ provider.defaultSerializeNull(g);
} else {
if (typeSer == null) {
- ser.serialize(elem, jgen, provider);
+ ser.serialize(elem, g, provider);
} else {
- ser.serializeWithType(elem, jgen, provider, typeSer);
+ ser.serializeWithType(elem, g, provider, typeSer);
}
}
++i;
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java
index 3138ae7..028d7d6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java
@@ -42,17 +42,12 @@
}
@Override
- public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException
+ public void serialize(Date value, JsonGenerator g, SerializerProvider provider) throws IOException
{
if (_asTimestamp(provider)) {
- gen.writeNumber(_timestamp(value));
- } else if (_customFormat != null) {
- // 21-Feb-2011, tatu: not optimal, but better than alternatives:
- synchronized (_customFormat) {
- gen.writeString(_customFormat.format(value));
- }
- } else {
- provider.defaultSerializeDateValue(value, gen);
+ g.writeNumber(_timestamp(value));
+ return;
}
+ _serializeAsString(value, g, provider);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java
index 4b9312b..572d23e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java
@@ -4,8 +4,10 @@
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -35,12 +37,23 @@
*/
protected final DateFormat _customFormat;
+ /**
+ * If {@link #_customFormat} is used, we will try to reuse instances in simplest
+ * possible form; thread-safe, but without overhead of <code>ThreadLocal</code>
+ * (not from code, but wrt retaining of possibly large number of format instances
+ * over all threads, properties with custom formats).
+ *
+ * @since 2.9
+ */
+ protected final AtomicReference<DateFormat> _reusedCustomFormat;
+
protected DateTimeSerializerBase(Class<T> type,
Boolean useTimestamp, DateFormat customFormat)
{
super(type);
_useTimestamp = useTimestamp;
_customFormat = customFormat;
+ _reusedCustomFormat = (customFormat == null) ? null : new AtomicReference<DateFormat>();
}
public abstract DateTimeSerializerBase<T> withFormat(Boolean timestamp, DateFormat customFormat);
@@ -49,9 +62,8 @@
public JsonSerializer<?> createContextual(SerializerProvider serializers,
BeanProperty property) throws JsonMappingException
{
- if (property == null) {
- return this;
- }
+ // Note! Should not skip if `property` null since that'd skip check
+ // for config overrides, in case of root value
JsonFormat.Value format = findFormatOverrides(serializers, property, handledType());
if (format == null) {
return this;
@@ -101,10 +113,9 @@
// mechanism for changing `DateFormat` instances (or even clone()ing)
// So: require it be `SimpleDateFormat`; can't config other types
if (!(df0 instanceof SimpleDateFormat)) {
-// serializers.reportBadDefinition(handledType(), String.format(
- serializers.reportMappingProblem(
-"Configured `DateFormat` (%s) not a `SimpleDateFormat`; can not configure `Locale` or `TimeZone`",
-df0.getClass().getName());
+ serializers.reportBadDefinition(handledType(), String.format(
+"Configured `DateFormat` (%s) not a `SimpleDateFormat`; cannot configure `Locale` or `TimeZone`",
+df0.getClass().getName()));
}
SimpleDateFormat df = (SimpleDateFormat) df0;
if (hasLocale) {
@@ -127,21 +138,16 @@
/**********************************************************
*/
- @Deprecated
- @Override
- public boolean isEmpty(T value) {
- // let's assume "null date" (timestamp 0) qualifies for empty
- return (value == null) || (_timestamp(value) == 0L);
- }
-
@Override
public boolean isEmpty(SerializerProvider serializers, T value) {
- // let's assume "null date" (timestamp 0) qualifies for empty
- return (value == null) || (_timestamp(value) == 0L);
+ // 09-Mar-2017, tatu: as per [databind#1550] timestamp 0 is NOT "empty"; but
+ // with versions up to 2.8.x this was the case. Fixed for 2.9.
+// return _timestamp(value) == 0L;
+ return false;
}
-
+
protected abstract long _timestamp(T value);
-
+
@Override
public JsonNode getSchema(SerializerProvider serializers, Type typeHint) {
//todo: (ryan) add a format for the date in the schema?
@@ -195,4 +201,29 @@
visitStringFormat(visitor, typeHint, JsonValueFormat.DATE_TIME);
}
}
+
+ /**
+ * @since 2.9
+ */
+ protected void _serializeAsString(Date value, JsonGenerator g, SerializerProvider provider) throws IOException
+ {
+ if (_customFormat == null) {
+ provider.defaultSerializeDateValue(value, g);
+ return;
+ }
+
+ // 19-Jul-2017, tatu: Here we will try a simple but (hopefully) effective mechanism for
+ // reusing formatter instance. This is our second attempt, after initially trying simple
+ // synchronization (which turned out to be bottleneck for some users in production...).
+ // While `ThreadLocal` could alternatively be used, it is likely that it would lead to
+ // higher memory footprint, but without much upside -- if we can not reuse, we'll just
+ // clone(), which has some overhead but not drastic one.
+
+ DateFormat f = _reusedCustomFormat.getAndSet(null);
+ if (f == null) {
+ f = (DateFormat) _customFormat.clone();
+ }
+ g.writeString(f.format(value));
+ _reusedCustomFormat.compareAndSet(null, f);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSerializer.java
index dae384a..def97ee 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSerializer.java
@@ -52,14 +52,6 @@
/* Construction, initialization
/**********************************************************
*/
-
- /**
- * @deprecated Since 2.1
- */
- @Deprecated
- public EnumSerializer(EnumValues v) {
- this(v, null);
- }
public EnumSerializer(EnumValues v, Boolean serializeAsIndex)
{
@@ -78,7 +70,7 @@
public static EnumSerializer construct(Class<?> enumClass, SerializationConfig config,
BeanDescription beanDesc, JsonFormat.Value format)
{
- /* 08-Apr-2015, tatu: As per [databind#749], we can not statically determine
+ /* 08-Apr-2015, tatu: As per [databind#749], we cannot statically determine
* between name() and toString(), need to construct `EnumValues` with names,
* handle toString() case dynamically (for example)
*/
@@ -96,15 +88,14 @@
public JsonSerializer<?> createContextual(SerializerProvider serializers,
BeanProperty property) throws JsonMappingException
{
- if (property != null) {
- JsonFormat.Value format = findFormatOverrides(serializers,
- property, handledType());
- if (format != null) {
- Boolean serializeAsIndex = _isShapeWrittenUsingIndex(property.getType().getRawClass(),
- format, false, _serializeAsIndex);
- if (serializeAsIndex != _serializeAsIndex) {
- return new EnumSerializer(_values, serializeAsIndex);
- }
+ JsonFormat.Value format = findFormatOverrides(serializers,
+ property, handledType());
+ if (format != null) {
+ Class<?> type = handledType();
+ Boolean serializeAsIndex = _isShapeWrittenUsingIndex(type,
+ format, false, _serializeAsIndex);
+ if (serializeAsIndex != _serializeAsIndex) {
+ return new EnumSerializer(_values, serializeAsIndex);
}
}
return this;
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java
index af522dd..6211d71 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java
@@ -18,14 +18,6 @@
super(EnumSet.class, elemType, true, null, null);
}
- /**
- * @deprecated since 2.6
- */
- @Deprecated // since 2.6
- public EnumSetSerializer(JavaType elemType, BeanProperty property) {
- this(elemType);
- }
-
public EnumSetSerializer(EnumSetSerializer src,
BeanProperty property, TypeSerializer vts, JsonSerializer<?> valueSerializer,
Boolean unwrapSingle) {
@@ -47,7 +39,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, EnumSet<? extends Enum<?>> value) {
- return (value == null) || value.isEmpty();
+ return value.isEmpty();
}
@Override
@@ -59,7 +51,7 @@
public final void serialize(EnumSet<? extends Enum<?>> value, JsonGenerator gen,
SerializerProvider provider) throws IOException
{
- final int len = value.size();
+ final int len = value.size();
if (len == 1) {
if (((_unwrapSingle == null)
&& provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
@@ -85,7 +77,7 @@
*/
for (Enum<?> en : value) {
if (enumSer == null) {
- /* 12-Jan-2010, tatu: Since enums can not be polymorphic, let's
+ /* 12-Jan-2010, tatu: Since enums cannot be polymorphic, let's
* not bother with typed serializer variant here
*/
enumSer = provider.findValueSerializer(en.getDeclaringClass(), _property);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/InetAddressSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/InetAddressSerializer.java
index 3371481..2bc0e14 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/InetAddressSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/InetAddressSerializer.java
@@ -3,42 +3,96 @@
import java.io.IOException;
import java.net.InetAddress;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
* Simple serializer for {@link java.net.InetAddress}. Main complexity is
* with registration, since same serializer is to be used for sub-classes.
+ *<p>
+ * Since 2.9 allows use of {@link JsonFormat} configuration (annotation,
+ * per-type defaulting) so that if <code>JsonFormat.Shape.NUMBER</code>
+ * (or <code>ARRAY</code>) is used, will serialize as "host address"
+ * (dotted numbers) instead of simple conversion.
*/
@SuppressWarnings("serial")
public class InetAddressSerializer
extends StdScalarSerializer<InetAddress>
+ implements ContextualSerializer
{
- public InetAddressSerializer() { super(InetAddress.class); }
+ /**
+ * @since 2.9
+ */
+ protected final boolean _asNumeric;
+ public InetAddressSerializer() {
+ this(false);
+ }
+
+ /**
+ * @since 2.9
+ */
+ public InetAddressSerializer(boolean asNumeric) {
+ super(InetAddress.class);
+ _asNumeric = asNumeric;
+ }
+
@Override
- public void serialize(InetAddress value, JsonGenerator jgen, SerializerProvider provider) throws IOException
+ public JsonSerializer<?> createContextual(SerializerProvider serializers,
+ BeanProperty property) throws JsonMappingException
{
- // Ok: get textual description; choose "more specific" part
- String str = value.toString().trim();
- int ix = str.indexOf('/');
- if (ix >= 0) {
- if (ix == 0) { // missing host name; use address
- str = str.substring(1);
- } else { // otherwise use name
- str = str.substring(0, ix);
+ JsonFormat.Value format = findFormatOverrides(serializers,
+ property, handledType());
+ boolean asNumeric = false;
+ if (format != null) {
+ JsonFormat.Shape shape = format.getShape();
+ if (shape.isNumeric() || shape == JsonFormat.Shape.ARRAY) {
+ asNumeric = true;
}
}
- jgen.writeString(str);
+ if (asNumeric != _asNumeric) {
+ return new InetAddressSerializer(asNumeric);
+ }
+ return this;
}
@Override
- public void serializeWithType(InetAddress value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonGenerationException
+ public void serialize(InetAddress value, JsonGenerator g, SerializerProvider provider) throws IOException
+ {
+ String str;
+
+ if (_asNumeric) { // since 2.9
+ str = value.getHostAddress();
+ } else {
+ // Ok: get textual description; choose "more specific" part
+ str = value.toString().trim();
+ int ix = str.indexOf('/');
+ if (ix >= 0) {
+ if (ix == 0) { // missing host name; use address
+ str = str.substring(1);
+ } else { // otherwise use name
+ str = str.substring(0, ix);
+ }
+ }
+ }
+ g.writeString(str);
+ }
+
+ @Override
+ public void serializeWithType(InetAddress value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException
{
// Better ensure we don't use specific sub-classes...
- typeSer.writeTypePrefixForScalar(value, jgen, InetAddress.class);
- serialize(value, jgen, provider);
- typeSer.writeTypeSuffixForScalar(value, jgen);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, InetAddress.class, JsonToken.VALUE_STRING));
+ serialize(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/InetSocketAddressSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/InetSocketAddressSerializer.java
index 8dcbac9..13b8be8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/InetSocketAddressSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/InetSocketAddressSerializer.java
@@ -1,15 +1,14 @@
package com.fasterxml.jackson.databind.ser.std;
-import com.fasterxml.jackson.core.JsonGenerationException;
+import java.io.IOException;
+import java.net.*;
+
import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
-import java.io.IOException;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-
/**
* Simple serializer for {@link InetSocketAddress}.
*/
@@ -40,11 +39,13 @@
}
@Override
- public void serializeWithType(InetSocketAddress value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonGenerationException
+ public void serializeWithType(InetSocketAddress value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException
{
// Better ensure we don't use specific sub-classes...
- typeSer.writeTypePrefixForScalar(value, jgen, InetSocketAddress.class);
- serialize(value, jgen, provider);
- typeSer.writeTypeSuffixForScalar(value, jgen);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, InetSocketAddress.class, JsonToken.VALUE_STRING));
+ serialize(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java
index 6c4082d..5cc5068 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java
@@ -40,7 +40,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, Iterable<?> value) {
// Not really good way to implement this, but has to do for now:
- return (value == null) || !value.iterator().hasNext();
+ return !value.iterator().hasNext();
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java
index 7582c3a..6112a7d 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java
@@ -8,9 +8,10 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
-import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
@@ -19,6 +20,7 @@
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.BeanSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Serializer class that can serialize Object that have a
@@ -37,11 +39,11 @@
public class JsonValueSerializer
extends StdSerializer<Object>
implements ContextualSerializer, JsonFormatVisitable, SchemaAware
- {
+{
/**
- * @since 2.8 (was "plain" method before)
+ * @since 2.9
*/
- protected final AnnotatedMethod _accessorMethod;
+ protected final AnnotatedMember _accessor;
protected final JsonSerializer<Object> _valueSerializer;
@@ -71,10 +73,10 @@
* to information we need
*/
@SuppressWarnings("unchecked")
- public JsonValueSerializer(AnnotatedMethod valueMethod, JsonSerializer<?> ser)
+ public JsonValueSerializer(AnnotatedMember accessor, JsonSerializer<?> ser)
{
- super(valueMethod.getType());
- _accessorMethod = valueMethod;
+ super(accessor.getType());
+ _accessor = accessor;
_valueSerializer = (JsonSerializer<Object>) ser;
_property = null;
_forceTypeInformation = true; // gets reconsidered when we are contextualized
@@ -85,7 +87,7 @@
JsonSerializer<?> ser, boolean forceTypeInfo)
{
super(_notNullClass(src.handledType()));
- _accessorMethod = src._accessorMethod;
+ _accessor = src._accessor;
_valueSerializer = (JsonSerializer<Object>) ser;
_property = property;
_forceTypeInformation = forceTypeInfo;
@@ -127,7 +129,7 @@
* if not, we don't really know the actual type until we get the instance.
*/
// 10-Mar-2010, tatu: Except if static typing is to be used
- JavaType t = _accessorMethod.getType();
+ JavaType t = _accessor.getType();
if (provider.isEnabled(MapperFeature.USE_STATIC_TYPING) || t.isFinal()) {
// false -> no need to cache
/* 10-Mar-2010, tatu: Ideally we would actually separate out type
@@ -161,7 +163,7 @@
public void serialize(Object bean, JsonGenerator gen, SerializerProvider prov) throws IOException
{
try {
- Object value = _accessorMethod.getValue(bean);
+ Object value = _accessor.getValue(bean);
if (value == null) {
prov.defaultSerializeNull(gen);
return;
@@ -177,20 +179,8 @@
ser = prov.findTypedValueSerializer(c, true, _property);
}
ser.serialize(value, gen, prov);
- } catch (IOException ioe) {
- throw ioe;
} catch (Exception e) {
- Throwable t = e;
- // Need to unwrap this specific type, to see infinite recursion...
- while (t instanceof InvocationTargetException && t.getCause() != null) {
- t = t.getCause();
- }
- // Errors shouldn't be wrapped (and often can't, as well)
- if (t instanceof Error) {
- throw (Error) t;
- }
- // let's try to indicate the path best we can...
- throw JsonMappingException.wrapWithPath(t, bean, _accessorMethod.getName() + "()");
+ wrapAndThrow(prov, e, bean, _accessor.getName() + "()");
}
}
@@ -201,7 +191,7 @@
// Regardless of other parts, first need to find value to serialize:
Object value = null;
try {
- value = _accessorMethod.getValue(bean);
+ value = _accessor.getValue(bean);
// and if we got null, can also just write it directly
if (value == null) {
provider.defaultSerializeNull(gen);
@@ -209,16 +199,17 @@
}
JsonSerializer<Object> ser = _valueSerializer;
if (ser == null) { // no serializer yet? Need to fetch
-// ser = provider.findTypedValueSerializer(value.getClass(), true, _property);
ser = provider.findValueSerializer(value.getClass(), _property);
} else {
- /* 09-Dec-2010, tatu: To work around natural type's refusal to add type info, we do
- * this (note: type is for the wrapper type, not enclosed value!)
- */
+ // 09-Dec-2010, tatu: To work around natural type's refusal to add type info, we do
+ // this (note: type is for the wrapper type, not enclosed value!)
if (_forceTypeInformation) {
- typeSer0.writeTypePrefixForScalar(bean, gen);
+ // Confusing? Type id is for POJO and NOT for value returned by JsonValue accessor...
+ WritableTypeId typeIdDef = typeSer0.writeTypePrefix(gen,
+ typeSer0.typeId(bean, JsonToken.VALUE_STRING));
ser.serialize(value, gen, provider);
- typeSer0.writeTypeSuffixForScalar(bean, gen);
+ typeSer0.writeTypeSuffix(gen, typeIdDef);
+
return;
}
}
@@ -227,20 +218,8 @@
// (delegat type).
TypeSerializerRerouter rr = new TypeSerializerRerouter(typeSer0, bean);
ser.serializeWithType(value, gen, provider, rr);
- } catch (IOException ioe) {
- throw ioe;
} catch (Exception e) {
- Throwable t = e;
- // Need to unwrap this specific type, to see infinite recursion...
- while (t instanceof InvocationTargetException && t.getCause() != null) {
- t = t.getCause();
- }
- // Errors shouldn't be wrapped (and often can't, as well)
- if (t instanceof Error) {
- throw (Error) t;
- }
- // let's try to indicate the path best we can...
- throw JsonMappingException.wrapWithPath(t, bean, _accessorMethod.getName() + "()");
+ wrapAndThrow(provider, e, bean, _accessor.getName() + "()");
}
}
@@ -268,8 +247,8 @@
*
* Note that meaning of JsonValue, then, is very different for Enums. Sigh.
*/
- final JavaType type = _accessorMethod.getType();
- Class<?> declaring = _accessorMethod.getDeclaringClass();
+ final JavaType type = _accessor.getType();
+ Class<?> declaring = _accessor.getDeclaringClass();
if ((declaring != null) && declaring.isEnum()) {
if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, declaring)) {
return;
@@ -283,7 +262,7 @@
return;
}
}
- ser.acceptJsonFormatVisitor(visitor, null);
+ ser.acceptJsonFormatVisitor(visitor, type);
}
/**
@@ -308,16 +287,14 @@
// 21-Apr-2016, tatu: This is convoluted to the max, but essentially we
// call `@JsonValue`-annotated accessor method on all Enum members,
// so it all "works out". To some degree.
- enums.add(String.valueOf(_accessorMethod.callOn(en)));
+ enums.add(String.valueOf(_accessor.getValue(en)));
} catch (Exception e) {
Throwable t = e;
while (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
- if (t instanceof Error) {
- throw (Error) t;
- }
- throw JsonMappingException.wrapWithPath(t, en, _accessorMethod.getName() + "()");
+ ClassUtil.throwIfError(t);
+ throw JsonMappingException.wrapWithPath(t, en, _accessor.getName() + "()");
}
}
stringVisitor.enumTypes(enums);
@@ -349,7 +326,7 @@
@Override
public String toString() {
- return "(@JsonValue serializer for method " + _accessorMethod.getDeclaringClass() + "#" + _accessorMethod.getName() + ")";
+ return "(@JsonValue serializer for method " + _accessor.getDeclaringClass() + "#" + _accessor.getName() + ")";
}
/*
@@ -394,78 +371,118 @@
return _typeSerializer.getTypeIdResolver();
}
+ // // // New Write API, 2.9+
+
+ @Override // since 2.9
+ public WritableTypeId writeTypePrefix(JsonGenerator g,
+ WritableTypeId typeId) throws IOException {
+ // 28-Jun-2017, tatu: Important! Need to "override" value
+ typeId.forValue = _forObject;
+ return _typeSerializer.writeTypePrefix(g, typeId);
+ }
+
+ @Override // since 2.9
+ public WritableTypeId writeTypeSuffix(JsonGenerator g,
+ WritableTypeId typeId) throws IOException {
+ // NOTE: already overwrote value object so:
+ return _typeSerializer.writeTypeSuffix(g, typeId);
+ }
+
+ // // // Old Write API, pre-2.9
+
@Override
+ @Deprecated
public void writeTypePrefixForScalar(Object value, JsonGenerator gen) throws IOException {
_typeSerializer.writeTypePrefixForScalar(_forObject, gen);
}
@Override
+ @Deprecated
public void writeTypePrefixForObject(Object value, JsonGenerator gen) throws IOException {
_typeSerializer.writeTypePrefixForObject(_forObject, gen);
}
@Override
+ @Deprecated
public void writeTypePrefixForArray(Object value, JsonGenerator gen) throws IOException {
_typeSerializer.writeTypePrefixForArray(_forObject, gen);
}
@Override
+ @Deprecated
public void writeTypeSuffixForScalar(Object value, JsonGenerator gen) throws IOException {
_typeSerializer.writeTypeSuffixForScalar(_forObject, gen);
}
@Override
+ @Deprecated
public void writeTypeSuffixForObject(Object value, JsonGenerator gen) throws IOException {
_typeSerializer.writeTypeSuffixForObject(_forObject, gen);
}
@Override
+ @Deprecated
public void writeTypeSuffixForArray(Object value, JsonGenerator gen) throws IOException {
_typeSerializer.writeTypeSuffixForArray(_forObject, gen);
}
@Override
+ @Deprecated
public void writeTypePrefixForScalar(Object value, JsonGenerator gen, Class<?> type) throws IOException {
_typeSerializer.writeTypePrefixForScalar(_forObject, gen, type);
}
@Override
+ @Deprecated
public void writeTypePrefixForObject(Object value, JsonGenerator gen, Class<?> type) throws IOException {
_typeSerializer.writeTypePrefixForObject(_forObject, gen, type);
}
@Override
+ @Deprecated
public void writeTypePrefixForArray(Object value, JsonGenerator gen, Class<?> type) throws IOException {
_typeSerializer.writeTypePrefixForArray(_forObject, gen, type);
}
-
+
+ /*
+ /**********************************************************
+ /* Deprecated methods (since 2.9)
+ /**********************************************************
+ */
+
@Override
+ @Deprecated
public void writeCustomTypePrefixForScalar(Object value, JsonGenerator gen, String typeId)
throws IOException {
_typeSerializer.writeCustomTypePrefixForScalar(_forObject, gen, typeId);
}
@Override
+ @Deprecated
public void writeCustomTypePrefixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
_typeSerializer.writeCustomTypePrefixForObject(_forObject, gen, typeId);
}
@Override
+ @Deprecated
public void writeCustomTypePrefixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
_typeSerializer.writeCustomTypePrefixForArray(_forObject, gen, typeId);
}
@Override
+ @Deprecated
public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator gen, String typeId) throws IOException {
_typeSerializer.writeCustomTypeSuffixForScalar(_forObject, gen, typeId);
}
@Override
+ @Deprecated
public void writeCustomTypeSuffixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
_typeSerializer.writeCustomTypeSuffixForObject(_forObject, gen, typeId);
}
@Override
+ @Deprecated
public void writeCustomTypeSuffixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
_typeSerializer.writeCustomTypeSuffixForArray(_forObject, gen, typeId);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapProperty.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapProperty.java
index 44e94c8..c6ee247 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapProperty.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapProperty.java
@@ -4,19 +4,13 @@
import java.lang.annotation.Annotation;
import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.BeanProperty;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.PropertyMetadata;
-import com.fasterxml.jackson.databind.PropertyName;
-import com.fasterxml.jackson.databind.SerializerProvider;
+
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
-import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Helper class needed to support flexible filtering of Map properties
@@ -28,11 +22,13 @@
{
private static final long serialVersionUID = 1L;
+ private final static BeanProperty BOGUS_PROP = new BeanProperty.Bogus();
+
protected final TypeSerializer _typeSerializer;
protected final BeanProperty _property;
- protected Object _key;
+ protected Object _key, _value;
protected JsonSerializer<Object> _keySerializer, _valueSerializer;
@@ -40,21 +36,31 @@
{
super((prop == null) ? PropertyMetadata.STD_REQUIRED_OR_OPTIONAL : prop.getMetadata());
_typeSerializer = typeSer;
- _property = prop;
+ _property = (prop == null) ? BOGUS_PROP : prop;
}
/**
* Initialization method that needs to be called before passing
* property to filter.
+ *
+ * @since 2.9
*/
- public void reset(Object key,
+ public void reset(Object key, Object value,
JsonSerializer<Object> keySer, JsonSerializer<Object> valueSer)
{
_key = key;
+ _value = value;
_keySerializer = keySer;
_valueSerializer = valueSer;
}
-
+
+ @Deprecated // since 2.9
+ public void reset(Object key,
+ JsonSerializer<Object> keySer, JsonSerializer<Object> valueSer)
+ {
+ reset(key, _value, keySer, valueSer);
+ }
+
@Override
public String getName() {
if (_key instanceof String) {
@@ -63,6 +69,20 @@
return String.valueOf(_key);
}
+ /**
+ * @since 2.9
+ */
+ public Object getValue() {
+ return _value;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public void setValue(Object v) {
+ _value = v;
+ }
+
@Override
public PropertyName getFullName() {
return new PropertyName(getName());
@@ -70,28 +90,28 @@
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
- return (_property == null) ? null : _property.getAnnotation(acls);
+ return _property.getAnnotation(acls);
}
@Override
public <A extends Annotation> A getContextAnnotation(Class<A> acls) {
- return (_property == null) ? null : _property.getContextAnnotation(acls);
+ return _property.getContextAnnotation(acls);
}
@Override
- public void serializeAsField(Object value, JsonGenerator gen,
+ public void serializeAsField(Object map, JsonGenerator gen,
SerializerProvider provider) throws IOException
{
_keySerializer.serialize(_key, gen, provider);
if (_typeSerializer == null) {
- _valueSerializer.serialize(value, gen, provider);
+ _valueSerializer.serialize(_value, gen, provider);
} else {
- _valueSerializer.serializeWithType(value, gen, provider, _typeSerializer);
+ _valueSerializer.serializeWithType(_value, gen, provider, _typeSerializer);
}
}
@Override
- public void serializeAsOmittedField(Object value, JsonGenerator gen,
+ public void serializeAsOmittedField(Object map, JsonGenerator gen,
SerializerProvider provider) throws Exception
{
if (!gen.canOmitFields()) {
@@ -100,13 +120,13 @@
}
@Override
- public void serializeAsElement(Object value, JsonGenerator gen,
+ public void serializeAsElement(Object map, JsonGenerator gen,
SerializerProvider provider) throws Exception
{
if (_typeSerializer == null) {
- _valueSerializer.serialize(value, gen, provider);
+ _valueSerializer.serialize(_value, gen, provider);
} else {
- _valueSerializer.serializeWithType(value, gen, provider, _typeSerializer);
+ _valueSerializer.serializeWithType(_value, gen, provider, _typeSerializer);
}
}
@@ -128,9 +148,7 @@
SerializerProvider provider)
throws JsonMappingException
{
- if (_property != null) {
- _property.depositSchemaProperty(objectVisitor, provider);
- }
+ _property.depositSchemaProperty(objectVisitor, provider);
}
@Override
@@ -138,20 +156,20 @@
public void depositSchemaProperty(ObjectNode propertiesNode,
SerializerProvider provider) throws JsonMappingException {
// nothing to do here
- }
+ }
@Override
public JavaType getType() {
- return (_property == null) ? TypeFactory.unknownType() : _property.getType();
+ return _property.getType();
}
@Override
public PropertyName getWrapperName() {
- return (_property == null) ? null : _property.getWrapperName();
+ return _property.getWrapperName();
}
@Override
public AnnotatedMember getMember() {
- return (_property == null) ? null : _property.getMember();
+ return _property.getMember();
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java
index a3af8cc..b3a2e62 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java
@@ -8,6 +8,7 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
@@ -20,6 +21,8 @@
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
+import com.fasterxml.jackson.databind.util.BeanUtil;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Standard serializer implementation for serializing {link java.util.Map} types.
@@ -37,16 +40,22 @@
protected final static JavaType UNSPECIFIED_TYPE = TypeFactory.unknownType();
/**
+ * @since 2.9
+ */
+ public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY;
+
+ /*
+ /**********************************************************
+ /* Basic information about referring property, type
+ /**********************************************************
+ */
+
+ /**
* Map-valued property being serialized with this instance
*/
protected final BeanProperty _property;
/**
- * Set of entries to omit during serialization, if any
- */
- protected final Set<String> _ignoredEntries;
-
- /**
* Whether static types should be used for serialization of values
* or not (if not, dynamic runtime type is used)
*/
@@ -62,6 +71,12 @@
*/
protected final JavaType _valueType;
+ /*
+ /**********************************************************
+ /* Serializers used
+ /**********************************************************
+ */
+
/**
* Key serializer to use, if it can be statically determined
*/
@@ -78,11 +93,22 @@
protected final TypeSerializer _valueTypeSerializer;
/**
- * If value type can not be statically determined, mapping from
+ * If value type cannot be statically determined, mapping from
* runtime value types to serializers are stored in this object.
*/
protected PropertySerializerMap _dynamicValueSerializers;
+ /*
+ /**********************************************************
+ /* Config settings, filtering
+ /**********************************************************
+ */
+
+ /**
+ * Set of entries to omit during serialization, if any
+ */
+ protected final Set<String> _ignoredEntries;
+
/**
* Id of the property filter to use, if any; null if none.
*
@@ -91,17 +117,10 @@
protected final Object _filterId;
/**
- * Flag set if output is forced to be sorted by keys (usually due
- * to annotation).
- *
- * @since 2.4
- */
- protected final boolean _sortKeys;
-
- /**
* Value that indicates suppression mechanism to use for <b>values contained</b>;
- * either one of values of {@link com.fasterxml.jackson.annotation.JsonInclude.Include},
- * or actual object to compare against ("default value").
+ * either "filter" (of which <code>equals()</code> is called), or marker
+ * value of {@link #MARKER_FOR_EMPTY}, or null to indicate no filtering for
+ * non-null values.
* Note that inclusion value for Map instance itself is handled by caller (POJO
* property that refers to the Map value).
*
@@ -109,6 +128,28 @@
*/
protected final Object _suppressableValue;
+ /**
+ * Flag that indicates what to do with `null` values, distinct from
+ * handling of {@link #_suppressableValue}
+ *
+ * @since 2.9
+ */
+ protected final boolean _suppressNulls;
+
+ /*
+ /**********************************************************
+ /* Config settings, other
+ /**********************************************************
+ */
+
+ /**
+ * Flag set if output is forced to be sorted by keys (usually due
+ * to annotation).
+ *
+ * @since 2.4
+ */
+ protected final boolean _sortKeys;
+
/*
/**********************************************************
/* Life-cycle
@@ -138,17 +179,9 @@
_filterId = null;
_sortKeys = false;
_suppressableValue = null;
+ _suppressNulls = false;
}
- /**
- * @since 2.5
- */
- protected void _ensureOverride() {
- if (getClass() != MapSerializer.class) {
- throw new IllegalStateException("Missing override in class "+getClass().getName());
- }
- }
-
@SuppressWarnings("unchecked")
protected MapSerializer(MapSerializer src, BeanProperty property,
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
@@ -163,23 +196,20 @@
_valueTypeSerializer = src._valueTypeSerializer;
_keySerializer = (JsonSerializer<Object>) keySerializer;
_valueSerializer = (JsonSerializer<Object>) valueSerializer;
- _dynamicValueSerializers = src._dynamicValueSerializers;
+ // [databind#2181]: may not be safe to reuse, start from empty
+ _dynamicValueSerializers = PropertySerializerMap.emptyForProperties();
_property = property;
_filterId = src._filterId;
_sortKeys = src._sortKeys;
_suppressableValue = src._suppressableValue;
- }
-
- @Deprecated // since 2.5
- protected MapSerializer(MapSerializer src, TypeSerializer vts) {
- this(src, vts, src._suppressableValue);
+ _suppressNulls = src._suppressNulls;
}
/**
- * @since 2.5
+ * @since 2.9
*/
protected MapSerializer(MapSerializer src, TypeSerializer vts,
- Object suppressableValue)
+ Object suppressableValue, boolean suppressNulls)
{
super(Map.class, false);
_ignoredEntries = src._ignoredEntries;
@@ -189,16 +219,14 @@
_valueTypeSerializer = vts;
_keySerializer = src._keySerializer;
_valueSerializer = src._valueSerializer;
+ // 22-Nov-2018, tatu: probably safe (even with [databind#2181]) since it's just
+ // inclusion, type serializer but NOT serializer
_dynamicValueSerializers = src._dynamicValueSerializers;
_property = src._property;
_filterId = src._filterId;
_sortKeys = src._sortKeys;
- // 05-Jun-2015, tatu: For referential, this is same as NON_EMPTY; for others, NON_NULL, so:
- if (suppressableValue == JsonInclude.Include.NON_ABSENT) {
- suppressableValue = _valueType.isReferenceType() ?
- JsonInclude.Include.NON_EMPTY : JsonInclude.Include.NON_NULL;
- }
_suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
protected MapSerializer(MapSerializer src, Object filterId, boolean sortKeys)
@@ -211,11 +239,13 @@
_valueTypeSerializer = src._valueTypeSerializer;
_keySerializer = src._keySerializer;
_valueSerializer = src._valueSerializer;
- _dynamicValueSerializers = src._dynamicValueSerializers;
+ // [databind#2181]: may not be safe to reuse, start from empty
+ _dynamicValueSerializers = PropertySerializerMap.emptyForProperties();
_property = src._property;
_filterId = filterId;
_sortKeys = sortKeys;
_suppressableValue = src._suppressableValue;
+ _suppressNulls = src._suppressNulls;
}
@Override
@@ -223,8 +253,8 @@
if (_valueTypeSerializer == vts) {
return this;
}
- _ensureOverride();
- return new MapSerializer(this, vts, null);
+ _ensureOverride("_withValueTypeSerializer");
+ return new MapSerializer(this, vts, _suppressableValue, _suppressNulls);
}
/**
@@ -234,7 +264,7 @@
JsonSerializer<?> keySerializer, JsonSerializer<?> valueSerializer,
Set<String> ignored, boolean sortKeys)
{
- _ensureOverride();
+ _ensureOverride("withResolved");
MapSerializer ser = new MapSerializer(this, property, keySerializer, valueSerializer, ignored);
if (sortKeys != ser._sortKeys) {
ser = new MapSerializer(ser, _filterId, sortKeys);
@@ -247,7 +277,7 @@
if (_filterId == filterId) {
return this;
}
- _ensureOverride();
+ _ensureOverride("withFilterId");
return new MapSerializer(this, filterId, _sortKeys);
}
@@ -255,31 +285,14 @@
* Mutant factory for constructing an instance with different inclusion strategy
* for content (Map values).
*
- * @since 2.5
+ * @since 2.9
*/
- public MapSerializer withContentInclusion(Object suppressableValue) {
- if (suppressableValue == _suppressableValue) {
+ public MapSerializer withContentInclusion(Object suppressableValue, boolean suppressNulls) {
+ if ((suppressableValue == _suppressableValue) && (suppressNulls == _suppressNulls)) {
return this;
}
- _ensureOverride();
- return new MapSerializer(this, _valueTypeSerializer, suppressableValue);
- }
-
- /**
- * @since 2.3
- *
- * @deprecated Since 2.8 use the other overload
- */
- @Deprecated // since 2.8
- public static MapSerializer construct(String[] ignoredList, JavaType mapType,
- boolean staticValueType, TypeSerializer vts,
- JsonSerializer<Object> keySerializer, JsonSerializer<Object> valueSerializer,
- Object filterId)
- {
- Set<String> ignoredEntries = (ignoredList == null || ignoredList.length == 0)
- ? null : ArrayBuilders.arrayToSet(ignoredList);
- return construct(ignoredEntries, mapType, staticValueType, vts,
- keySerializer, valueSerializer, filterId);
+ _ensureOverride("withContentInclusion");
+ return new MapSerializer(this, _valueTypeSerializer, suppressableValue, suppressNulls);
}
/**
@@ -302,7 +315,7 @@
if (!staticValueType) {
staticValueType = (valueType != null && valueType.isFinal());
} else {
- // also: Object.class can not be handled as static, ever
+ // also: Object.class cannot be handled as static, ever
if (valueType.getRawClass() == Object.class) {
staticValueType = false;
}
@@ -315,6 +328,62 @@
return ser;
}
+ /**
+ * @since 2.9
+ */
+ protected void _ensureOverride(String method) {
+ ClassUtil.verifyMustOverride(MapSerializer.class, this, method);
+ }
+
+ /**
+ * @since 2.5
+ */
+ @Deprecated // since 2.9
+ protected void _ensureOverride() {
+ _ensureOverride("N/A");
+ }
+
+ /*
+ /**********************************************************
+ /* Deprecated creators
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.5
+ * @deprecated // since 2.9
+ */
+ @Deprecated // since 2.9
+ protected MapSerializer(MapSerializer src, TypeSerializer vts,
+ Object suppressableValue)
+ {
+ this(src, vts, suppressableValue, false);
+ }
+
+ /**
+ * @deprecated since 2.9
+ */
+ @Deprecated // since 2.9
+ public MapSerializer withContentInclusion(Object suppressableValue) {
+ return new MapSerializer(this, _valueTypeSerializer, suppressableValue, _suppressNulls);
+ }
+
+ /**
+ * @since 2.3
+ *
+ * @deprecated Since 2.8 use the other overload
+ */
+ @Deprecated // since 2.8
+ public static MapSerializer construct(String[] ignoredList, JavaType mapType,
+ boolean staticValueType, TypeSerializer vts,
+ JsonSerializer<Object> keySerializer, JsonSerializer<Object> valueSerializer,
+ Object filterId)
+ {
+ Set<String> ignoredEntries = ArrayBuilders.arrayToSet(ignoredList);
+ return construct(ignoredEntries, mapType, staticValueType, vts,
+ keySerializer, valueSerializer, filterId);
+ }
+
/*
/**********************************************************
/* Post-processing (contextualization)
@@ -330,10 +399,9 @@
JsonSerializer<?> keySer = null;
final AnnotationIntrospector intr = provider.getAnnotationIntrospector();
final AnnotatedMember propertyAcc = (property == null) ? null : property.getMember();
- Object suppressableValue = _suppressableValue;
// First: if we have a property, may have property-annotation overrides
- if ((propertyAcc != null) && (intr != null)) {
+ if (_neitherNull(propertyAcc, intr)) {
Object serDef = intr.findKeySerializer(propertyAcc);
if (serDef != null) {
keySer = provider.serializerInstance(propertyAcc, serDef);
@@ -343,17 +411,11 @@
ser = provider.serializerInstance(propertyAcc, serDef);
}
}
-
- JsonInclude.Value inclV = findIncludeOverrides(provider, property, Map.class);
- JsonInclude.Include incl = inclV.getContentInclusion();
- if ((incl != null) && (incl != JsonInclude.Include.USE_DEFAULTS)) {
- suppressableValue = incl;
- }
if (ser == null) {
ser = _valueSerializer;
}
// [databind#124]: May have a content converter
- ser = findConvertingContentSerializer(provider, property, ser);
+ ser = findContextualConvertingSerializer(provider, property, ser);
if (ser == null) {
// 30-Sep-2012, tatu: One more thing -- if explicit content type is annotated,
// we can consider it a static case as well.
@@ -361,8 +423,6 @@
if (_valueTypeIsStatic && !_valueType.isJavaLangObject()) {
ser = provider.findValueSerializer(_valueType, property);
}
- } else {
- ser = provider.handleSecondaryContextualization(ser, property);
}
if (keySer == null) {
keySer = _keySerializer;
@@ -374,11 +434,11 @@
}
Set<String> ignored = _ignoredEntries;
boolean sortKeys = false;
- if ((intr != null) && (propertyAcc != null)) {
+ if (_neitherNull(propertyAcc, intr)) {
JsonIgnoreProperties.Value ignorals = intr.findPropertyIgnorals(propertyAcc);
if (ignorals != null){
Set<String> newIgnored = ignorals.findIgnoredForSerialization();
- if ((newIgnored != null) && !newIgnored.isEmpty()) {
+ if (_nonEmpty(newIgnored)) {
ignored = (ignored == null) ? new HashSet<String>() : new HashSet<String>(ignored);
for (String str : newIgnored) {
ignored.add(str);
@@ -386,7 +446,7 @@
}
}
Boolean b = intr.findSerializationSortAlphabetically(propertyAcc);
- sortKeys = (b != null) && b.booleanValue();
+ sortKeys = Boolean.TRUE.equals(b);
}
JsonFormat.Value format = findFormatOverrides(provider, property, Map.class);
if (format != null) {
@@ -396,9 +456,6 @@
}
}
MapSerializer mser = withResolved(property, keySer, ser, ignored, sortKeys);
- if (suppressableValue != _suppressableValue) {
- mser = mser.withContentInclusion(suppressableValue);
- }
// [databind#307]: allow filtering
if (property != null) {
@@ -409,10 +466,58 @@
mser = mser.withFilterId(filterId);
}
}
+ JsonInclude.Value inclV = property.findPropertyInclusion(provider.getConfig(), null);
+ if (inclV != null) {
+ JsonInclude.Include incl = inclV.getContentInclusion();
+
+ if (incl != JsonInclude.Include.USE_DEFAULTS) {
+ Object valueToSuppress;
+ boolean suppressNulls;
+ switch (incl) {
+ case NON_DEFAULT:
+ valueToSuppress = BeanUtil.getDefaultValue(_valueType);
+ suppressNulls = true;
+ if (valueToSuppress != null) {
+ if (valueToSuppress.getClass().isArray()) {
+ valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
+ }
+ }
+ break;
+ case NON_ABSENT:
+ suppressNulls = true;
+ valueToSuppress = _valueType.isReferenceType() ? MARKER_FOR_EMPTY : null;
+ break;
+ case NON_EMPTY:
+ suppressNulls = true;
+ valueToSuppress = MARKER_FOR_EMPTY;
+ break;
+ case CUSTOM:
+ valueToSuppress = provider.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = provider.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
+ case NON_NULL:
+ valueToSuppress = null;
+ suppressNulls = true;
+ break;
+ case ALWAYS: // default
+ default:
+ valueToSuppress = null;
+ // 30-Sep-2016, tatu: Should not need to check global flags here,
+ // if inclusion forced to be ALWAYS
+ suppressNulls = false;
+ break;
+ }
+ mser = mser.withContentInclusion(valueToSuppress, suppressNulls);
+ }
+ }
}
return mser;
}
-
+
/*
/**********************************************************
/* Accessors
@@ -432,45 +537,55 @@
@Override
public boolean isEmpty(SerializerProvider prov, Map<?,?> value)
{
- if (value == null || value.isEmpty()) {
+ if (value.isEmpty()) {
return true;
}
+
// 05-Nove-2015, tatu: Simple cases are cheap, but for recursive
// emptiness checking we actually need to see if values are empty as well.
Object supp = _suppressableValue;
-
- if ((supp == null) || (supp == JsonInclude.Include.ALWAYS)) {
+ if ((supp == null) && !_suppressNulls) {
return false;
}
JsonSerializer<Object> valueSer = _valueSerializer;
+ final boolean checkEmpty = (MARKER_FOR_EMPTY == supp);
if (valueSer != null) {
for (Object elemValue : value.values()) {
- if ((elemValue != null) && !valueSer.isEmpty(prov, elemValue)) {
+ if (elemValue == null) {
+ if (_suppressNulls) {
+ continue;
+ }
+ return false;
+ }
+ if (checkEmpty) {
+ if (!valueSer.isEmpty(prov, elemValue)) {
+ return false;
+ }
+ } else if ((supp == null) || !supp.equals(value)) {
return false;
}
}
return true;
}
// But if not statically known, try this:
- PropertySerializerMap serializers = _dynamicValueSerializers;
for (Object elemValue : value.values()) {
if (elemValue == null) {
- continue;
+ if (_suppressNulls) {
+ continue;
+ }
+ return false;
}
- Class<?> cc = elemValue.getClass();
- // 05-Nov-2015, tatu: Let's not worry about generic types here, actually;
- // unlikely to make any difference, but does add significant overhead
- valueSer = serializers.serializerFor(cc);
- if (valueSer == null) {
- try {
- valueSer = _findAndAddDynamic(serializers, cc, prov);
- } catch (JsonMappingException e) { // Ugh... can not just throw as-is, so...
- // 05-Nov-2015, tatu: For now, probably best not to assume empty then
+ try {
+ valueSer = _findSerializer(prov, elemValue);
+ } catch (JsonMappingException e) { // Ugh... cannot just throw as-is, so...
+ // 05-Nov-2015, tatu: For now, probably best not to assume empty then
+ return false;
+ }
+ if (checkEmpty) {
+ if (!valueSer.isEmpty(prov, elemValue)) {
return false;
}
- serializers = _dynamicValueSerializers;
- }
- if (!valueSer.isEmpty(prov, elemValue)) {
+ } else if ((supp == null) || !supp.equals(value)) {
return false;
}
}
@@ -481,7 +596,7 @@
public boolean hasSingleElement(Map<?,?> value) {
return (value.size() == 1);
}
-
+
/*
/**********************************************************
/* Extended API
@@ -514,22 +629,14 @@
{
gen.writeStartObject(value);
if (!value.isEmpty()) {
- Object suppressableValue = _suppressableValue;
- if (suppressableValue == JsonInclude.Include.ALWAYS) {
- suppressableValue = null;
- } else if (suppressableValue == null) {
- if (!provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) {
- suppressableValue = JsonInclude.Include.NON_NULL;
- }
- }
if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
- value = _orderEntries(value, gen, provider, suppressableValue);
+ value = _orderEntries(value, gen, provider);
}
PropertyFilter pf;
if ((_filterId != null) && (pf = findPropertyFilter(provider, _filterId, value)) != null) {
- serializeFilteredFields(value, gen, provider, pf, suppressableValue);
- } else if (suppressableValue != null) {
- serializeOptionalFields(value, gen, provider, suppressableValue);
+ serializeFilteredFields(value, gen, provider, pf, _suppressableValue);
+ } else if ((_suppressableValue != null) || _suppressNulls) {
+ serializeOptionalFields(value, gen, provider, _suppressableValue);
} else if (_valueSerializer != null) {
serializeFieldsUsing(value, gen, provider, _valueSerializer);
} else {
@@ -544,33 +651,26 @@
TypeSerializer typeSer)
throws IOException
{
- typeSer.writeTypePrefixForObject(value, gen);
// [databind#631]: Assign current value, to be accessible by custom serializers
gen.setCurrentValue(value);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
+ typeSer.typeId(value, JsonToken.START_OBJECT));
if (!value.isEmpty()) {
- Object suppressableValue = _suppressableValue;
- if (suppressableValue == JsonInclude.Include.ALWAYS) {
- suppressableValue = null;
- } else if (suppressableValue == null) {
- if (!provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) {
- suppressableValue = JsonInclude.Include.NON_NULL;
- }
- }
if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
- value = _orderEntries(value, gen, provider, suppressableValue);
+ value = _orderEntries(value, gen, provider);
}
PropertyFilter pf;
if ((_filterId != null) && (pf = findPropertyFilter(provider, _filterId, value)) != null) {
- serializeFilteredFields(value, gen, provider, pf, suppressableValue);
- } else if (suppressableValue != null) {
- serializeOptionalFields(value, gen, provider, suppressableValue);
+ serializeFilteredFields(value, gen, provider, pf, _suppressableValue);
+ } else if ((_suppressableValue != null) || _suppressNulls) {
+ serializeOptionalFields(value, gen, provider, _suppressableValue);
} else if (_valueSerializer != null) {
serializeFieldsUsing(value, gen, provider, _valueSerializer);
} else {
serializeFields(value, gen, provider);
}
}
- typeSer.writeTypeSuffixForObject(value, gen);
+ typeSer.writeTypeSuffix(gen, typeIdDef);
}
/*
@@ -594,48 +694,35 @@
}
final JsonSerializer<Object> keySerializer = _keySerializer;
final Set<String> ignored = _ignoredEntries;
+ Object keyElem = null;
- PropertySerializerMap serializers = _dynamicValueSerializers;
-
- for (Map.Entry<?,?> entry : value.entrySet()) {
- Object valueElem = entry.getValue();
- // First, serialize key
- Object keyElem = entry.getKey();
-
- if (keyElem == null) {
- provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider);
- } else {
- // One twist: is entry ignorable? If so, skip
- if ((ignored != null) && ignored.contains(keyElem)) continue;
- keySerializer.serialize(keyElem, gen, provider);
- }
-
- // And then value
- if (valueElem == null) {
- provider.defaultSerializeNull(gen);
- continue;
- }
- JsonSerializer<Object> serializer = _valueSerializer;
- if (serializer == null) {
- Class<?> cc = valueElem.getClass();
- serializer = serializers.serializerFor(cc);
- if (serializer == null) {
- if (_valueType.hasGenericTypes()) {
- serializer = _findAndAddDynamic(serializers,
- provider.constructSpecializedType(_valueType, cc), provider);
- } else {
- serializer = _findAndAddDynamic(serializers, cc, provider);
+ try {
+ for (Map.Entry<?,?> entry : value.entrySet()) {
+ Object valueElem = entry.getValue();
+ // First, serialize key
+ keyElem = entry.getKey();
+ if (keyElem == null) {
+ provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider);
+ } else {
+ // One twist: is entry ignorable? If so, skip
+ if ((ignored != null) && ignored.contains(keyElem)) {
+ continue;
}
- serializers = _dynamicValueSerializers;
+ keySerializer.serialize(keyElem, gen, provider);
}
- }
- try {
+ // And then value
+ if (valueElem == null) {
+ provider.defaultSerializeNull(gen);
+ continue;
+ }
+ JsonSerializer<Object> serializer = _valueSerializer;
+ if (serializer == null) {
+ serializer = _findSerializer(provider, valueElem);
+ }
serializer.serialize(valueElem, gen, provider);
- } catch (Exception e) {
- // Add reference information
- String keyDesc = ""+keyElem;
- wrapAndThrow(provider, e, value, keyDesc);
}
+ } catch (Exception e) { // Add reference information
+ wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
@@ -652,7 +739,7 @@
return;
}
final Set<String> ignored = _ignoredEntries;
- PropertySerializerMap serializers = _dynamicValueSerializers;
+ final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
// First find key serializer
@@ -669,29 +756,24 @@
final Object valueElem = entry.getValue();
JsonSerializer<Object> valueSer;
if (valueElem == null) {
- if (suppressableValue != null) { // all suppressions include null-suppression
+ if (_suppressNulls) { // all suppressions include null-suppression
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
- Class<?> cc = valueElem.getClass();
- valueSer = serializers.serializerFor(cc);
- if (valueSer == null) {
- if (_valueType.hasGenericTypes()) {
- valueSer = _findAndAddDynamic(serializers,
- provider.constructSpecializedType(_valueType, cc), provider);
- } else {
- valueSer = _findAndAddDynamic(serializers, cc, provider);
- }
- serializers = _dynamicValueSerializers;
- }
+ valueSer = _findSerializer(provider, valueElem);
}
// also may need to skip non-empty values:
- if ((suppressableValue == JsonInclude.Include.NON_EMPTY)
- && valueSer.isEmpty(provider, valueElem)) {
- continue;
+ if (checkEmpty) {
+ if (valueSer.isEmpty(provider, valueElem)) {
+ continue;
+ }
+ } else if (suppressableValue != null) {
+ if (suppressableValue.equals(valueElem)) {
+ continue;
+ }
}
}
// and then serialize, if all went well
@@ -699,8 +781,7 @@
keySerializer.serialize(keyElem, gen, provider);
valueSer.serialize(valueElem, gen, provider);
} catch (Exception e) {
- String keyDesc = ""+keyElem;
- wrapAndThrow(provider, e, value, keyDesc);
+ wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
@@ -738,8 +819,7 @@
ser.serializeWithType(valueElem, gen, provider, typeSer);
}
} catch (Exception e) {
- String keyDesc = ""+keyElem;
- wrapAndThrow(provider, e, value, keyDesc);
+ wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
@@ -757,10 +837,9 @@
throws IOException
{
final Set<String> ignored = _ignoredEntries;
-
- PropertySerializerMap serializers = _dynamicValueSerializers;
final MapProperty prop = new MapProperty(_valueTypeSerializer, _property);
-
+ final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
+
for (Map.Entry<?,?> entry : value.entrySet()) {
// First, serialize key; unless ignorable by key
final Object keyElem = entry.getKey();
@@ -778,49 +857,36 @@
JsonSerializer<Object> valueSer;
// And then value
if (valueElem == null) {
- if (suppressableValue != null) { // all suppressions include null-suppression
+ if (_suppressNulls) {
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
- Class<?> cc = valueElem.getClass();
- valueSer = serializers.serializerFor(cc);
- if (valueSer == null) {
- if (_valueType.hasGenericTypes()) {
- valueSer = _findAndAddDynamic(serializers,
- provider.constructSpecializedType(_valueType, cc), provider);
- } else {
- valueSer = _findAndAddDynamic(serializers, cc, provider);
- }
- serializers = _dynamicValueSerializers;
- }
+ valueSer = _findSerializer(provider, valueElem);
}
// also may need to skip non-empty values:
- if ((suppressableValue == JsonInclude.Include.NON_EMPTY)
- && valueSer.isEmpty(provider, valueElem)) {
- continue;
+ if (checkEmpty) {
+ if (valueSer.isEmpty(provider, valueElem)) {
+ continue;
+ }
+ } else if (suppressableValue != null) {
+ if (suppressableValue.equals(valueElem)) {
+ continue;
+ }
}
}
// and with that, ask filter to handle it
- prop.reset(keyElem, keySerializer, valueSer);
+ prop.reset(keyElem, valueElem, keySerializer, valueSer);
try {
- filter.serializeAsField(valueElem, gen, provider, prop);
+ filter.serializeAsField(value, gen, provider, prop);
} catch (Exception e) {
- String keyDesc = ""+keyElem;
- wrapAndThrow(provider, e, value, keyDesc);
+ wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
- @Deprecated // since 2.5
- public void serializeFilteredFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider,
- PropertyFilter filter) throws IOException {
- serializeFilteredFields(value, gen, provider, filter,
- provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES) ? null : JsonInclude.Include.NON_NULL);
- }
-
/**
* @since 2.5
*/
@@ -829,7 +895,7 @@
throws IOException
{
final Set<String> ignored = _ignoredEntries;
- PropertySerializerMap serializers = _dynamicValueSerializers;
+ final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
for (Map.Entry<?,?> entry : value.entrySet()) {
Object keyElem = entry.getKey();
@@ -846,44 +912,97 @@
// And then value
JsonSerializer<Object> valueSer;
if (valueElem == null) {
- if (suppressableValue != null) { // all suppression include null suppression
+ if (_suppressNulls) { // all suppression include null suppression
continue;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
- Class<?> cc = valueElem.getClass();
- valueSer = serializers.serializerFor(cc);
if (valueSer == null) {
- if (_valueType.hasGenericTypes()) {
- valueSer = _findAndAddDynamic(serializers,
- provider.constructSpecializedType(_valueType, cc), provider);
- } else {
- valueSer = _findAndAddDynamic(serializers, cc, provider);
- }
- serializers = _dynamicValueSerializers;
+ valueSer = _findSerializer(provider, valueElem);
}
// also may need to skip non-empty values:
- if ((suppressableValue == JsonInclude.Include.NON_EMPTY)
- && valueSer.isEmpty(provider, valueElem)) {
- continue;
+ if (checkEmpty) {
+ if (valueSer.isEmpty(provider, valueElem)) {
+ continue;
+ }
+ } else if (suppressableValue != null) {
+ if (suppressableValue.equals(valueElem)) {
+ continue;
+ }
}
}
keySerializer.serialize(keyElem, gen, provider);
try {
valueSer.serializeWithType(valueElem, gen, provider, _valueTypeSerializer);
} catch (Exception e) {
- String keyDesc = ""+keyElem;
- wrapAndThrow(provider, e, value, keyDesc);
+ wrapAndThrow(provider, e, value, String.valueOf(keyElem));
}
}
}
- @Deprecated // since 2.5
- protected void serializeTypedFields(Map<?,?> value, JsonGenerator gen, SerializerProvider provider)
- throws IOException {
- serializeTypedFields(value, gen, provider,
- provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES) ? null : JsonInclude.Include.NON_NULL);
+ /**
+ * Helper method used when we have a JSON Filter to use AND contents are
+ * "any properties" of a POJO.
+ *
+ * @param bean Enclosing POJO that has any-getter used to obtain "any properties"
+ *
+ * @since 2.9
+ */
+ public void serializeFilteredAnyProperties(SerializerProvider provider, JsonGenerator gen,
+ Object bean, Map<?,?> value, PropertyFilter filter,
+ Object suppressableValue)
+ throws IOException
+ {
+ final Set<String> ignored = _ignoredEntries;
+ final MapProperty prop = new MapProperty(_valueTypeSerializer, _property);
+ final boolean checkEmpty = (MARKER_FOR_EMPTY == suppressableValue);
+
+ for (Map.Entry<?,?> entry : value.entrySet()) {
+ // First, serialize key; unless ignorable by key
+ final Object keyElem = entry.getKey();
+ if (ignored != null && ignored.contains(keyElem)) continue;
+
+ JsonSerializer<Object> keySerializer;
+ if (keyElem == null) {
+ keySerializer = provider.findNullKeySerializer(_keyType, _property);
+ } else {
+ keySerializer = _keySerializer;
+ }
+ // or by value; nulls often suppressed
+ final Object valueElem = entry.getValue();
+
+ JsonSerializer<Object> valueSer;
+ // And then value
+ if (valueElem == null) {
+ if (_suppressNulls) {
+ continue;
+ }
+ valueSer = provider.getDefaultNullValueSerializer();
+ } else {
+ valueSer = _valueSerializer;
+ if (valueSer == null) {
+ valueSer = _findSerializer(provider, valueElem);
+ }
+ // also may need to skip non-empty values:
+ if (checkEmpty) {
+ if (valueSer.isEmpty(provider, valueElem)) {
+ continue;
+ }
+ } else if (suppressableValue != null) {
+ if (suppressableValue.equals(valueElem)) {
+ continue;
+ }
+ }
+ }
+ // and with that, ask filter to handle it
+ prop.reset(keyElem, valueElem, keySerializer, valueSer);
+ try {
+ filter.serializeAsField(bean, gen, provider, prop);
+ } catch (Exception e) {
+ wrapAndThrow(provider, e, value, String.valueOf(keyElem));
+ }
+ }
}
/*
@@ -891,11 +1010,11 @@
/* Schema related functionality
/**********************************************************
*/
-
+
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
- //(ryan) even though it's possible to statically determine the "value" type of the map,
+ // even though it's possible to statically determine the "value" type of the map,
// there's no way to statically determine the keys, so the "Entries" can't be determined.
return createSchemaNode("object", true);
}
@@ -904,7 +1023,7 @@
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException
{
- JsonMapFormatVisitor v2 = (visitor == null) ? null : visitor.expectMapFormat(typeHint);
+ JsonMapFormatVisitor v2 = visitor.expectMapFormat(typeHint);
if (v2 != null) {
v2.keyFormat(_keySerializer, _keyType);
JsonSerializer<?> valueSer = _valueSerializer;
@@ -921,7 +1040,7 @@
/* Internal helper methods
/**********************************************************
*/
-
+
protected final JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map,
Class<?> type, SerializerProvider provider) throws JsonMappingException
{
@@ -944,7 +1063,7 @@
}
protected Map<?,?> _orderEntries(Map<?,?> input, JsonGenerator gen,
- SerializerProvider provider, Object suppressableValue) throws IOException
+ SerializerProvider provider) throws IOException
{
// minor optimization: may already be sorted?
if (input instanceof SortedMap<?,?>) {
@@ -959,7 +1078,7 @@
for (Map.Entry<?,?> entry : input.entrySet()) {
Object key = entry.getKey();
if (key == null) {
- _writeNullKeyedEntry(gen, provider, suppressableValue, entry.getValue());
+ _writeNullKeyedEntry(gen, provider, entry.getValue());
continue;
}
result.put(key, entry.getValue());
@@ -986,42 +1105,50 @@
}
protected void _writeNullKeyedEntry(JsonGenerator gen, SerializerProvider provider,
- Object suppressableValue, Object value) throws IOException
+ Object value) throws IOException
{
JsonSerializer<Object> keySerializer = provider.findNullKeySerializer(_keyType, _property);
JsonSerializer<Object> valueSer;
if (value == null) {
- if (suppressableValue != null) { // all suppressions include null-suppression
+ if (_suppressNulls) {
return;
}
valueSer = provider.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
- Class<?> cc = value.getClass();
- valueSer = _dynamicValueSerializers.serializerFor(cc);
- if (valueSer == null) {
- if (_valueType.hasGenericTypes()) {
- valueSer = _findAndAddDynamic(_dynamicValueSerializers,
- provider.constructSpecializedType(_valueType, cc), provider);
- } else {
- valueSer = _findAndAddDynamic(_dynamicValueSerializers, cc, provider);
- }
- }
+ valueSer = _findSerializer(provider, value);
}
- // also may need to skip non-empty values:
- if ((suppressableValue == JsonInclude.Include.NON_EMPTY)
- && valueSer.isEmpty(provider, value)) {
+ if (_suppressableValue == MARKER_FOR_EMPTY) {
+ if (valueSer.isEmpty(provider, value)) {
+ return;
+ }
+ } else if ((_suppressableValue != null)
+ && (_suppressableValue.equals(value))) {
return;
}
}
- // and then serialize, if all went well
+
try {
keySerializer.serialize(null, gen, provider);
valueSer.serialize(value, gen, provider);
} catch (Exception e) {
- String keyDesc = "";
- wrapAndThrow(provider, e, value, keyDesc);
+ wrapAndThrow(provider, e, value, "");
}
}
+
+ private final JsonSerializer<Object> _findSerializer(SerializerProvider provider,
+ Object value) throws JsonMappingException
+ {
+ final Class<?> cc = value.getClass();
+ JsonSerializer<Object> valueSer = _dynamicValueSerializers.serializerFor(cc);
+ if (valueSer != null) {
+ return valueSer;
+ }
+ if (_valueType.hasGenericTypes()) {
+ return _findAndAddDynamic(_dynamicValueSerializers,
+ provider.constructSpecializedType(_valueType, cc), provider);
+ }
+ return _findAndAddDynamic(_dynamicValueSerializers, cc, provider);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java
index a0f75a3..04b1cd5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java
@@ -14,6 +14,7 @@
* {@link java.lang.Double} and {@link java.lang.Boolean}.
*/
@SuppressWarnings("serial")
+@Deprecated // since 2.9
public abstract class NonTypedScalarSerializerBase<T>
extends StdScalarSerializer<T>
{
@@ -24,7 +25,7 @@
protected NonTypedScalarSerializerBase(Class<?> t, boolean bogus) {
super(t, bogus);
}
-
+
@Override
public final void serializeWithType(T value, JsonGenerator gen, SerializerProvider provider,
TypeSerializer typeSer) throws IOException
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NullSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NullSerializer.java
index 2171644..e7e8a2f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NullSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NullSerializer.java
@@ -34,7 +34,7 @@
* Although this method should rarely get called, for convenience we should override
* it, and handle it same way as "natural" types: by serializing exactly as is,
* without type decorations. The most common possible use case is that of delegation
- * by JSON filter; caller can not know what kind of serializer it gets handed.
+ * by JSON filter; caller cannot know what kind of serializer it gets handed.
*/
@Override
public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers,
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java
index 4e57a2a..1180798 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java
@@ -5,11 +5,13 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
* As a fallback, we may need to use this serializer for other
@@ -20,6 +22,7 @@
@SuppressWarnings("serial")
public class NumberSerializer
extends StdScalarSerializer<Number>
+ implements ContextualSerializer
{
/**
* Static instance that is only to be used for {@link java.lang.Number}.
@@ -38,6 +41,21 @@
}
@Override
+ public JsonSerializer<?> createContextual(SerializerProvider prov,
+ BeanProperty property) throws JsonMappingException
+ {
+ JsonFormat.Value format = findFormatOverrides(prov, property, handledType());
+ if (format != null) {
+ switch (format.getShape()) {
+ case STRING:
+ return ToStringSerializer.instance;
+ default:
+ }
+ }
+ return this;
+ }
+
+ @Override
public void serialize(Number value, JsonGenerator g, SerializerProvider provider) throws IOException
{
// should mostly come in as one of these two:
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java
index d71bc40..a113ae2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java
@@ -1,24 +1,20 @@
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Type;
import com.fasterxml.jackson.annotation.JsonFormat;
+
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
-import com.fasterxml.jackson.databind.jsonschema.SchemaAware;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
-import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
-import com.fasterxml.jackson.databind.type.ArrayType;
-import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Generic serializer for Object arrays (<code>Object[]</code>).
@@ -51,7 +47,7 @@
protected JsonSerializer<Object> _elementSerializer;
/**
- * If element type can not be statically determined, mapping from
+ * If element type cannot be statically determined, mapping from
* runtime type to serializer is handled using this object
*/
protected PropertySerializerMap _dynamicSerializers;
@@ -79,6 +75,8 @@
_elementType = src._elementType;
_valueTypeSerializer = vts;
_staticTyping = src._staticTyping;
+ // 22-Nov-2018, tatu: probably safe (even with [databind#2181]) since it's just
+ // inclusion, type serializer but NOT serializer
_dynamicSerializers = src._dynamicSerializers;
_elementSerializer = src._elementSerializer;
}
@@ -92,7 +90,8 @@
_elementType = src._elementType;
_valueTypeSerializer = vts;
_staticTyping = src._staticTyping;
- _dynamicSerializers = src._dynamicSerializers;
+ // [databind#2181]: may not be safe to reuse, start from empty
+ _dynamicSerializers = PropertySerializerMap.emptyForProperties();
_elementSerializer = (JsonSerializer<Object>) elementSerializer;
}
@@ -153,7 +152,7 @@
ser = _elementSerializer;
}
// [databind#124]: May have a content converter
- ser = findConvertingContentSerializer(serializers, property, ser);
+ ser = findContextualConvertingSerializer(serializers, property, ser);
if (ser == null) {
// 30-Sep-2012, tatu: One more thing -- if explicit content type is annotated,
// we can consider it a static case as well.
@@ -162,8 +161,6 @@
ser = serializers.findValueSerializer(_elementType, property);
}
}
- } else {
- ser = serializers.handleSecondaryContextualization(ser, property);
}
return withResolved(property, vts, ser, unwrapSingle);
}
@@ -186,7 +183,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, Object[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -245,7 +242,6 @@
Class<?> cc = elem.getClass();
JsonSerializer<Object> serializer = serializers.serializerFor(cc);
if (serializer == null) {
- // To fix [JACKSON-508]
if (_elementType.hasGenericTypes()) {
serializer = _findAndAddDynamic(serializers,
provider.constructSpecializedType(_elementType, cc), provider);
@@ -255,22 +251,8 @@
}
serializer.serialize(elem, gen, provider);
}
- } catch (IOException ioe) {
- throw ioe;
} catch (Exception e) {
- // [JACKSON-55] Need to add reference information
- /* 05-Mar-2009, tatu: But one nasty edge is when we get
- * StackOverflow: usually due to infinite loop. But that gets
- * hidden within an InvocationTargetException...
- */
- Throwable t = e;
- while (t instanceof InvocationTargetException && t.getCause() != null) {
- t = t.getCause();
- }
- if (t instanceof Error) {
- throw (Error) t;
- }
- throw JsonMappingException.wrapWithPath(t, elem, i);
+ wrapAndThrow(provider, e, elem, i);
}
}
@@ -295,17 +277,8 @@
ser.serializeWithType(elem, jgen, provider, typeSer);
}
}
- } catch (IOException ioe) {
- throw ioe;
} catch (Exception e) {
- Throwable t = e;
- while (t instanceof InvocationTargetException && t.getCause() != null) {
- t = t.getCause();
- }
- if (t instanceof Error) {
- throw (Error) t;
- }
- throw JsonMappingException.wrapWithPath(t, elem, i);
+ wrapAndThrow(provider, e, elem, i);
}
}
@@ -330,44 +303,10 @@
}
serializer.serializeWithType(elem, jgen, provider, typeSer);
}
- } catch (IOException ioe) {
- throw ioe;
} catch (Exception e) {
- Throwable t = e;
- while (t instanceof InvocationTargetException && t.getCause() != null) {
- t = t.getCause();
- }
- if (t instanceof Error) {
- throw (Error) t;
- }
- throw JsonMappingException.wrapWithPath(t, elem, i);
+ wrapAndThrow(provider, e, elem, i);
}
}
-
- @SuppressWarnings("deprecation")
- @Override
- public JsonNode getSchema(SerializerProvider provider, Type typeHint)
- throws JsonMappingException
- {
- ObjectNode o = createSchemaNode("array", true);
- if (typeHint != null) {
- JavaType javaType = provider.constructType(typeHint);
- if (javaType.isArrayType()) {
- Class<?> componentType = ((ArrayType) javaType).getContentType().getRawClass();
- // 15-Oct-2010, tatu: We can't serialize plain Object.class; but what should it produce here? Untyped?
- if (componentType == Object.class) {
- o.set("items", com.fasterxml.jackson.databind.jsonschema.JsonSchema.getDefaultSchemaNode());
- } else {
- JsonSerializer<Object> ser = provider.findValueSerializer(componentType, _property);
- JsonNode schemaNode = (ser instanceof SchemaAware) ?
- ((SchemaAware) ser).getSchema(provider, null) :
- com.fasterxml.jackson.databind.jsonschema.JsonSchema.getDefaultSchemaNode();
- o.set("items", schemaNode);
- }
- }
- }
- return o;
- }
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
@@ -375,11 +314,17 @@
{
JsonArrayFormatVisitor arrayVisitor = visitor.expectArrayFormat(typeHint);
if (arrayVisitor != null) {
+ JavaType contentType = _elementType;
+
+ // [databind#1793]: Was getting `null` for `typeHint`. But why would we even use it...
+/*
TypeFactory tf = visitor.getProvider().getTypeFactory();
- JavaType contentType = tf.moreSpecificType(_elementType, typeHint.getContentType());
+ contentType = tf.moreSpecificType(_elementType, typeHint.getContentType());
if (contentType == null) {
- throw JsonMappingException.from(visitor.getProvider(), "Could not resolve type");
+ visitor.getProvider().reportBadDefinition(_elementType,
+ "Could not resolve type: "+_elementType);
}
+*/
JsonSerializer<?> valueSer = _elementSerializer;
if (valueSer == null) {
valueSer = visitor.getProvider().findValueSerializer(contentType, _property);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/RawSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/RawSerializer.java
index d6ae1da..bed8e7a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/RawSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/RawSerializer.java
@@ -4,6 +4,7 @@
import java.io.IOException;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -18,7 +19,7 @@
{
/**
* Constructor takes in expected type of values; but since caller
- * typically can not really provide actual type parameter, we will
+ * typically cannot really provide actual type parameter, we will
* just take wild card and coerce type.
*/
public RawSerializer(Class<?> cls) {
@@ -31,13 +32,14 @@
}
@Override
- public void serializeWithType(T value, JsonGenerator jgen, SerializerProvider provider,
+ public void serializeWithType(T value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException
{
- typeSer.writeTypePrefixForScalar(value, jgen);
- serialize(value, jgen, provider);
- typeSer.writeTypeSuffixForScalar(value, jgen);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.VALUE_EMBEDDED_OBJECT));
+ serialize(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java
index 095d209..00186c6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java
@@ -12,6 +12,8 @@
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import com.fasterxml.jackson.databind.type.ReferenceType;
+import com.fasterxml.jackson.databind.util.ArrayBuilders;
+import com.fasterxml.jackson.databind.util.BeanUtil;
import com.fasterxml.jackson.databind.util.NameTransformer;
/**
@@ -28,6 +30,11 @@
private static final long serialVersionUID = 1L;
/**
+ * @since 2.9
+ */
+ public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY;
+
+ /**
* Value type
*/
protected final JavaType _referredType;
@@ -50,19 +57,39 @@
protected final NameTransformer _unwrapper;
/**
- * Further guidance on serialization-inclusion (or not), regarding
- * contained value (if any).
- */
- protected final JsonInclude.Include _contentInclusion;
-
- /**
- * If element type can not be statically determined, mapping from
+ * If element type cannot be statically determined, mapping from
* runtime type to serializer is handled using this object
*/
protected transient PropertySerializerMap _dynamicSerializers;
/*
/**********************************************************
+ /* Config settings, filtering
+ /**********************************************************
+ */
+
+ /**
+ * Value that indicates suppression mechanism to use for <b>values contained</b>;
+ * either "filter" (of which <code>equals()</code> is called), or marker
+ * value of {@link #MARKER_FOR_EMPTY}, or null to indicate no filtering for
+ * non-null values.
+ * Note that inclusion value for Map instance itself is handled by caller (POJO
+ * property that refers to the Map value).
+ *
+ * @since 2.9
+ */
+ protected final Object _suppressableValue;
+
+ /**
+ * Flag that indicates what to do with `null` values, distinct from
+ * handling of {@link #_suppressableValue}
+ *
+ * @since 2.9
+ */
+ protected final boolean _suppressNulls;
+
+ /*
+ /**********************************************************
/* Constructors, factory methods
/**********************************************************
*/
@@ -76,7 +103,8 @@
_valueTypeSerializer = vts;
_valueSerializer = ser;
_unwrapper = null;
- _contentInclusion = null;
+ _suppressableValue = null;
+ _suppressNulls = false;
_dynamicSerializers = PropertySerializerMap.emptyForProperties();
}
@@ -84,32 +112,32 @@
protected ReferenceTypeSerializer(ReferenceTypeSerializer<?> base, BeanProperty property,
TypeSerializer vts, JsonSerializer<?> valueSer,
NameTransformer unwrapper,
- JsonInclude.Include contentIncl)
+ Object suppressableValue, boolean suppressNulls)
{
super(base);
_referredType = base._referredType;
- _dynamicSerializers = base._dynamicSerializers;
+ // [databind#2181]: may not be safe to reuse, start from empty
+ _dynamicSerializers = PropertySerializerMap.emptyForProperties();
_property = property;
_valueTypeSerializer = vts;
_valueSerializer = (JsonSerializer<Object>) valueSer;
_unwrapper = unwrapper;
- if ((contentIncl == JsonInclude.Include.USE_DEFAULTS)
- || (contentIncl == JsonInclude.Include.ALWAYS)) {
- _contentInclusion = null;
- } else {
- _contentInclusion = contentIncl;
- }
+ _suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
@Override
public JsonSerializer<T> unwrappingSerializer(NameTransformer transformer) {
- JsonSerializer<Object> ser = _valueSerializer;
- if (ser != null) {
- ser = ser.unwrappingSerializer(transformer);
+ JsonSerializer<Object> valueSer = _valueSerializer;
+ if (valueSer != null) {
+ valueSer = valueSer.unwrappingSerializer(transformer);
}
NameTransformer unwrapper = (_unwrapper == null) ? transformer
: NameTransformer.chainedTransformer(transformer, _unwrapper);
- return withResolved(_property, _valueTypeSerializer, ser, unwrapper, _contentInclusion);
+ if ((_valueSerializer == valueSer) && (_unwrapper == unwrapper)) {
+ return this;
+ }
+ return withResolved(_property, _valueTypeSerializer, valueSer, unwrapper);
}
/*
@@ -117,13 +145,39 @@
/* Abstract methods to implement
/**********************************************************
*/
-
+
+ /**
+ * Mutant factory method called when changes are needed; should construct
+ * newly configured instance with new values as indicated.
+ *<p>
+ * NOTE: caller has verified that there are changes, so implementations
+ * need NOT check if a new instance is needed.
+ *
+ * @since 2.9
+ */
protected abstract ReferenceTypeSerializer<T> withResolved(BeanProperty prop,
TypeSerializer vts, JsonSerializer<?> valueSer,
- NameTransformer unwrapper,
- JsonInclude.Include contentIncl);
+ NameTransformer unwrapper);
- protected abstract boolean _isValueEmpty(T value);
+ /**
+ * Mutant factory method called to create a differently constructed instance,
+ * specifically with different exclusion rules for contained value.
+ *<p>
+ * NOTE: caller has verified that there are changes, so implementations
+ * need NOT check if a new instance is needed.
+ *
+ * @since 2.9
+ */
+ public abstract ReferenceTypeSerializer<T> withContentInclusion(Object suppressableValue,
+ boolean suppressNulls);
+
+ /**
+ * Method called to see if there is a value present or not.
+ * Note that value itself may still be `null`, even if present,
+ * if referential type allows three states (absent, present-null,
+ * present-non-null); some only allow two (absent, present-non-null).
+ */
+ protected abstract boolean _isValuePresent(T value);
protected abstract Object _getReferenced(T value);
@@ -144,7 +198,7 @@
typeSer = typeSer.forProperty(property);
}
// First: do we have an annotation override from property?
- JsonSerializer<?> ser = findAnnotatedContentSerializer(provider, property);;
+ JsonSerializer<?> ser = findAnnotatedContentSerializer(provider, property);
if (ser == null) {
// If not, use whatever was configured by type
ser = _valueSerializer;
@@ -157,14 +211,68 @@
ser = provider.handlePrimaryContextualization(ser, property);
}
}
- // Also: may want to have more refined exclusion based on referenced value
- JsonInclude.Include contentIncl = _contentInclusion;
- JsonInclude.Value incl = findIncludeOverrides(provider, property, handledType());
- JsonInclude.Include newIncl = incl.getContentInclusion();
- if ((newIncl != contentIncl) && (newIncl != JsonInclude.Include.USE_DEFAULTS)) {
- contentIncl = newIncl;
+ // First, resolve wrt property, resolved serializers
+ ReferenceTypeSerializer<?> refSer;
+ if ((_property == property)
+ && (_valueTypeSerializer == typeSer) && (_valueSerializer == ser)) {
+ refSer = this;
+ } else {
+ refSer = withResolved(property, typeSer, ser, _unwrapper);
}
- return withResolved(property, typeSer, ser, _unwrapper, contentIncl);
+
+ // and then see if we have property-inclusion overrides
+ if (property != null) {
+ JsonInclude.Value inclV = property.findPropertyInclusion(provider.getConfig(), handledType());
+ if (inclV != null) {
+ JsonInclude.Include incl = inclV.getContentInclusion();
+
+ if (incl != JsonInclude.Include.USE_DEFAULTS) {
+ Object valueToSuppress;
+ boolean suppressNulls;
+ switch (incl) {
+ case NON_DEFAULT:
+ valueToSuppress = BeanUtil.getDefaultValue(_referredType);
+ suppressNulls = true;
+ if (valueToSuppress != null) {
+ if (valueToSuppress.getClass().isArray()) {
+ valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
+ }
+ }
+ break;
+ case NON_ABSENT:
+ suppressNulls = true;
+ valueToSuppress = _referredType.isReferenceType() ? MARKER_FOR_EMPTY : null;
+ break;
+ case NON_EMPTY:
+ suppressNulls = true;
+ valueToSuppress = MARKER_FOR_EMPTY;
+ break;
+ case CUSTOM:
+ valueToSuppress = provider.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) { // is this legal?
+ suppressNulls = true;
+ } else {
+ suppressNulls = provider.includeFilterSuppressNulls(valueToSuppress);
+ }
+ break;
+ case NON_NULL:
+ valueToSuppress = null;
+ suppressNulls = true;
+ break;
+ case ALWAYS: // default
+ default:
+ valueToSuppress = null;
+ suppressNulls = false;
+ break;
+ }
+ if ((_suppressableValue != valueToSuppress)
+ || (_suppressNulls != suppressNulls)) {
+ refSer = refSer.withContentInclusion(valueToSuppress, suppressNulls);
+ }
+ }
+ }
+ }
+ return refSer;
}
protected boolean _useStatic(SerializerProvider provider, BeanProperty property,
@@ -209,13 +317,17 @@
@Override
public boolean isEmpty(SerializerProvider provider, T value)
{
- if ((value == null) || _isValueEmpty(value)) {
+ // First, absent value (note: null check is just sanity check here)
+ if (!_isValuePresent(value)) {
return true;
}
- if (_contentInclusion == null) {
+ Object contents = _getReferenced(value);
+ if (contents == null) { // possible for explicitly contained `null`
+ return _suppressNulls;
+ }
+ if (_suppressableValue == null) {
return false;
}
- Object contents = _getReferenced(value);
JsonSerializer<Object> ser = _valueSerializer;
if (ser == null) {
try {
@@ -224,7 +336,10 @@
throw new RuntimeJsonMappingException(e);
}
}
- return ser.isEmpty(provider, contents);
+ if (_suppressableValue == MARKER_FOR_EMPTY) {
+ return ser.isEmpty(provider, contents);
+ }
+ return _suppressableValue.equals(contents);
}
@Override
@@ -232,6 +347,13 @@
return (_unwrapper != null);
}
+ /**
+ * @since 2.9
+ */
+ public JavaType getReferredType() {
+ return _referredType;
+ }
+
/*
/**********************************************************
/* Serialization methods
@@ -321,33 +443,36 @@
* serializer.
*/
private final JsonSerializer<Object> _findCachedSerializer(SerializerProvider provider,
- Class<?> type) throws JsonMappingException
+ Class<?> rawType) throws JsonMappingException
{
- JsonSerializer<Object> ser = _dynamicSerializers.serializerFor(type);
+ JsonSerializer<Object> ser = _dynamicSerializers.serializerFor(rawType);
if (ser == null) {
- ser = _findSerializer(provider, type, _property);
+ // NOTE: call this instead of `map._findAndAddDynamic(...)` (which in turn calls
+ // `findAndAddSecondarySerializer`) since we may need to apply unwrapper
+ // too, before caching. But calls made are the same
+ if (_referredType.hasGenericTypes()) {
+ // [databind#1673] Must ensure we will resolve all available type information
+ // so as not to miss generic declaration of, say, `List<GenericPojo>`...
+ JavaType fullType = provider.constructSpecializedType(_referredType, rawType);
+ ser = provider.findValueSerializer(fullType, _property);
+ } else {
+ ser = provider.findValueSerializer(rawType, _property);
+ }
if (_unwrapper != null) {
ser = ser.unwrappingSerializer(_unwrapper);
}
- _dynamicSerializers = _dynamicSerializers.newWith(type, ser);
+ _dynamicSerializers = _dynamicSerializers.newWith(rawType, ser);
}
return ser;
}
private final JsonSerializer<Object> _findSerializer(SerializerProvider provider,
- Class<?> type, BeanProperty prop) throws JsonMappingException
- {
- // 13-Mar-2017, tatu: Used to call `findTypeValueSerializer()`, but contextualization
- // not working for that case for some reason
- // return provider.findTypedValueSerializer(type, true, prop);
- return provider.findValueSerializer(type, prop);
- }
-
- private final JsonSerializer<Object> _findSerializer(SerializerProvider provider,
JavaType type, BeanProperty prop) throws JsonMappingException
{
// 13-Mar-2017, tatu: Used to call `findTypeValueSerializer()`, but contextualization
// not working for that case for some reason
+ // 15-Jan-2017, tatu: ... possibly because we need to access "secondary" serializer,
+ // not primary (primary being one for Reference type itself, not value)
// return provider.findTypedValueSerializer(type, true, prop);
return provider.findValueSerializer(type, prop);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/SerializableSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/SerializableSerializer.java
index 793a49c..93efb68 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/SerializableSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/SerializableSerializer.java
@@ -1,28 +1,21 @@
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
-import java.lang.reflect.Type;
-import java.util.concurrent.atomic.AtomicReference;
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.JsonSerializable;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
-import com.fasterxml.jackson.databind.jsonschema.JsonSerializableSchema;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Generic handler for types that implement {@link JsonSerializable}.
*<p>
* Note: given that this is used for anything that implements
- * interface, can not be checked for direct class equivalence.
+ * interface, cannot be checked for direct class equivalence.
*/
@JacksonStdImpl
@SuppressWarnings("serial")
@@ -31,9 +24,6 @@
{
public final static SerializableSerializer instance = new SerializableSerializer();
- // Ugh. Should NOT need this...
- private final static AtomicReference<ObjectMapper> _mapperReference = new AtomicReference<ObjectMapper>();
-
protected SerializableSerializer() { super(JsonSerializable.class); }
@Override
@@ -54,61 +44,6 @@
TypeSerializer typeSer) throws IOException {
value.serializeWithType(gen, serializers, typeSer);
}
-
- @Override
- @SuppressWarnings("deprecation")
- public JsonNode getSchema(SerializerProvider provider, Type typeHint)
- throws JsonMappingException
- {
- ObjectNode objectNode = createObjectNode();
- String schemaType = "any";
- String objectProperties = null;
- String itemDefinition = null;
- if (typeHint != null) {
- Class<?> rawClass = TypeFactory.rawClass(typeHint);
- if (rawClass.isAnnotationPresent(JsonSerializableSchema.class)) {
- JsonSerializableSchema schemaInfo = rawClass.getAnnotation(JsonSerializableSchema.class);
- schemaType = schemaInfo.schemaType();
- if (!JsonSerializableSchema.NO_VALUE.equals(schemaInfo.schemaObjectPropertiesDefinition())) {
- objectProperties = schemaInfo.schemaObjectPropertiesDefinition();
- }
- if (!JsonSerializableSchema.NO_VALUE.equals(schemaInfo.schemaItemDefinition())) {
- itemDefinition = schemaInfo.schemaItemDefinition();
- }
- }
- }
- /* 19-Mar-2012, tatu: geez, this is butt-ugly abonimation of code...
- * really, really should not require back ref to an ObjectMapper.
- */
- objectNode.put("type", schemaType);
- if (objectProperties != null) {
- try {
- objectNode.set("properties", _getObjectMapper().readTree(objectProperties));
- } catch (IOException e) {
- provider.reportMappingProblem("Failed to parse @JsonSerializableSchema.schemaObjectPropertiesDefinition value");
- }
- }
- if (itemDefinition != null) {
- try {
- objectNode.set("items", _getObjectMapper().readTree(itemDefinition));
- } catch (IOException e) {
- provider.reportMappingProblem("Failed to parse @JsonSerializableSchema.schemaItemDefinition value");
- }
- }
- // always optional, no need to specify:
- //objectNode.put("required", false);
- return objectNode;
- }
-
- private final static synchronized ObjectMapper _getObjectMapper()
- {
- ObjectMapper mapper = _mapperReference.get();
- if (mapper == null) {
- mapper = new ObjectMapper();
- _mapperReference.set(mapper);
- }
- return mapper;
- }
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java
index 2857b6f..c3cf092 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java
@@ -1,14 +1,12 @@
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
-import java.lang.reflect.Type;
import java.text.DateFormat;
-import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
-import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
/**
* Compared to regular {@link java.util.Date} serialization, we do use String
@@ -21,19 +19,17 @@
extends DateTimeSerializerBase<java.sql.Date>
{
public SqlDateSerializer() {
- /* 12-Apr-2014, tatu: for now, pass explicit 'false' to mean 'not using timestamp',
- * for backwards compatibility; this differs from other Date/Calendar types.
- */
- this(Boolean.FALSE);
+ // 11-Oct-2016, tatu: As per [databind#219] fixed for 2.9; was passing `false` prior
+ this(null, null);
}
- protected SqlDateSerializer(Boolean useTimestamp) {
- super(java.sql.Date.class, useTimestamp, null);
+ protected SqlDateSerializer(Boolean useTimestamp, DateFormat customFormat) {
+ super(java.sql.Date.class, useTimestamp, customFormat);
}
@Override
public SqlDateSerializer withFormat(Boolean timestamp, DateFormat customFormat) {
- return new SqlDateSerializer(timestamp);
+ return new SqlDateSerializer(timestamp, customFormat);
}
@Override
@@ -42,26 +38,21 @@
}
@Override
- public void serialize(java.sql.Date value, JsonGenerator gen, SerializerProvider provider)
- throws IOException, JsonGenerationException
+ public void serialize(java.sql.Date value, JsonGenerator g, SerializerProvider provider)
+ throws IOException
{
if (_asTimestamp(provider)) {
- gen.writeNumber(_timestamp(value));
- } else {
- gen.writeString(value.toString());
+ g.writeNumber(_timestamp(value));
+ return;
}
- }
-
- @Override
- public JsonNode getSchema(SerializerProvider provider, Type typeHint)
- {
- //todo: (ryan) add a format for the date in the schema?
- return createSchemaNode("string", true);
- }
-
- @Override
- public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
- {
- _acceptJsonFormatVisitor(visitor, typeHint, _useTimestamp);
+ // Alas, can't just call `_serializeAsString()`....
+ if (_customFormat == null) {
+ // 11-Oct-2016, tatu: For backwards-compatibility purposes, we shall just use
+ // the awful standard JDK serialization via `sqlDate.toString()`... this
+ // is problematic in multiple ways (including using arbitrary timezone...)
+ g.writeString(value.toString());
+ return;
+ }
+ _serializeAsString(value, g, provider);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java
index 759c46d..32594d8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java
@@ -1,13 +1,16 @@
package com.fasterxml.jackson.databind.ser.std;
+import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
@@ -19,8 +22,6 @@
extends StdSerializer<T>
implements ContextualSerializer
{
- protected final JsonSerializer<String> _serializer;
-
/**
* Setting for specific local override for "unwrap single element arrays":
* true for enable unwrapping, false for preventing it, `null` for using
@@ -29,29 +30,26 @@
* @since 2.6
*/
protected final Boolean _unwrapSingle;
-
+
protected StaticListSerializerBase(Class<?> cls) {
super(cls, false);
- _serializer = null;
_unwrapSingle = null;
}
/**
- * @since 2.6
+ * @since 2.9
*/
- @SuppressWarnings("unchecked")
protected StaticListSerializerBase(StaticListSerializerBase<?> src,
- JsonSerializer<?> ser, Boolean unwrapSingle) {
+ Boolean unwrapSingle) {
super(src);
- _serializer = (JsonSerializer<String>) ser;
_unwrapSingle = unwrapSingle;
}
/**
- * @since 2.6
+ * @since 2.9
*/
public abstract JsonSerializer<?> _withResolved(BeanProperty prop,
- JsonSerializer<?> ser, Boolean unwrapSingle);
+ Boolean unwrapSingle);
/*
/**********************************************************
@@ -59,6 +57,7 @@
/**********************************************************
*/
+ @SuppressWarnings("unchecked")
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializers,
BeanProperty property)
@@ -81,31 +80,22 @@
if (format != null) {
unwrapSingle = format.getFeature(JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);
}
- if (ser == null) {
- ser = _serializer;
- }
// [databind#124]: May have a content converter
- ser = findConvertingContentSerializer(serializers, property, ser);
+ ser = findContextualConvertingSerializer(serializers, property, ser);
if (ser == null) {
ser = serializers.findValueSerializer(String.class, property);
- } else {
- ser = serializers.handleSecondaryContextualization(ser, property);
}
// Optimization: default serializer just writes String, so we can avoid a call:
if (isDefaultSerializer(ser)) {
- ser = null;
+ if (unwrapSingle == _unwrapSingle) {
+ return this;
+ }
+ return _withResolved(property, unwrapSingle);
}
+ // otherwise...
// note: will never have TypeSerializer, because Strings are "natural" type
- if ((ser == _serializer) && (unwrapSingle == _unwrapSingle)) {
- return this;
- }
- return _withResolved(property, ser, unwrapSingle);
- }
-
- @Deprecated // since 2.5
- @Override
- public boolean isEmpty(T value) {
- return isEmpty(null, value);
+ return new CollectionSerializer(serializers.constructType(String.class),
+ true, /*TypeSerializer*/ null, (JsonSerializer<Object>) ser);
}
@Override
@@ -117,10 +107,13 @@
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode("array", true).set("items", contentSchema());
}
-
+
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
- acceptContentVisitor(visitor.expectArrayFormat(typeHint));
+ JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
+ if (v2 != null) {
+ acceptContentVisitor(v2);
+ }
}
/*
@@ -130,7 +123,12 @@
*/
protected abstract JsonNode contentSchema();
-
+
protected abstract void acceptContentVisitor(JsonArrayFormatVisitor visitor)
throws JsonMappingException;
+
+ // just to make sure it gets implemented:
+ @Override
+ public abstract void serializeWithType(T value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java
index 16c7e96..eb05fa4 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java
@@ -5,6 +5,7 @@
import java.util.HashMap;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
@@ -58,23 +59,24 @@
protected abstract static class TypedPrimitiveArraySerializer<T>
extends ArraySerializerBase<T>
{
- /**
- * Type serializer to use for values, if any.
- */
- protected final TypeSerializer _valueTypeSerializer;
-
protected TypedPrimitiveArraySerializer(Class<T> cls) {
super(cls);
- _valueTypeSerializer = null;
}
protected TypedPrimitiveArraySerializer(TypedPrimitiveArraySerializer<T> src,
- BeanProperty prop, TypeSerializer vts, Boolean unwrapSingle) {
+ BeanProperty prop, Boolean unwrapSingle) {
super(src, prop, unwrapSingle);
- _valueTypeSerializer = vts;
+ }
+
+ // 01-Dec-2016, tatu: Only now realized that due strong typing of Java arrays,
+ // we cannot really ever have value type serializers
+ @Override
+ public final ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
+ // throw exception or just do nothing?
+ return this;
}
}
-
+
/*
/****************************************************************
/* Concrete serializers, arrays
@@ -123,7 +125,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, boolean[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -135,22 +137,19 @@
public final void serialize(boolean[] value, JsonGenerator g, SerializerProvider provider) throws IOException
{
final int len = value.length;
- if (len == 1) {
- if (((_unwrapSingle == null) &&
- provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
- || (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, provider);
- return;
- }
+ if ((len == 1) && _shouldUnwrapSingle(provider)) {
+ serializeContents(value, g, provider);
+ return;
}
g.writeStartArray(len);
+ g.setCurrentValue(value);
serializeContents(value, g, provider);
g.writeEndArray();
}
-
+
@Override
public void serializeContents(boolean[] value, JsonGenerator g, SerializerProvider provider)
- throws IOException, JsonGenerationException
+ throws IOException
{
for (int i = 0, len = value.length; i < len; ++i) {
g.writeBoolean(value[i]);
@@ -182,18 +181,13 @@
public ShortArraySerializer() { super(short[].class); }
public ShortArraySerializer(ShortArraySerializer src, BeanProperty prop,
- TypeSerializer vts, Boolean unwrapSingle) {
- super(src, prop, vts, unwrapSingle);
+ Boolean unwrapSingle) {
+ super(src, prop, unwrapSingle);
}
@Override
public JsonSerializer<?> _withResolved(BeanProperty prop,Boolean unwrapSingle) {
- return new ShortArraySerializer(this, prop, _valueTypeSerializer, unwrapSingle);
- }
-
- @Override
- public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
- return new ShortArraySerializer(this, _property, vts, _unwrapSingle);
+ return new ShortArraySerializer(this, prop, unwrapSingle);
}
@Override
@@ -209,7 +203,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, short[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -220,16 +214,13 @@
@Override
public final void serialize(short[] value, JsonGenerator g, SerializerProvider provider) throws IOException
{
- final int len = value.length;
- if (len == 1) {
- if (((_unwrapSingle == null) &&
- provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
- || (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, provider);
- return;
- }
+ final int len = value.length;
+ if ((len == 1) && _shouldUnwrapSingle(provider)) {
+ serializeContents(value, g, provider);
+ return;
}
g.writeStartArray(len);
+ g.setCurrentValue(value);
serializeContents(value, g, provider);
g.writeEndArray();
}
@@ -237,16 +228,8 @@
@SuppressWarnings("cast")
@Override
public void serializeContents(short[] value, JsonGenerator g, SerializerProvider provider)
- throws IOException, JsonGenerationException
+ throws IOException
{
- if (_valueTypeSerializer != null) {
- for (int i = 0, len = value.length; i < len; ++i) {
- _valueTypeSerializer.writeTypePrefixForScalar(null, g, Short.TYPE);
- g.writeNumber(value[i]);
- _valueTypeSerializer.writeTypeSuffixForScalar(null, g);
- }
- return;
- }
for (int i = 0, len = value.length; i < len; ++i) {
g.writeNumber((int)value[i]);
}
@@ -273,7 +256,7 @@
* they are most likely to be textual data, and should be written as
* Strings, not arrays of entries.
*<p>
- * NOTE: since it is NOT serialized as an array, can not use AsArraySerializer as base
+ * NOTE: since it is NOT serialized as an array, cannot use AsArraySerializer as base
*/
@JacksonStdImpl
public static class CharArraySerializer extends StdSerializer<char[]>
@@ -282,16 +265,17 @@
@Override
public boolean isEmpty(SerializerProvider prov, char[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
public void serialize(char[] value, JsonGenerator g, SerializerProvider provider)
- throws IOException, JsonGenerationException
+ throws IOException
{
// [JACKSON-289] allows serializing as 'sparse' char array too:
if (provider.isEnabled(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS)) {
g.writeStartArray(value.length);
+ g.setCurrentValue(value);
_writeArrayContents(g, value);
g.writeEndArray();
} else {
@@ -302,22 +286,25 @@
@Override
public void serializeWithType(char[] value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
- throws IOException, JsonGenerationException
+ throws IOException
{
// [JACKSON-289] allows serializing as 'sparse' char array too:
- if (provider.isEnabled(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS)) {
- typeSer.writeTypePrefixForArray(value, g);
+ final boolean asArray = provider.isEnabled(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS);
+ WritableTypeId typeIdDef;
+ if (asArray) {
+ typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.START_ARRAY));
_writeArrayContents(g, value);
- typeSer.writeTypeSuffixForArray(value, g);
} else { // default is to write as simple String
- typeSer.writeTypePrefixForScalar(value, g);
+ typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.VALUE_STRING));
g.writeString(value, 0, value.length);
- typeSer.writeTypeSuffixForScalar(value, g);
}
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
private final void _writeArrayContents(JsonGenerator g, char[] value)
- throws IOException, JsonGenerationException
+ throws IOException
{
for (int i = 0, len = value.length; i < len; ++i) {
g.writeString(value, i, 1);
@@ -385,7 +372,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, int[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -397,13 +384,9 @@
public final void serialize(int[] value, JsonGenerator g, SerializerProvider provider) throws IOException
{
final int len = value.length;
- if (len == 1) {
- if (((_unwrapSingle == null) &&
- provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
- || (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, provider);
- return;
- }
+ if ((len == 1) && _shouldUnwrapSingle(provider)) {
+ serializeContents(value, g, provider);
+ return;
}
// 11-May-2016, tatu: As per [core#277] we have efficient `writeArray(...)` available
g.setCurrentValue(value);
@@ -440,18 +423,13 @@
public LongArraySerializer() { super(long[].class); }
public LongArraySerializer(LongArraySerializer src, BeanProperty prop,
- TypeSerializer vts, Boolean unwrapSingle) {
- super(src, prop, vts, unwrapSingle);
+ Boolean unwrapSingle) {
+ super(src, prop, unwrapSingle);
}
@Override
public JsonSerializer<?> _withResolved(BeanProperty prop,Boolean unwrapSingle) {
- return new LongArraySerializer(this, prop, _valueTypeSerializer, unwrapSingle);
- }
-
- @Override
- public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
- return new LongArraySerializer(this, _property, vts, _unwrapSingle);
+ return new LongArraySerializer(this, prop, unwrapSingle);
}
@Override
@@ -467,7 +445,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, long[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -479,13 +457,9 @@
public final void serialize(long[] value, JsonGenerator g, SerializerProvider provider) throws IOException
{
final int len = value.length;
- if (len == 1) {
- if (((_unwrapSingle == null) &&
- provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
- || (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, provider);
- return;
- }
+ if ((len == 1) && _shouldUnwrapSingle(provider)) {
+ serializeContents(value, g, provider);
+ return;
}
// 11-May-2016, tatu: As per [core#277] we have efficient `writeArray(...)` available
g.setCurrentValue(value);
@@ -496,15 +470,6 @@
public void serializeContents(long[] value, JsonGenerator g, SerializerProvider provider)
throws IOException
{
- if (_valueTypeSerializer != null) {
- for (int i = 0, len = value.length; i < len; ++i) {
- _valueTypeSerializer.writeTypePrefixForScalar(null, g, Long.TYPE);
- g.writeNumber(value[i]);
- _valueTypeSerializer.writeTypeSuffixForScalar(null, g);
- }
- return;
- }
-
for (int i = 0, len = value.length; i < len; ++i) {
g.writeNumber(value[i]);
}
@@ -536,18 +501,13 @@
super(float[].class);
}
public FloatArraySerializer(FloatArraySerializer src, BeanProperty prop,
- TypeSerializer vts, Boolean unwrapSingle) {
- super(src, prop, vts, unwrapSingle);
- }
-
- @Override
- public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) {
- return new FloatArraySerializer(this, _property, vts, _unwrapSingle);
+ Boolean unwrapSingle) {
+ super(src, prop, unwrapSingle);
}
@Override
public JsonSerializer<?> _withResolved(BeanProperty prop,Boolean unwrapSingle) {
- return new FloatArraySerializer(this, prop, _valueTypeSerializer, unwrapSingle);
+ return new FloatArraySerializer(this, prop, unwrapSingle);
}
@Override
@@ -563,7 +523,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, float[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -575,31 +535,20 @@
public final void serialize(float[] value, JsonGenerator g, SerializerProvider provider) throws IOException
{
final int len = value.length;
- if (len == 1) {
- if (((_unwrapSingle == null) &&
- provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
- || (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, provider);
- return;
- }
+ if ((len == 1) && _shouldUnwrapSingle(provider)) {
+ serializeContents(value, g, provider);
+ return;
}
g.writeStartArray(len);
+ g.setCurrentValue(value);
serializeContents(value, g, provider);
g.writeEndArray();
}
@Override
public void serializeContents(float[] value, JsonGenerator g, SerializerProvider provider)
- throws IOException, JsonGenerationException
+ throws IOException
{
- if (_valueTypeSerializer != null) {
- for (int i = 0, len = value.length; i < len; ++i) {
- _valueTypeSerializer.writeTypePrefixForScalar(null, g, Float.TYPE);
- g.writeNumber(value[i]);
- _valueTypeSerializer.writeTypeSuffixForScalar(null, g);
- }
- return;
- }
for (int i = 0, len = value.length; i < len; ++i) {
g.writeNumber(value[i]);
}
@@ -661,7 +610,7 @@
@Override
public boolean isEmpty(SerializerProvider prov, double[] value) {
- return (value == null) || (value.length == 0);
+ return value.length == 0;
}
@Override
@@ -673,16 +622,12 @@
public final void serialize(double[] value, JsonGenerator g, SerializerProvider provider) throws IOException
{
final int len = value.length;
- if (len == 1) {
- if (((_unwrapSingle == null) &&
- provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
- || (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, provider);
- return;
- }
+ if ((len == 1) && _shouldUnwrapSingle(provider)) {
+ serializeContents(value, g, provider);
+ return;
}
- // 11-May-2016, tatu: As per [core#277] we have efficient `writeArray(...)` available
g.setCurrentValue(value);
+ // 11-May-2016, tatu: As per [core#277] we have efficient `writeArray(...)` available
g.writeArray(value, 0, value.length);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java
index 067ebc6..f162101 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java
@@ -9,6 +9,7 @@
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
import java.io.IOException;
@@ -83,9 +84,7 @@
protected StdDelegatingSerializer withDelegate(Converter<Object,?> converter,
JavaType delegateType, JsonSerializer<?> delegateSerializer)
{
- if (getClass() != StdDelegatingSerializer.class) {
- throw new IllegalStateException("Sub-class "+getClass().getName()+" must override 'withDelegate'");
- }
+ ClassUtil.verifyMustOverride(StdDelegatingSerializer.class, this, "withDelegate");
return new StdDelegatingSerializer(converter, delegateType, delegateSerializer);
}
@@ -116,9 +115,8 @@
if (delegateType == null) {
delegateType = _converter.getOutputType(provider.getTypeFactory());
}
- /* 02-Apr-2015, tatu: For "dynamic case", where type is only specified as
- * java.lang.Object (or missing generic), [databind#731]
- */
+ // 02-Apr-2015, tatu: For "dynamic case", where type is only specified as
+ // java.lang.Object (or missing generic), [databind#731]
if (!delegateType.isJavaLangObject()) {
delSer = provider.findValueSerializer(delegateType);
}
@@ -186,15 +184,12 @@
}
@Override
- @Deprecated // since 2.5
- public boolean isEmpty(Object value) {
- return isEmpty(null, value);
- }
-
- @Override
public boolean isEmpty(SerializerProvider prov, Object value)
{
Object delegateValue = convertValue(value);
+ if (delegateValue == null) {
+ return true;
+ }
if (_delegateSerializer == null) { // best we can do for now, too costly to look up
return (value == null);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdJdkSerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdJdkSerializers.java
index a87ce64..b99bab3 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdJdkSerializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdJdkSerializers.java
@@ -27,15 +27,13 @@
HashMap<Class<?>,Object> sers = new HashMap<Class<?>,Object>();
// First things that 'toString()' can handle
- final ToStringSerializer sls = ToStringSerializer.instance;
+ sers.put(java.net.URL.class, new ToStringSerializer(java.net.URL.class));
+ sers.put(java.net.URI.class, new ToStringSerializer(java.net.URI.class));
- sers.put(java.net.URL.class, sls);
- sers.put(java.net.URI.class, sls);
-
- sers.put(Currency.class, sls);
+ sers.put(Currency.class, new ToStringSerializer(Currency.class));
sers.put(UUID.class, new UUIDSerializer());
- sers.put(java.util.regex.Pattern.class, sls);
- sers.put(Locale.class, sls);
+ sers.put(java.util.regex.Pattern.class, new ToStringSerializer(java.util.regex.Pattern.class));
+ sers.put(Locale.class, new ToStringSerializer(Locale.class));
// then atomic types (note: AtomicReference defined elsewhere)
sers.put(AtomicBoolean.class, AtomicBooleanSerializer.class);
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializer.java
index fceba52..c62e444 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializer.java
@@ -1,17 +1,13 @@
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
-import java.lang.reflect.Type;
-import java.util.Date;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
/**
- * Specialized serializer that can be used as the generic key
- * serializer, when serializing {@link java.util.Map}s to JSON
- * Objects.
+ * Specialized serializer that can be used as the generic key serializer,
+ * when serializing {@link java.util.Map}s to JSON Objects.
*
* @deprecated Since 2.8, use {@link StdKeySerializers.Default} instead.
*/
@@ -23,42 +19,7 @@
@Override
public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException {
- String str;
- Class<?> cls = value.getClass();
-
- if (cls == String.class) {
- str = (String) value;
- } else if (cls.isEnum()) {
- // 24-Sep-2015, tatu: Minor improvement over older (2.6.2 and before) code: at least
- // use name/toString() variation for as per configuration
- if (provider.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) {
- str = value.toString();
- } else {
- Enum<?> en = (Enum<?>) value;
- if (provider.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) {
- str = String.valueOf(en.ordinal());
- } else {
- str = en.name();
- }
- }
- } else if (value instanceof Date) {
- provider.defaultSerializeDateKey((Date) value, g);
- return;
- } else if (cls == Class.class) {
- str = ((Class<?>) value).getName();
- } else {
- str = value.toString();
- }
- g.writeFieldName(str);
- }
-
- @Override
- public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
- return createSchemaNode("string");
- }
-
- @Override
- public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
- visitStringFormat(visitor, typeHint);
+ // 19-Oct-2016, tatu: Simplified to bare essentials since this is deprecated
+ g.writeFieldName(value.toString());
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java
index 369e659..70fe67f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java
@@ -8,18 +8,17 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
+import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.EnumValues;
@SuppressWarnings("serial")
-public class StdKeySerializers
+public abstract class StdKeySerializers
{
@SuppressWarnings("deprecation")
protected final static JsonSerializer<Object> DEFAULT_KEY_SERIALIZER = new StdKeySerializer();
protected final static JsonSerializer<Object> DEFAULT_STRING_SERIALIZER = new StringKeySerializer();
- private StdKeySerializers() { }
-
/**
* @param config Serialization configuration in use, may be needed in choosing
* serializer to use
@@ -31,7 +30,7 @@
Class<?> rawKeyType, boolean useDefault)
{
// 24-Sep-2015, tatu: Important -- should ONLY consider types for which `@JsonValue`
- // can not be used, since caller has not yet checked for that annotation
+ // cannot be used, since caller has not yet checked for that annotation
// This is why Enum types are not handled here quite yet
// [databind#943: Use a dynamic key serializer if we are not given actual
@@ -42,6 +41,15 @@
if (rawKeyType == String.class) {
return DEFAULT_STRING_SERIALIZER;
}
+ if (rawKeyType.isPrimitive()) {
+ rawKeyType = ClassUtil.wrapperType(rawKeyType);
+ }
+ if (rawKeyType == Integer.class) {
+ return new Default(Default.TYPE_INTEGER, rawKeyType);
+ }
+ if (rawKeyType == Long.class) {
+ return new Default(Default.TYPE_LONG, rawKeyType);
+ }
if (rawKeyType.isPrimitive() || Number.class.isAssignableFrom(rawKeyType)) {
// 28-Jun-2016, tatu: Used to just return DEFAULT_KEY_SERIALIZER, but makes
// more sense to use simpler one directly
@@ -60,8 +68,12 @@
if (rawKeyType == java.util.UUID.class) {
return new Default(Default.TYPE_TO_STRING, rawKeyType);
}
+ if (rawKeyType == byte[].class) {
+ return new Default(Default.TYPE_BYTE_ARRAY, rawKeyType);
+ }
if (useDefault) {
- return DEFAULT_KEY_SERIALIZER;
+ // 19-Oct-2016, tatu: Used to just return DEFAULT_KEY_SERIALIZER but why not:
+ return new Default(Default.TYPE_TO_STRING, rawKeyType);
}
return null;
}
@@ -91,7 +103,8 @@
EnumValues.constructFromName(config, (Class<Enum<?>>) rawKeyType));
}
}
- return DEFAULT_KEY_SERIALIZER;
+ // 19-Oct-2016, tatu: Used to just return DEFAULT_KEY_SERIALIZER but why not:
+ return new Default(Default.TYPE_TO_STRING, rawKeyType);
}
/**
@@ -121,7 +134,10 @@
final static int TYPE_CALENDAR = 2;
final static int TYPE_CLASS = 3;
final static int TYPE_ENUM = 4;
- final static int TYPE_TO_STRING = 5;
+ final static int TYPE_INTEGER = 5; // since 2.9
+ final static int TYPE_LONG = 6; // since 2.9
+ final static int TYPE_BYTE_ARRAY = 7; // since 2.9
+ final static int TYPE_TO_STRING = 8;
protected final int _typeId;
@@ -159,6 +175,16 @@
g.writeFieldName(key);
}
break;
+ case TYPE_INTEGER:
+ case TYPE_LONG:
+ g.writeFieldId(((Number) value).longValue());
+ break;
+ case TYPE_BYTE_ARRAY:
+ {
+ String encoded = provider.getConfig().getBase64Variant().encode((byte[]) value);
+ g.writeFieldName(encoded);
+ }
+ break;
case TYPE_TO_STRING:
default:
g.writeFieldName(value.toString());
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 e64a3d6..916a9c9 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
@@ -4,6 +4,7 @@
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -39,9 +40,11 @@
public void serializeWithType(T value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer) throws IOException
{
- typeSer.writeTypePrefixForScalar(value, g);
+ // NOTE: need not really be string; just indicates "scalar of some kind"
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.VALUE_STRING));
serialize(value, g, provider);
- typeSer.writeTypeSuffixForScalar(value, g);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java
index a1108e8..52ed126 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java
@@ -3,6 +3,9 @@
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -30,21 +33,17 @@
extends JsonSerializer<T>
implements JsonFormatVisitable, SchemaAware, java.io.Serializable
{
- /**
- * Unique key we use to store a temporary lock, to prevent infinite recursion
- * when resolving content converters (see [databind#357]).
- *<p>
- * NOTE: may need to revisit this if nested content converters are needed; if so,
- * may need to create per-call lock object. But let's start with a simpler
- * solution for now.
- *
- * @since 2.7
- */
- private final static Object CONVERTING_CONTENT_CONVERTER_LOCK = new Object();
-
private static final long serialVersionUID = 1L;
/**
+ * Key used for storing a lock object to prevent infinite recursion when
+ * constructing converting serializers.
+ *
+ * @since 2.9
+ */
+ private final static Object KEY_CONTENT_CONVERTER_LOCK = new Object();
+
+ /**
* Nominal type supported, usually declared type of
* property for which serializer is used.
*/
@@ -138,7 +137,7 @@
{
ObjectNode schema = (ObjectNode) getSchema(provider, typeHint);
if (!isOptional) {
- schema.put("required", !isOptional);
+ schema.put("required", !isOptional);
}
return schema;
}
@@ -149,13 +148,9 @@
/**********************************************************
*/
- protected ObjectNode createObjectNode() {
- return JsonNodeFactory.instance.objectNode();
- }
-
protected ObjectNode createSchemaNode(String type)
{
- ObjectNode schema = createObjectNode();
+ ObjectNode schema = JsonNodeFactory.instance.objectNode();
schema.put("type", type);
return schema;
}
@@ -177,9 +172,7 @@
*/
protected void visitStringFormat(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException {
- if (visitor != null) {
- /*JsonStringFormatVisitor v2 =*/ visitor.expectStringFormat(typeHint);
- }
+ /*JsonStringFormatVisitor v2 =*/ visitor.expectStringFormat(typeHint);
}
/**
@@ -193,11 +186,9 @@
JsonValueFormat format)
throws JsonMappingException
{
- if (visitor != null) {
- JsonStringFormatVisitor v2 = visitor.expectStringFormat(typeHint);
- if (v2 != null) {
- v2.format(format);
- }
+ JsonStringFormatVisitor v2 = visitor.expectStringFormat(typeHint);
+ if (v2 != null) {
+ v2.format(format);
}
}
@@ -211,13 +202,9 @@
NumberType numberType)
throws JsonMappingException
{
- if (visitor != null) {
- JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint);
- if (v2 != null) {
- if (numberType != null) {
- v2.numberType(numberType);
- }
- }
+ JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint);
+ if (_neitherNull(v2, numberType)) {
+ v2.numberType(numberType);
}
}
@@ -232,15 +219,13 @@
NumberType numberType, JsonValueFormat format)
throws JsonMappingException
{
- if (visitor != null) {
- JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint);
- if (v2 != null) {
- if (numberType != null) {
- v2.numberType(numberType);
- }
- if (format != null) {
- v2.format(format);
- }
+ JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint);
+ if (v2 != null) {
+ if (numberType != null) {
+ v2.numberType(numberType);
+ }
+ if (format != null) {
+ v2.format(format);
}
}
}
@@ -255,11 +240,9 @@
NumberType numberType)
throws JsonMappingException
{
- if (visitor != null) {
- JsonNumberFormatVisitor v2 = visitor.expectNumberFormat(typeHint);
- if (v2 != null) {
- v2.numberType(numberType);
- }
+ JsonNumberFormatVisitor v2 = visitor.expectNumberFormat(typeHint);
+ if (v2 != null) {
+ v2.numberType(numberType);
}
}
@@ -270,13 +253,9 @@
JsonSerializer<?> itemSerializer, JavaType itemType)
throws JsonMappingException
{
- if (visitor != null) {
- JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
- if (v2 != null) {
- if (itemSerializer != null) {
- v2.itemsFormat(itemSerializer, itemType);
- }
- }
+ JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
+ if (_neitherNull(v2, itemSerializer)) {
+ v2.itemsFormat(itemSerializer, itemType);
}
}
@@ -287,14 +266,12 @@
JsonFormatTypes itemType)
throws JsonMappingException
{
- if (visitor != null) {
- JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
- if (v2 != null) {
- v2.itemsFormat(itemType);
- }
+ JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
+ if (v2 != null) {
+ v2.itemsFormat(itemType);
}
}
-
+
/*
/**********************************************************
/* Helper methods for exception handling
@@ -324,20 +301,16 @@
while (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
- // Errors and "plain" IOExceptions to be passed as is
- if (t instanceof Error) {
- throw (Error) t;
- }
+ // Errors and "plain" to be passed as is
+ ClassUtil.throwIfError(t);
// Ditto for IOExceptions... except for mapping exceptions!
boolean wrap = (provider == null) || provider.isEnabled(SerializationFeature.WRAP_EXCEPTIONS);
if (t instanceof IOException) {
if (!wrap || !(t instanceof JsonMappingException)) {
throw (IOException) t;
}
- } else if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for unchecked exceptions
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
+ } else if (!wrap) {
+ ClassUtil.throwIfRTE(t);
}
// Need to add reference information
throw JsonMappingException.wrapWithPath(t, bean, fieldName);
@@ -351,19 +324,15 @@
t = t.getCause();
}
// Errors are to be passed as is
- if (t instanceof Error) {
- throw (Error) t;
- }
+ ClassUtil.throwIfError(t);
// Ditto for IOExceptions... except for mapping exceptions!
boolean wrap = (provider == null) || provider.isEnabled(SerializationFeature.WRAP_EXCEPTIONS);
if (t instanceof IOException) {
if (!wrap || !(t instanceof JsonMappingException)) {
throw (IOException) t;
}
- } else if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for unchecked exceptions
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
+ } else if (!wrap) {
+ ClassUtil.throwIfRTE(t);
}
// Need to add reference information
throw JsonMappingException.wrapWithPath(t, bean, index);
@@ -383,34 +352,50 @@
* @param existingSerializer (optional) configured content
* serializer if one already exists.
*
- * @since 2.2
+ * @since 2.9
*/
+ protected JsonSerializer<?> findContextualConvertingSerializer(SerializerProvider provider,
+ BeanProperty property, JsonSerializer<?> existingSerializer)
+ throws JsonMappingException
+ {
+ // 08-Dec-2016, tatu: to fix [databind#357], need to prevent recursive calls for
+ // same property
+ @SuppressWarnings("unchecked")
+ Map<Object,Object> conversions = (Map<Object,Object>) provider.getAttribute(KEY_CONTENT_CONVERTER_LOCK);
+ if (conversions != null) {
+ Object lock = conversions.get(property);
+ if (lock != null) {
+ return existingSerializer;
+ }
+ } else {
+ conversions = new IdentityHashMap<>();
+ provider.setAttribute(KEY_CONTENT_CONVERTER_LOCK, conversions);
+ }
+ conversions.put(property, Boolean.TRUE);
+ try {
+ JsonSerializer<?> ser = findConvertingContentSerializer(provider, property, existingSerializer);
+ if (ser != null) {
+ return provider.handleSecondaryContextualization(ser, property);
+ }
+ } finally {
+ conversions.remove(property);
+ }
+ return existingSerializer;
+ }
+
+ /**
+ * @deprecated Since 2.9 use {link {@link #findContextualConvertingSerializer} instead
+ */
+ @Deprecated
protected JsonSerializer<?> findConvertingContentSerializer(SerializerProvider provider,
BeanProperty prop, JsonSerializer<?> existingSerializer)
throws JsonMappingException
{
- /* 19-Oct-2014, tatu: As per [databind#357], need to avoid infinite loop
- * when applying contextual content converter; this is not ideal way,
- * but should work for most cases.
- */
- Object ob = provider.getAttribute(CONVERTING_CONTENT_CONVERTER_LOCK);
- if (ob != null) {
- if (ob == Boolean.TRUE) { // just to ensure it's value we added.
- return existingSerializer;
- }
- }
-
final AnnotationIntrospector intr = provider.getAnnotationIntrospector();
- if (intr != null && prop != null) {
+ if (_neitherNull(intr, prop)) {
AnnotatedMember m = prop.getMember();
if (m != null) {
- provider.setAttribute(CONVERTING_CONTENT_CONVERTER_LOCK, Boolean.TRUE);
- Object convDef;
- try {
- convDef = intr.findSerializationContentConverter(m);
- } finally {
- provider.setAttribute(CONVERTING_CONTENT_CONVERTER_LOCK, null);
- }
+ Object convDef = intr.findSerializationContentConverter(m);
if (convDef != null) {
Converter<Object,Object> conv = provider.converterInstance(prop.getMember(), convDef);
JavaType delegateType = conv.getOutputType(provider.getTypeFactory());
@@ -438,8 +423,8 @@
FilterProvider filters = provider.getFilterProvider();
// Not ok to miss the provider, if a filter is declared to be needed.
if (filters == null) {
- throw JsonMappingException.from(provider,
- "Can not resolve PropertyFilter with id '"+filterId+"'; no FilterProvider configured");
+ provider.reportBadDefinition(handledType(),
+ "Cannot resolve PropertyFilter with id '"+filterId+"'; no FilterProvider configured");
}
// But whether unknown ids are ok just depends on filter provider; if we get null that's fine
return filters.findPropertyFilter(filterId, valueToFilter);
@@ -534,4 +519,18 @@
protected boolean isDefaultSerializer(JsonSerializer<?> serializer) {
return ClassUtil.isJacksonStdImpl(serializer);
}
+
+ /**
+ * @since 2.9
+ */
+ protected final static boolean _neitherNull(Object a, Object b) {
+ return (a != null) && (b != null);
+ }
+
+ /**
+ * @since 2.9
+ */
+ protected final static boolean _nonEmpty(Collection<?> c) {
+ return (c != null) && !c.isEmpty();
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java
index 3ec907c..d3c621c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
/**
* This is the special serializer for regular {@link java.lang.String}s.
@@ -22,26 +23,17 @@
public final class StringSerializer
// NOTE: generic parameter changed from String to Object in 2.6, to avoid
// use of bridge methods
- extends NonTypedScalarSerializerBase<Object>
+// In 2.9, removed use of intermediate type `NonTypedScalarSerializerBase`
+ extends StdScalarSerializer<Object>
{
private static final long serialVersionUID = 1L;
public StringSerializer() { super(String.class, false); }
- /**
- * For Strings, both null and Empty String qualify for emptiness.
- */
- @Override
- @Deprecated
- public boolean isEmpty(Object value) {
- String str = (String) value;
- return (str == null) || (str.length() == 0);
- }
-
@Override
public boolean isEmpty(SerializerProvider prov, Object value) {
String str = (String) value;
- return (str == null) || (str.length() == 0);
+ return str.length() == 0;
}
@Override
@@ -50,6 +42,14 @@
}
@Override
+ public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider,
+ TypeSerializer typeSer) throws IOException
+ {
+ // no type info, just regular serialization
+ gen.writeString((String) value);
+ }
+
+ @Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode("string", true);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/TimeZoneSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/TimeZoneSerializer.java
index 3ad88de..6bfb214 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/TimeZoneSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/TimeZoneSerializer.java
@@ -4,6 +4,7 @@
import java.util.TimeZone;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -13,15 +14,18 @@
public TimeZoneSerializer() { super(TimeZone.class); }
@Override
- public void serialize(TimeZone value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
- jgen.writeString(value.getID());
+ public void serialize(TimeZone value, JsonGenerator g, SerializerProvider provider) throws IOException {
+ g.writeString(value.getID());
}
@Override
- public void serializeWithType(TimeZone value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
+ public void serializeWithType(TimeZone value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException
+ {
// Better ensure we don't use specific sub-classes:
- typeSer.writeTypePrefixForScalar(value, jgen, TimeZone.class);
- serialize(value, jgen, provider);
- typeSer.writeTypeSuffixForScalar(value, jgen);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, TimeZone.class, JsonToken.VALUE_STRING));
+ serialize(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ToStringSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ToStringSerializer.java
index 5d3aa91..eb73422 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ToStringSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ToStringSerializer.java
@@ -4,6 +4,7 @@
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -48,13 +49,9 @@
@Override
public boolean isEmpty(SerializerProvider prov, Object value) {
- if (value == null) {
- return true;
- }
- String str = value.toString();
- return str.isEmpty();
+ return value.toString().isEmpty();
}
-
+
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
throws IOException
@@ -74,20 +71,21 @@
* change this behavior.
*/
@Override
- public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider,
+ public void serializeWithType(Object value, JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException
{
- typeSer.writeTypePrefixForScalar(value, gen);
- serialize(value, gen, provider);
- typeSer.writeTypeSuffixForScalar(value, gen);
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.VALUE_STRING));
+ serialize(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
-
+
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return createSchemaNode("string", true);
}
-
+
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
{
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/TokenBufferSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/TokenBufferSerializer.java
index d7539b2..a7624b0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/TokenBufferSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/TokenBufferSerializer.java
@@ -4,6 +4,7 @@
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -15,7 +16,7 @@
/**
* We also want to directly support serialization of {@link TokenBuffer};
- * and since it is part of core package, it can not implement
+ * and since it is part of core package, it cannot implement
* {@link com.fasterxml.jackson.databind.JsonSerializable}
* (which is only included in the mapper package)
*/
@@ -28,7 +29,7 @@
@Override
public void serialize(TokenBuffer value, JsonGenerator jgen, SerializerProvider provider)
- throws IOException
+ throws IOException
{
value.serialize(jgen);
}
@@ -44,14 +45,17 @@
* than doing introspection on both serialization and deserialization.
*/
@Override
- public final void serializeWithType(TokenBuffer value, JsonGenerator jgen, SerializerProvider provider,
- TypeSerializer typeSer) throws IOException
+ public final void serializeWithType(TokenBuffer value, JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException
{
- typeSer.writeTypePrefixForScalar(value, jgen);
- serialize(value, jgen, provider);
- typeSer.writeTypeSuffixForScalar(value, jgen);
+ // 28-Jun-2017, tatu: As per javadoc, not sure what to report as likely shape. Could
+ // even look into first actual token inside... but, for now let's keep it simple
+ WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
+ typeSer.typeId(value, JsonToken.VALUE_EMBEDDED_OBJECT));
+ serialize(value, g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
-
+
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java
index cd9e5a1..21768e7 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java
@@ -25,9 +25,6 @@
@Override
public boolean isEmpty(SerializerProvider prov, UUID value)
{
- if (value == null) {
- return true;
- }
// Null UUID is empty, so...
if (value.getLeastSignificantBits() == 0L
&& value.getMostSignificantBits() == 0L) {
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 24ac759..ef3e6aa 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/ArrayType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/ArrayType.java
@@ -125,7 +125,7 @@
}
private JavaType _reportUnsupported() {
- throw new UnsupportedOperationException("Can not narrow or widen array types");
+ throw new UnsupportedOperationException("Cannot narrow or widen array types");
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/CollectionLikeType.java b/src/main/java/com/fasterxml/jackson/databind/type/CollectionLikeType.java
index f355160..2bd9bbf 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/CollectionLikeType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/CollectionLikeType.java
@@ -86,7 +86,7 @@
if (baseType instanceof TypeBase) {
return new CollectionLikeType((TypeBase) baseType, elementType);
}
- throw new IllegalArgumentException("Can not upgrade from an instance of "+baseType.getClass());
+ throw new IllegalArgumentException("Cannot upgrade from an instance of "+baseType.getClass());
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/MapLikeType.java b/src/main/java/com/fasterxml/jackson/databind/type/MapLikeType.java
index f1d1ee0..d62356b 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/MapLikeType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/MapLikeType.java
@@ -66,7 +66,7 @@
return new MapLikeType((TypeBase) baseType, keyT, valueT);
}
throw new IllegalArgumentException(
- "Can not upgrade from an instance of " + baseType.getClass());
+ "Cannot upgrade from an instance of " + baseType.getClass());
}
@Deprecated
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/PlaceholderForType.java b/src/main/java/com/fasterxml/jackson/databind/type/PlaceholderForType.java
index 54ed402..7193d98 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/PlaceholderForType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/PlaceholderForType.java
@@ -83,6 +83,7 @@
return _unsupported();
}
+ @SuppressWarnings("deprecation")
@Override
protected JavaType _narrow(Class<?> subclass) {
return _unsupported();
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/ReferenceType.java b/src/main/java/com/fasterxml/jackson/databind/type/ReferenceType.java
index e8c6962..f9ac7a3 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/ReferenceType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/ReferenceType.java
@@ -70,7 +70,7 @@
if (baseType instanceof TypeBase) {
return new ReferenceType((TypeBase) baseType, refdType);
}
- throw new IllegalArgumentException("Can not upgrade from an instance of "+baseType.getClass());
+ throw new IllegalArgumentException("Cannot upgrade from an instance of "+baseType.getClass());
}
/**
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/ResolvedRecursiveType.java b/src/main/java/com/fasterxml/jackson/databind/type/ResolvedRecursiveType.java
index 53e061c..4f4984e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/ResolvedRecursiveType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/ResolvedRecursiveType.java
@@ -36,14 +36,29 @@
public JavaType getSelfReferencedType() { return _referencedType; }
+ // 23-Jul-2019, tatu: [databind#2331] Need to also delegate this...
+ @Override
+ public TypeBindings getBindings() {
+ if (_referencedType != null) { // `null` before resolution [databind#2395]
+ return _referencedType.getBindings();
+ }
+ return super.getBindings();
+ }
+
@Override
public StringBuilder getGenericSignature(StringBuilder sb) {
- return _referencedType.getGenericSignature(sb);
+ if (_referencedType != null) {
+ return _referencedType.getGenericSignature(sb);
+ }
+ return sb.append("?");
}
@Override
public StringBuilder getErasedSignature(StringBuilder sb) {
- return _referencedType.getErasedSignature(sb);
+ if (_referencedType != null) {
+ return _referencedType.getErasedSignature(sb);
+ }
+ return sb;
}
@Override
@@ -112,7 +127,7 @@
if (o == this) return true;
if (o == null) return false;
if (o.getClass() == getClass()) {
- // 16-Jun-2017, tatu: as per [databind#1658], can not do recursive call since
+ // 16-Jun-2017, tatu: as per [databind#1658], cannot do recursive call since
// there is likely to be a cycle...
// but... true or false?
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/SimpleType.java b/src/main/java/com/fasterxml/jackson/databind/type/SimpleType.java
index 1fb2433..9e97f75 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/SimpleType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/SimpleType.java
@@ -104,14 +104,14 @@
* Map/Collection entries are constructed
*/
if (Map.class.isAssignableFrom(cls)) {
- throw new IllegalArgumentException("Can not construct SimpleType for a Map (class: "+cls.getName()+")");
+ throw new IllegalArgumentException("Cannot construct SimpleType for a Map (class: "+cls.getName()+")");
}
if (Collection.class.isAssignableFrom(cls)) {
- throw new IllegalArgumentException("Can not construct SimpleType for a Collection (class: "+cls.getName()+")");
+ throw new IllegalArgumentException("Cannot construct SimpleType for a Collection (class: "+cls.getName()+")");
}
// ... and while we are at it, not array types either
if (cls.isArray()) {
- throw new IllegalArgumentException("Can not construct SimpleType for an array (class: "+cls.getName()+")");
+ throw new IllegalArgumentException("Cannot construct SimpleType for an array (class: "+cls.getName()+")");
}
TypeBindings b = TypeBindings.emptyBindings();
return new SimpleType(cls, b,
@@ -127,7 +127,7 @@
}
// Should we check that there is a sub-class relationship?
// 15-Jan-2016, tatu: Almost yes, but there are some complications with
- // placeholder values (`Void`, `NoClass`), so can not quite do yet.
+ // placeholder values (`Void`, `NoClass`), so cannot quite do yet.
// TODO: fix in 2.9
if (!_class.isAssignableFrom(subclass)) {
/*
@@ -162,13 +162,13 @@
}
}
// should not get here but...
- throw new IllegalArgumentException("Internal error: Can not resolve sub-type for Class "+subclass.getName()+" to "
+ throw new IllegalArgumentException("Internal error: Cannot resolve sub-type for Class "+subclass.getName()+" to "
+_class.getName());
}
@Override
public JavaType withContentType(JavaType contentType) {
- throw new IllegalArgumentException("Simple types have no content types; can not call withContentType()");
+ throw new IllegalArgumentException("Simple types have no content types; cannot call withContentType()");
}
@Override
@@ -182,7 +182,7 @@
@Override
public JavaType withContentTypeHandler(Object h) {
// no content type, so:
- throw new IllegalArgumentException("Simple types have no content types; can not call withContenTypeHandler()");
+ throw new IllegalArgumentException("Simple types have no content types; cannot call withContenTypeHandler()");
}
@Override
@@ -196,7 +196,7 @@
@Override
public SimpleType withContentValueHandler(Object h) {
// no content type, so:
- throw new IllegalArgumentException("Simple types have no content types; can not call withContenValueHandler()");
+ throw new IllegalArgumentException("Simple types have no content types; cannot call withContenValueHandler()");
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeBase.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeBase.java
index 056c727..a98d6ed 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/TypeBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeBase.java
@@ -5,6 +5,8 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -162,13 +164,14 @@
*/
@Override
- public void serializeWithType(JsonGenerator gen, SerializerProvider provider,
+ public void serializeWithType(JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
- throws IOException, JsonProcessingException
+ throws IOException
{
- typeSer.writeTypePrefixForScalar(this, gen);
- this.serialize(gen, provider);
- typeSer.writeTypeSuffixForScalar(this, gen);
+ WritableTypeId typeIdDef = new WritableTypeId(this, JsonToken.VALUE_STRING);
+ typeSer.writeTypePrefix(g, typeIdDef);
+ this.serialize(g, provider);
+ typeSer.writeTypeSuffix(g, typeIdDef);
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeBindings.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeBindings.java
index 6752f20..872608a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/TypeBindings.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeBindings.java
@@ -4,6 +4,7 @@
import java.util.*;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Helper class used for resolving type parameters for given class
@@ -109,7 +110,7 @@
}
// Check here to give better error message
if (names.length != types.length) {
- throw new IllegalArgumentException("Can not create TypeBindings for class "+erasedType.getName()
+ throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with "+types.length+" type parameter"
+((types.length == 1) ? "" : "s")+": class expects "+names.length);
}
@@ -122,7 +123,7 @@
TypeVariable<?>[] vars = TypeParamStash.paramsFor1(erasedType);
int varLen = (vars == null) ? 0 : vars.length;
if (varLen != 1) {
- throw new IllegalArgumentException("Can not create TypeBindings for class "+erasedType.getName()
+ throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with 1 type parameter: class expects "+varLen);
}
return new TypeBindings(new String[] { vars[0].getName() },
@@ -135,7 +136,7 @@
TypeVariable<?>[] vars = TypeParamStash.paramsFor2(erasedType);
int varLen = (vars == null) ? 0 : vars.length;
if (varLen != 2) {
- throw new IllegalArgumentException("Can not create TypeBindings for class "+erasedType.getName()
+ throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with 2 type parameters: class expects "+varLen);
}
return new TypeBindings(new String[] { vars[0].getName(), vars[1].getName() },
@@ -155,7 +156,7 @@
return EMPTY;
}
if (varLen != 1) {
- throw new IllegalArgumentException("Can not create TypeBindings for class "+erasedType.getName()
+ throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with 1 type parameter: class expects "+varLen);
}
return new TypeBindings(new String[] { vars[0].getName() },
@@ -183,7 +184,7 @@
}
// Check here to give better error message
if (names.length != types.length) {
- throw new IllegalArgumentException("Can not create TypeBindings for class "+erasedType.getName()
+ throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName()
+" with "+types.length+" type parameter"
+((types.length == 1) ? "" : "s")+": class expects "+names.length);
}
@@ -335,7 +336,9 @@
@Override public boolean equals(Object o)
{
if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
+ if (!ClassUtil.hasClass(o, getClass())) {
+ return false;
+ }
TypeBindings other = (TypeBindings) o;
int len = _types.length;
if (len != other.size()) {
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 e005406..c077354 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
@@ -182,7 +182,11 @@
typeCache = null;
} else if (_modifiers == null) {
mods = new TypeModifier[] { mod };
+ // 29-Jul-2019, tatu: Actually I think we better clear cache in this case
+ // as well to ensure no leakage occurs (see [databind#2395])
+ typeCache = null;
} else {
+ // but may keep existing cache otherwise
mods = ArrayBuilders.insertInListNoDup(_modifiers, mod);
}
return new TypeFactory(typeCache, _parser, mods, _classLoader);
@@ -297,15 +301,13 @@
prob = ClassUtil.getRootCause(e);
}
}
- if (prob instanceof RuntimeException) {
- throw (RuntimeException) prob;
- }
+ ClassUtil.throwIfRTE(prob);
throw new ClassNotFoundException(prob.getMessage(), prob);
}
protected Class<?> classForName(String name, boolean initialize,
- ClassLoader loader) throws ClassNotFoundException {
- return Class.forName(name, true, loader);
+ ClassLoader loader) throws ClassNotFoundException {
+ return Class.forName(name, true, loader);
}
protected Class<?> classForName(String name) throws ClassNotFoundException {
@@ -363,7 +365,7 @@
// (1) Original target type has no generics -- just resolve subtype
if (baseType.getBindings().isEmpty()) {
- newType = _fromClass(null, subclass, EMPTY_BINDINGS);
+ newType = _fromClass(null, subclass, EMPTY_BINDINGS);
break;
}
// (2) A small set of "well-known" List/Map subtypes where can take a short-cut
@@ -396,7 +398,7 @@
// (3) Sub-class does not take type parameters -- just resolve subtype
int typeParamCount = subclass.getTypeParameters().length;
if (typeParamCount == 0) {
- newType = _fromClass(null, subclass, TypeBindings.emptyBindings());
+ newType = _fromClass(null, subclass, EMPTY_BINDINGS);
break;
}
// (4) If all else fails, do the full traversal using placeholders
@@ -456,7 +458,30 @@
for (int i = 0, len = expectedTypes.size(); i < len; ++i) {
JavaType exp = expectedTypes.get(i);
JavaType act = actualTypes.get(i);
+
if (!_verifyAndResolvePlaceholders(exp, act)) {
+ // 14-May-2018, tatu: As per [databind#2034] it seems we better relax assignment
+ // rules further -- at least likely "raw" (untyped, non-generic) base should probably
+ // allow specialization.
+ if (exp.hasRawClass(Object.class)) {
+ continue;
+ }
+ // 19-Apr-2018, tatu: Hack for [databind#1964] -- allow type demotion
+ // for `java.util.Map` key type if (and only if) target type is
+ // `java.lang.Object`
+ if (i == 0) {
+ if (sourceType.hasRawClass(Map.class)
+ && act.hasRawClass(Object.class)) {
+ continue;
+ }
+ }
+ // 19-Nov-2018, tatu: To solve [databind#2155], let's allow type-compatible
+ // assignment for interfaces at least...
+ if (exp.isInterface()) {
+ if (exp.isTypeOrSuperTypeOf(act.getRawClass())) {
+ continue;
+ }
+ }
return String.format("Type parameter #%d/%d differs; can not specialize %s with %s",
(i+1), len, exp.toCanonical(), act.toCanonical());
}
@@ -654,7 +679,7 @@
public JavaType constructType(Type type, JavaType contextType) {
TypeBindings bindings;
if (contextType == null) {
- bindings = TypeBindings.emptyBindings();
+ bindings = EMPTY_BINDINGS;
} else {
bindings = contextType.getBindings();
// 16-Nov-2016, tatu: Unfortunately as per [databind#1456] this can't
@@ -721,11 +746,22 @@
* for contained types.
*/
public CollectionType constructCollectionType(Class<? extends Collection> collectionClass,
- JavaType elementType) {
- // 19-Oct-2015, tatu: Allow case of no-type-variables, since it seems likely to be
- // a valid use case here
- return (CollectionType) _fromClass(null, collectionClass,
- TypeBindings.create(collectionClass, elementType));
+ JavaType elementType)
+ {
+ TypeBindings bindings = TypeBindings.createIfNeeded(collectionClass, elementType);
+ CollectionType result = (CollectionType) _fromClass(null, collectionClass, bindings);
+ // 17-May-2017, tatu: As per [databind#1415], we better verify bound values if (but only if)
+ // type being resolved was non-generic (i.e.element type was ignored)
+ if (bindings.isEmpty() && (elementType != null)) {
+ JavaType t = result.findSuperType(Collection.class);
+ JavaType realET = t.getContentType();
+ if (!realET.equals(elementType)) {
+ throw new IllegalArgumentException(String.format(
+ "Non-generic Collection class %s did not resolve to something with element type %s but %s ",
+ ClassUtil.nameOf(collectionClass), elementType, realET));
+ }
+ }
+ return result;
}
/**
@@ -779,8 +815,26 @@
* for contained types.
*/
public MapType constructMapType(Class<? extends Map> mapClass, JavaType keyType, JavaType valueType) {
- return (MapType) _fromClass(null, mapClass,
- TypeBindings.create(mapClass, keyType, valueType));
+ TypeBindings bindings = TypeBindings.createIfNeeded(mapClass, new JavaType[] { keyType, valueType });
+ MapType result = (MapType) _fromClass(null, mapClass, bindings);
+ // 17-May-2017, tatu: As per [databind#1415], we better verify bound values if (but only if)
+ // type being resolved was non-generic (i.e.element type was ignored)
+ if (bindings.isEmpty()) {
+ JavaType t = result.findSuperType(Map.class);
+ JavaType realKT = t.getKeyType();
+ if (!realKT.equals(keyType)) {
+ throw new IllegalArgumentException(String.format(
+ "Non-generic Map class %s did not resolve to something with key type %s but %s ",
+ ClassUtil.nameOf(mapClass), keyType, realKT));
+ }
+ JavaType realVT = t.getContentType();
+ if (!realVT.equals(valueType)) {
+ throw new IllegalArgumentException(String.format(
+ "Non-generic Map class %s did not resolve to something with value type %s but %s ",
+ ClassUtil.nameOf(mapClass), valueType, realVT));
+ }
+ }
+ return result;
}
/**
@@ -868,15 +922,15 @@
* type {@code List<Set<Integer>>}, you could
* call
*<pre>
- * JavaType inner = TypeFactory.constructParametrizedType(Set.class, Set.class, Integer.class);
- * return TypeFactory.constructParametrizedType(ArrayList.class, List.class, inner);
+ * JavaType inner = TypeFactory.constructParametricType(Set.class, Set.class, Integer.class);
+ * return TypeFactory.constructParametricType(ArrayList.class, List.class, inner);
*</pre>
*<p>
* The reason for first two arguments to be separate is that parameterization may
* apply to a super-type. For example, if generic type was instead to be
* constructed for {@code ArrayList<Integer>}, the usual call would be:
*<pre>
- * TypeFactory.constructParametrizedType(ArrayList.class, List.class, Integer.class);
+ * TypeFactory.constructParametricType(ArrayList.class, List.class, Integer.class);
*</pre>
* since parameterization is applied to {@link java.util.List}.
* In most cases distinction does not matter, but there are types where it does;
@@ -904,15 +958,15 @@
* type {@code List<Set<Integer>>}, you could
* call
*<pre>
- * JavaType inner = TypeFactory.constructParametrizedType(Set.class, Set.class, Integer.class);
- * return TypeFactory.constructParametrizedType(ArrayList.class, List.class, inner);
+ * JavaType inner = TypeFactory.constructParametricType(Set.class, Set.class, Integer.class);
+ * return TypeFactory.constructParametricType(ArrayList.class, List.class, inner);
*</pre>
*<p>
* The reason for first two arguments to be separate is that parameterization may
* apply to a super-type. For example, if generic type was instead to be
* constructed for {@code ArrayList<Integer>}, the usual call would be:
*<pre>
- * TypeFactory.constructParametrizedType(ArrayList.class, List.class, Integer.class);
+ * TypeFactory.constructParametricType(ArrayList.class, List.class, Integer.class);
*</pre>
* since parameterization is applied to {@link java.util.List}.
* In most cases distinction does not matter, but there are types where it does;
@@ -932,7 +986,10 @@
/**
* @since 2.5 -- but will probably deprecated in 2.7 or 2.8 (not needed with 2.7)
+ *
+ * @deprecated since 2.9 Use {@link #constructParametricType(Class,JavaType...)} instead
*/
+ @Deprecated
public JavaType constructParametrizedType(Class<?> parametrized, Class<?> parametersFor,
JavaType... parameterTypes)
{
@@ -941,7 +998,10 @@
/**
* @since 2.5 -- but will probably deprecated in 2.7 or 2.8 (not needed with 2.7)
+ *
+ * @deprecated since 2.9 Use {@link #constructParametricType(Class,Class...)} instead
*/
+ @Deprecated
public JavaType constructParametrizedType(Class<?> parametrized, Class<?> parametersFor,
Class<?>... parameterClasses)
{
@@ -1041,7 +1101,7 @@
vt = typeParams.get(1);
break;
default:
- throw new IllegalArgumentException("Strange Map type "+rawClass.getName()+": can not determine type parameters");
+ throw new IllegalArgumentException("Strange Map type "+rawClass.getName()+": cannot determine type parameters");
}
}
return MapType.construct(rawClass, bindings, superClass, superInterfaces, kt, vt);
@@ -1058,7 +1118,7 @@
} else if (typeParams.size() == 1) {
ct = typeParams.get(0);
} else {
- throw new IllegalArgumentException("Strange Collection type "+rawClass.getName()+": can not determine type parameters");
+ throw new IllegalArgumentException("Strange Collection type "+rawClass.getName()+": cannot determine type parameters");
}
return CollectionType.construct(rawClass, bindings, superClass, superInterfaces, ct);
}
@@ -1074,7 +1134,7 @@
} else if (typeParams.size() == 1) {
ct = typeParams.get(0);
} else {
- throw new IllegalArgumentException("Strange Reference type "+rawClass.getName()+": can not determine type parameters");
+ throw new IllegalArgumentException("Strange Reference type "+rawClass.getName()+": cannot determine type parameters");
}
return ReferenceType.construct(rawClass, bindings, superClass, superInterfaces, ct);
}
@@ -1180,9 +1240,8 @@
// sanity check
throw new IllegalArgumentException("Unrecognized Type: "+((type == null) ? "[null]" : type.toString()));
}
- /* 21-Feb-2016, nateB/tatu: as per [databind#1129] (applied for 2.7.2),
- * we do need to let all kinds of types to be refined, esp. for Scala module.
- */
+ // 21-Feb-2016, nateB/tatu: as per [databind#1129] (applied for 2.7.2),
+ // we do need to let all kinds of types to be refined, esp. for Scala module.
if (_modifiers != null) {
TypeBindings b = resultType.getBindings();
if (b == null) {
@@ -1253,7 +1312,7 @@
superClass = null;
superInterfaces = _resolveSuperInterfaces(context, rawType, bindings);
} else {
- // Note: even Enums can implement interfaces, so can not drop those
+ // Note: even Enums can implement interfaces, so cannot drop those
superClass = _resolveSuperClass(context, rawType, bindings);
superInterfaces = _resolveSuperInterfaces(context, rawType, bindings);
}
@@ -1322,7 +1381,7 @@
JavaType superClass, JavaType[] superInterfaces)
{
if (bindings == null) {
- bindings = TypeBindings.emptyBindings();
+ bindings = EMPTY_BINDINGS;
}
// Quite simple when we resolving exact class/interface; start with that
@@ -1410,6 +1469,9 @@
{
// ideally should find it via bindings:
final String name = var.getName();
+ if (bindings == null) {
+ throw new IllegalArgumentException("Null `bindings` passed (type variable \""+name+"\")");
+ }
JavaType type = bindings.findBoundType(name);
if (type != null) {
return type;
@@ -1421,7 +1483,18 @@
}
bindings = bindings.withUnboundVariable(name);
- Type[] bounds = var.getBounds();
+ final Type[] bounds;
+
+ // 15-Jan-2019, tatu: As weird as this looks, apparently on some platforms (Arm CPU, mobile
+ // devices), unsynchronized internal access can lead to issues, see:
+ //
+ // https://vmlens.com/articles/java-lang-reflect-typevariable-getbounds-is-not-thread-safe/
+ //
+ // No good way to reproduce but since this should not be on critical path, let's add
+ // syncing as it seems potentially necessary.
+ synchronized (var) {
+ bounds = var.getBounds();
+ }
return _fromAny(context, bounds[0], bindings);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeModifier.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeModifier.java
index 0b59011..7210041 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/TypeModifier.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeModifier.java
@@ -11,7 +11,7 @@
* replace) basic type instance factory constructs.
* This is typically needed to support creation of
* {@link MapLikeType} and {@link CollectionLikeType} instances,
- * as those can not be constructed in generic fashion.
+ * as those cannot be constructed in generic fashion.
*/
public abstract class TypeModifier
{
@@ -29,7 +29,7 @@
* construct instance of primary type itself
*
* @return Actual type instance to use; usually either <code>type</code> (as is or with
- * modifications), or a newly constructed type instance based on it. Can not be null.
+ * modifications), or a newly constructed type instance based on it. Cannot be null.
*/
public abstract JavaType modifyType(JavaType type, Type jdkType, TypeBindings context,
TypeFactory typeFactory);
diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeParser.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeParser.java
index 1817e0a..3cfcb72 100644
--- a/src/main/java/com/fasterxml/jackson/databind/type/TypeParser.java
+++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeParser.java
@@ -3,6 +3,7 @@
import java.util.*;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Simple recursive-descent parser for parsing canonical {@link JavaType}
@@ -80,10 +81,8 @@
try {
return _factory.findClass(className);
} catch (Exception e) {
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- }
- throw _problem(tokens, "Can not locate class '"+className+"', problem: "+e.getMessage());
+ ClassUtil.throwIfRTE(e);
+ throw _problem(tokens, "Cannot locate class '"+className+"', problem: "+e.getMessage());
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/AccessPattern.java b/src/main/java/com/fasterxml/jackson/databind/util/AccessPattern.java
new file mode 100644
index 0000000..03cabcd
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/util/AccessPattern.java
@@ -0,0 +1,31 @@
+package com.fasterxml.jackson.databind.util;
+
+/**
+ * Enumeration used to indicate required access pattern for providers:
+ * this can sometimes be used to optimize out dynamic calls.
+ * The main difference is between constant values (which can be resolved once)
+ * and dynamic ones (which must be resolved anew every time).
+ */
+public enum AccessPattern {
+ /**
+ * Value that indicates that provider never returns anything other than
+ * Java `null`.
+ */
+ ALWAYS_NULL,
+
+ /**
+ * Value that indicates that provider will always return a constant
+ * value, regardless of when it is called; and also that it never
+ * uses `context` argument (which may then be passed as `null`)
+ */
+ CONSTANT,
+
+ /**
+ * Value that indicates that provider may return different values
+ * at different times (often a freshly constructed empty container),
+ * and thus must be called every time "null replacement" value is
+ * needed.
+ */
+ DYNAMIC
+ ;
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/Annotations.java b/src/main/java/com/fasterxml/jackson/databind/util/Annotations.java
index cdb7ea9..d362403 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/Annotations.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/Annotations.java
@@ -18,6 +18,16 @@
public <A extends Annotation> A get(Class<A> cls);
/**
+ * @since 2.9
+ */
+ public boolean has(Class<?> cls);
+
+ /**
+ * @since 2.9
+ */
+ public boolean hasOneOf(Class<? extends Annotation>[] annoClasses);
+
+ /**
* Returns number of annotation entries in this collection.
*/
public int size();
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ArrayBuilders.java b/src/main/java/com/fasterxml/jackson/databind/util/ArrayBuilders.java
index 834a96e..ae2939a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/ArrayBuilders.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/ArrayBuilders.java
@@ -162,7 +162,7 @@
@Override
public boolean equals(Object other) {
if (other == this) return true;
- if (other == null || other.getClass() != defaultValueType) {
+ if (!ClassUtil.hasClass(other, defaultValueType)) {
return false;
}
if (Array.getLength(other) != length) return false;
@@ -184,76 +184,15 @@
public static <T> HashSet<T> arrayToSet(T[] elements)
{
- HashSet<T> result = new HashSet<T>();
if (elements != null) {
- for (T elem : elements) {
- result.add(elem);
+ int len = elements.length;
+ HashSet<T> result = new HashSet<T>(len);
+ for (int i = 0; i < len; ++i) {
+ result.add(elements[i]);
}
+ return result;
}
- return result;
- }
-
- public static <T> ArrayList<T> arrayToList(T[] elements)
- {
- ArrayList<T> result = new ArrayList<T>();
- if (elements != null) {
- for (T elem : elements) {
- result.add(elem);
- }
- }
- return result;
- }
-
- public static <T> HashSet<T> setAndArray(Set<T> set, T[] elements)
- {
- HashSet<T> result = new HashSet<T>();
- if (set != null) {
- result.addAll(set);
- }
- if (elements != null) {
- for (T value : elements) {
- result.add(value);
- }
- }
- return result;
- }
-
- /**
- * Helper method for adding specified element to a List, but also
- * considering case where the List may not have been yet constructed
- * (that is, null is passed instead).
- *
- * @param list List to add to; may be null to indicate that a new
- * List is to be constructed
- * @param element Element to add to list
- *
- * @return List in which element was added; either <code>list</code>
- * (if it was not null), or a newly constructed List.
- */
- public static <T> List<T> addToList(List<T> list, T element)
- {
- if (list == null) {
- list = new ArrayList<T>();
- }
- list.add(element);
- return list;
- }
-
- /**
- * Helper method for constructing a new array that contains specified
- * element followed by contents of the given array. No checking is done
- * to see if element being inserted is duplicate.
- */
- public static <T> T[] insertInList(T[] array, T element)
- {
- int len = array.length;
- @SuppressWarnings("unchecked")
- T[] result = (T[]) Array.newInstance(array.getClass().getComponentType(), len+1);
- if (len > 0) {
- System.arraycopy(array, 0, result, 1, len);
- }
- result[0] = element;
- return result;
+ return new HashSet<T>();
}
/**
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 5fcaa8d..54462ce 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java
@@ -1,5 +1,11 @@
package com.fasterxml.jackson.databind.util;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
/**
@@ -77,6 +83,7 @@
/**
* @since 2.5
*/
+ @Deprecated // since 2.9, not used any more
public static String okNameForSetter(AnnotatedMethod am, boolean stdNaming) {
String name = okNameForMutator(am, "set", stdNaming);
if ((name != null)
@@ -103,33 +110,52 @@
/*
/**********************************************************
- /* Handling property names, deprecated methods
+ /* Value defaulting helpers
/**********************************************************
*/
+
+ /**
+ * Accessor used to find out "default value" to use for comparing values to
+ * serialize, to determine whether to exclude value from serialization with
+ * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
+ *<p>
+ * Default logic is such that for primitives and wrapper types for primitives, expected
+ * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String,
+ * and for structured (Maps, Collections, arrays) and reference types, criteria
+ * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}
+ * is used.
+ *
+ * @since 2.7
+ */
+ public static Object getDefaultValue(JavaType type)
+ {
+ // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
+ // handling for primitives since they are never passed as nulls.
+ Class<?> cls = type.getRawClass();
- @Deprecated // since 2.5
- public static String okNameForGetter(AnnotatedMethod am) {
- return okNameForGetter(am, false);
- }
-
- @Deprecated // since 2.5
- public static String okNameForRegularGetter(AnnotatedMethod am, String name) {
- return okNameForRegularGetter(am, name, false);
- }
-
- @Deprecated // since 2.5
- public static String okNameForIsGetter(AnnotatedMethod am, String name) {
- return okNameForIsGetter(am, name, false);
- }
-
- @Deprecated // since 2.5
- public static String okNameForSetter(AnnotatedMethod am) {
- return okNameForSetter(am, false);
- }
-
- @Deprecated // since 2.5
- public static String okNameForMutator(AnnotatedMethod am, String prefix) {
- return okNameForMutator(am, prefix, false);
+ // 30-Sep-2016, tatu: Also works for Wrappers, so both `Integer.TYPE` and `Integer.class`
+ // would return `Integer.TYPE`
+ Class<?> prim = ClassUtil.primitiveType(cls);
+ if (prim != null) {
+ return ClassUtil.defaultValue(prim);
+ }
+ if (type.isContainerType() || type.isReferenceType()) {
+ return JsonInclude.Include.NON_EMPTY;
+ }
+ if (cls == String.class) {
+ return "";
+ }
+ // 09-Mar-2016, tatu: Not sure how far this path we want to go but for now
+ // let's add `java.util.Date` and `java.util.Calendar`, as per [databind#1550]
+ if (type.isTypeOrSubTypeOf(Date.class)) {
+ return new Date(0L);
+ }
+ if (type.isTypeOrSubTypeOf(Calendar.class)) {
+ Calendar c = new GregorianCalendar();
+ c.setTimeInMillis(0L);
+ return c;
+ }
+ return null;
}
/*
@@ -139,8 +165,8 @@
*/
/**
- * This method was added to address [JACKSON-53]: need to weed out
- * CGLib-injected "getCallbacks".
+ * This method was added to address the need to weed out
+ * CGLib-injected "getCallbacks" method.
* At this point caller has detected a potential getter method
* with name "getCallbacks" and we need to determine if it is
* indeed injectect by Cglib. We do this by verifying that the
@@ -150,25 +176,21 @@
{
Class<?> rt = am.getRawType();
// Ok, first: must return an array type
- if (rt == null || !rt.isArray()) {
- return false;
- }
- /* And that type needs to be "net.sf.cglib.proxy.Callback".
- * Theoretically could just be a type that implements it, but
- * for now let's keep things simple, fix if need be.
- */
- Class<?> compType = rt.getComponentType();
- // Actually, let's just verify it's a "net.sf.cglib.*" class/interface
- String pkgName = ClassUtil.getPackageName(compType);
- if (pkgName != null) {
- if (pkgName.contains(".cglib")) {
- if (pkgName.startsWith("net.sf.cglib")
- // also, as per [JACKSON-177]
- || pkgName.startsWith("org.hibernate.repackage.cglib")
- // and [core#674]
- || pkgName.startsWith("org.springframework.cglib")
- ) {
- return true;
+ if (rt.isArray()) {
+ /* And that type needs to be "net.sf.cglib.proxy.Callback".
+ * Theoretically could just be a type that implements it, but
+ * for now let's keep things simple, fix if need be.
+ */
+ Class<?> compType = rt.getComponentType();
+ // Actually, let's just verify it's a "net.sf.cglib.*" class/interface
+ String pkgName = ClassUtil.getPackageName(compType);
+ if (pkgName != null) {
+ if (pkgName.contains(".cglib")) {
+ return pkgName.startsWith("net.sf.cglib")
+ // also, as per [JACKSON-177]
+ || pkgName.startsWith("org.hibernate.repackage.cglib")
+ // and [core#674]
+ || pkgName.startsWith("org.springframework.cglib");
}
}
}
@@ -177,32 +199,22 @@
/**
* Similar to {@link #isCglibGetCallbacks}, need to suppress
- * a cyclic reference to resolve [JACKSON-103]
+ * a cyclic reference.
*/
protected static boolean isGroovyMetaClassSetter(AnnotatedMethod am)
{
Class<?> argType = am.getRawParameterType(0);
String pkgName = ClassUtil.getPackageName(argType);
- if (pkgName != null && pkgName.startsWith("groovy.lang")) {
- return true;
- }
- return false;
+ return (pkgName != null) && pkgName.startsWith("groovy.lang");
}
/**
- * Another helper method to deal with rest of [JACKSON-103]
+ * Another helper method to deal with Groovy's problematic metadata accessors
*/
protected static boolean isGroovyMetaClassGetter(AnnotatedMethod am)
{
- Class<?> rt = am.getRawType();
- if (rt == null || rt.isArray()) {
- return false;
- }
- String pkgName = ClassUtil.getPackageName(rt);
- if (pkgName != null && pkgName.startsWith("groovy.lang")) {
- return true;
- }
- return false;
+ String pkgName = ClassUtil.getPackageName(am.getRawType());
+ return (pkgName != null) && pkgName.startsWith("groovy.lang");
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java b/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java
index 5d14255..28e88c5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java
@@ -7,7 +7,10 @@
import java.util.*;
import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
public final class ClassUtil
@@ -16,24 +19,8 @@
private final static Annotation[] NO_ANNOTATIONS = new Annotation[0];
private final static Ctor[] NO_CTORS = new Ctor[0];
-
- /*
- /**********************************************************
- /* Helper classes
- /**********************************************************
- */
- /* 21-Feb-2016, tatu: Unfortunately `Collections.emptyIterator()` only
- * comes with JDK7, so we'll still have to include our bogus implementation
- * for as long as we want JDK6 runtime compatibility
- */
- private final static class EmptyIterator<T> implements Iterator<T> {
- @Override public boolean hasNext() { return false; }
- @Override public T next() { throw new NoSuchElementException(); }
- @Override public void remove() { throw new UnsupportedOperationException(); }
- }
-
- private final static EmptyIterator<?> EMPTY_ITERATOR = new EmptyIterator<Object>();
+ private final static Iterator<?> EMPTY_ITERATOR = Collections.emptyIterator();
/*
/**********************************************************
@@ -46,8 +33,6 @@
*/
@SuppressWarnings("unchecked")
public static <T> Iterator<T> emptyIterator() {
-// 21-Feb-2016, tatu: As per above, use a locally defined empty iterator
-// return Collections.emptyIterator();
return (Iterator<T>) EMPTY_ITERATOR;
}
@@ -288,73 +273,43 @@
return false;
}
- /*
- /**********************************************************
- /* Type name handling methods
- /**********************************************************
- */
-
- /**
- * Helper method used to construct appropriate description
- * when passed either type (Class) or an instance; in latter
- * case, class of instance is to be used.
- */
- public static String getClassDescription(Object classOrInstance)
- {
- if (classOrInstance == null) {
- return "unknown";
- }
- Class<?> cls = (classOrInstance instanceof Class<?>) ?
- (Class<?>) classOrInstance : classOrInstance.getClass();
- return cls.getName();
+ public static boolean isBogusClass(Class<?> cls) {
+ return (cls == Void.class || cls == Void.TYPE
+ || cls == com.fasterxml.jackson.databind.annotation.NoClass.class);
}
- /*
- /**********************************************************
- /* Class loading
- /**********************************************************
- */
+ public static boolean isNonStaticInnerClass(Class<?> cls) {
+ return !Modifier.isStatic(cls.getModifiers())
+ && (getEnclosingClass(cls) != null);
+ }
/**
- * @deprecated Since 2.6, use method in {@link com.fasterxml.jackson.databind.type.TypeFactory}.
+ * @since 2.7
*/
- @Deprecated
- public static Class<?> findClass(String className) throws ClassNotFoundException
+ public static boolean isObjectOrPrimitive(Class<?> cls) {
+ return (cls == CLS_OBJECT) || cls.isPrimitive();
+ }
+
+ /**
+ * @since 2.9
+ */
+ public static boolean hasClass(Object inst, Class<?> raw) {
+ // 10-Nov-2016, tatu: Could use `Class.isInstance()` if we didn't care
+ // about being exactly that type
+ return (inst != null) && (inst.getClass() == raw);
+ }
+
+ /**
+ * @since 2.9
+ */
+ public static void verifyMustOverride(Class<?> expType, Object instance,
+ String method)
{
- // [JACKSON-597]: support primitive types (and void)
- if (className.indexOf('.') < 0) {
- if ("int".equals(className)) return Integer.TYPE;
- if ("long".equals(className)) return Long.TYPE;
- if ("float".equals(className)) return Float.TYPE;
- if ("double".equals(className)) return Double.TYPE;
- if ("boolean".equals(className)) return Boolean.TYPE;
- if ("byte".equals(className)) return Byte.TYPE;
- if ("char".equals(className)) return Character.TYPE;
- if ("short".equals(className)) return Short.TYPE;
- if ("void".equals(className)) return Void.TYPE;
+ if (instance.getClass() != expType) {
+ throw new IllegalStateException(String.format(
+ "Sub-class %s (of class %s) must override method '%s'",
+ instance.getClass().getName(), expType.getName(), method));
}
- // Two-phase lookup: first using context ClassLoader; then default
- Throwable prob = null;
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
-
- if (loader != null) {
- try {
- return Class.forName(className, true, loader);
- } catch (Exception e) {
- prob = getRootCause(e);
- }
- }
- try {
- return Class.forName(className);
- } catch (Exception e) {
- if (prob == null) {
- prob = getRootCause(e);
- }
- }
- if (prob instanceof RuntimeException) {
- throw (RuntimeException) prob;
- }
- throw new ClassNotFoundException(prob.getMessage(), prob);
}
/*
@@ -388,11 +343,56 @@
/*
/**********************************************************
- /* Exception handling
+ /* Exception handling; simple re-throw
/**********************************************************
*/
/**
+ * Helper method that will check if argument is an {@link Error},
+ * and if so, (re)throw it; otherwise just return
+ *
+ * @since 2.9
+ */
+ public static Throwable throwIfError(Throwable t) {
+ if (t instanceof Error) {
+ throw (Error) t;
+ }
+ return t;
+ }
+
+ /**
+ * Helper method that will check if argument is an {@link RuntimeException},
+ * and if so, (re)throw it; otherwise just return
+ *
+ * @since 2.9
+ */
+ public static Throwable throwIfRTE(Throwable t) {
+ if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ }
+ return t;
+ }
+
+ /**
+ * Helper method that will check if argument is an {@link IOException},
+ * and if so, (re)throw it; otherwise just return
+ *
+ * @since 2.9
+ */
+ public static Throwable throwIfIOE(Throwable t) throws IOException {
+ if (t instanceof IOException) {
+ throw (IOException) t;
+ }
+ return t;
+ }
+
+ /*
+ /**********************************************************
+ /* Exception handling; other
+ /**********************************************************
+ */
+
+ /**
* Method that can be used to find the "root cause", innermost
* of chained (wrapped) exceptions.
*/
@@ -405,41 +405,21 @@
}
/**
- * Method that will unwrap root causes of given Throwable, and throw
- * the innermost {@link Exception} or {@link Error} as is.
- * This is useful in cases where mandatory wrapping is added, which
- * is often done by Reflection API.
- */
- public static void throwRootCause(Throwable t) throws Exception
- {
- t = getRootCause(t);
- if (t instanceof Exception) {
- throw (Exception) t;
- }
- throw (Error) t;
- }
-
- /**
- * Method that works like {@link #throwRootCause} if (and only if)
- * root cause is an {@link IOException}; otherwise returns root cause
+ * Method that works like by calling {@link #getRootCause} and then
+ * either throwing it (if instanceof {@link IOException}), or
+ * return.
*
* @since 2.8
*/
- public static Throwable throwRootCauseIfIOE(Throwable t) throws IOException
- {
- t = getRootCause(t);
- if (t instanceof IOException) {
- throw (IOException) t;
- }
- return t;
+ public static Throwable throwRootCauseIfIOE(Throwable t) throws IOException {
+ return throwIfIOE(getRootCause(t));
}
/**
* Method that will wrap 't' as an {@link IllegalArgumentException} if it
* is a checked exception; otherwise (runtime exception or error) throw as is
*/
- public static void throwAsIAE(Throwable t)
- {
+ public static void throwAsIAE(Throwable t) {
throwAsIAE(t, t.getMessage());
}
@@ -450,16 +430,25 @@
*/
public static void throwAsIAE(Throwable t, String msg)
{
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
- if (t instanceof Error) {
- throw (Error) t;
- }
+ throwIfRTE(t);
+ throwIfError(t);
throw new IllegalArgumentException(msg, t);
}
/**
+ * @since 2.9
+ */
+ public static <T> T throwAsMappingException(DeserializationContext ctxt,
+ IOException e0) throws JsonMappingException {
+ if (e0 instanceof JsonMappingException) {
+ throw (JsonMappingException) e0;
+ }
+ JsonMappingException e = JsonMappingException.from(ctxt, e0.getMessage());
+ e.initCause(e0);
+ throw e;
+ }
+
+ /**
* Method that will locate the innermost exception for given Throwable;
* and then wrap it as an {@link IllegalArgumentException} if it
* is a checked exception; otherwise (runtime exception or error) throw as is
@@ -488,8 +477,8 @@
*
* @since 2.8
*/
- public static void closeOnFailAndThrowAsIAE(JsonGenerator g, Exception fail)
- throws IOException
+ public static void closeOnFailAndThrowAsIOE(JsonGenerator g, Exception fail)
+ throws IOException
{
/* 04-Mar-2014, tatu: Let's try to prevent auto-closing of
* structures, which typically causes more damage.
@@ -500,12 +489,8 @@
} catch (Exception e) {
fail.addSuppressed(e);
}
- if (fail instanceof IOException) {
- throw (IOException) fail;
- }
- if (fail instanceof RuntimeException) {
- throw (RuntimeException) fail;
- }
+ throwIfIOE(fail);
+ throwIfRTE(fail);
throw new RuntimeException(fail);
}
@@ -518,7 +503,7 @@
*
* @since 2.8
*/
- public static void closeOnFailAndThrowAsIAE(JsonGenerator g,
+ public static void closeOnFailAndThrowAsIOE(JsonGenerator g,
Closeable toClose, Exception fail)
throws IOException
{
@@ -537,12 +522,8 @@
fail.addSuppressed(e);
}
}
- if (fail instanceof IOException) {
- throw (IOException) fail;
- }
- if (fail instanceof RuntimeException) {
- throw (RuntimeException) fail;
- }
+ throwIfIOE(fail);
+ throwIfRTE(fail);
throw new RuntimeException(fail);
}
@@ -580,17 +561,17 @@
}
}
- public static <T> Constructor<T> findConstructor(Class<T> cls, boolean canFixAccess)
+ public static <T> Constructor<T> findConstructor(Class<T> cls, boolean forceAccess)
throws IllegalArgumentException
{
try {
Constructor<T> ctor = cls.getDeclaredConstructor();
- if (canFixAccess) {
- checkAndFixAccess(ctor);
+ if (forceAccess) {
+ checkAndFixAccess(ctor, forceAccess);
} else {
// Has to be public...
if (!Modifier.isPublic(ctor.getModifiers())) {
- throw new IllegalArgumentException("Default constructor for "+cls.getName()+" is not accessible (non-public?): not allowed to try modify access via Reflection: can not instantiate type");
+ throw new IllegalArgumentException("Default constructor for "+cls.getName()+" is not accessible (non-public?): not allowed to try modify access via Reflection: cannot instantiate type");
}
}
return ctor;
@@ -604,6 +585,180 @@
/*
/**********************************************************
+ /* Class name, description access
+ /**********************************************************
+ */
+
+ /**
+ * @since 2.9
+ */
+ public static Class<?> classOf(Object inst) {
+ if (inst == null) {
+ return null;
+ }
+ return inst.getClass();
+ }
+
+ /**
+ * @since 2.9
+ */
+ public static Class<?> rawClass(JavaType t) {
+ if (t == null) {
+ return null;
+ }
+ return t.getRawClass();
+ }
+
+ /**
+ * @since 2.9
+ */
+ public static <T> T nonNull(T valueOrNull, T defaultValue) {
+ return (valueOrNull == null) ? defaultValue : valueOrNull;
+ }
+
+ /**
+ * @since 2.9
+ */
+ public static String nullOrToString(Object value) {
+ if (value == null) {
+ return null;
+ }
+ return value.toString();
+ }
+
+ /**
+ * @since 2.9
+ */
+ public static String nonNullString(String str) {
+ if (str == null) {
+ return "";
+ }
+ return str;
+ }
+
+ /**
+ * Returns either quoted value (with double-quotes) -- if argument non-null
+ * String -- or String NULL (no quotes) (if null).
+ *
+ * @since 2.9
+ */
+ public static String quotedOr(Object str, String forNull) {
+ if (str == null) {
+ return forNull;
+ }
+ return String.format("\"%s\"", str);
+ }
+
+ /*
+ /**********************************************************
+ /* Type name, name, desc handling methods
+ /**********************************************************
+ */
+
+ /**
+ * Helper method used to construct appropriate description
+ * when passed either type (Class) or an instance; in latter
+ * case, class of instance is to be used.
+ */
+ public static String getClassDescription(Object classOrInstance)
+ {
+ if (classOrInstance == null) {
+ return "unknown";
+ }
+ Class<?> cls = (classOrInstance instanceof Class<?>) ?
+ (Class<?>) classOrInstance : classOrInstance.getClass();
+ return nameOf(cls);
+ }
+
+ /**
+ * Helper method used to construct appropriate description
+ * when passed either type (Class) or an instance; in latter
+ * case, class of instance is to be used.
+ *
+ * @since 2.9
+ */
+ public static String classNameOf(Object inst) {
+ if (inst == null) {
+ return "[null]";
+ }
+ return nameOf(inst.getClass());
+ }
+
+ /**
+ * Returns either `cls.getName()` (if `cls` not null),
+ * or "[null]" if `cls` is null.
+ *
+ * @since 2.9
+ */
+ public static String nameOf(Class<?> cls) {
+ if (cls == null) {
+ return "[null]";
+ }
+ int index = 0;
+ while (cls.isArray()) {
+ ++index;
+ cls = cls.getComponentType();
+ }
+ String base = cls.isPrimitive() ? cls.getSimpleName() : cls.getName();
+ if (index > 0) {
+ StringBuilder sb = new StringBuilder(base);
+ do {
+ sb.append("[]");
+ } while (--index > 0);
+ base = sb.toString();
+ }
+ return backticked(base);
+ }
+
+ /**
+ * Returns either backtick-quoted `named.getName()` (if `named` not null),
+ * or "[null]" if `named` is null.
+ *
+ * @since 2.9
+ */
+ public static String nameOf(Named named) {
+ if (named == null) {
+ return "[null]";
+ }
+ return backticked(named.getName());
+ }
+
+ /*
+ /**********************************************************
+ /* Other escaping, description acces
+ /**********************************************************
+ */
+
+ /**
+ * Returns either `text` or [null].
+ *
+ * @since 2.9
+ */
+ public static String backticked(String text) {
+ if (text == null) {
+ return "[null]";
+ }
+ return new StringBuilder(text.length()+2).append('`').append(text).append('`').toString();
+ }
+
+ /**
+ * Helper method that returns {@link Throwable#getMessage()} for all other exceptions
+ * except for {@link JsonProcessingException}, for which {@code getOriginalMessage()} is
+ * returned instead.
+ * Method is used to avoid accidentally including trailing location information twice
+ * in message when wrapping exceptions.
+ *
+ * @since 2.9.7
+ */
+ public static String exceptionMessage(Throwable t) {
+ if (t instanceof JsonProcessingException) {
+ return ((JsonProcessingException) t).getOriginalMessage();
+ }
+ return t.getMessage();
+ }
+
+ /*
+ /**********************************************************
/* Primitive type support
/**********************************************************
*/
@@ -763,7 +918,7 @@
// Google App Engine); so let's only fail if we really needed it...
if (!ao.isAccessible()) {
Class<?> declClass = member.getDeclaringClass();
- throw new IllegalArgumentException("Can not access "+member+" (from class "+declClass.getName()+"; failed to set access: "+se.getMessage());
+ throw new IllegalArgumentException("Cannot access "+member+" (from class "+declClass.getName()+"; failed to set access: "+se.getMessage());
}
}
}
@@ -873,39 +1028,25 @@
/* Jackson-specific stuff
/**********************************************************
*/
-
+
/**
* Method that can be called to determine if given Object is the default
* implementation Jackson uses; as opposed to a custom serializer installed by
* a module or calling application. Determination is done using
* {@link JacksonStdImpl} annotation on handler (serializer, deserializer etc)
* class.
+ *<p>
+ * NOTE: passing `null` is legal, and will result in <code>true</code>
+ * being returned.
*/
public static boolean isJacksonStdImpl(Object impl) {
- return (impl != null) && isJacksonStdImpl(impl.getClass());
+ return (impl == null) || isJacksonStdImpl(impl.getClass());
}
public static boolean isJacksonStdImpl(Class<?> implClass) {
return (implClass.getAnnotation(JacksonStdImpl.class) != null);
}
- public static boolean isBogusClass(Class<?> cls) {
- return (cls == Void.class || cls == Void.TYPE
- || cls == com.fasterxml.jackson.databind.annotation.NoClass.class);
- }
-
- public static boolean isNonStaticInnerClass(Class<?> cls) {
- return !Modifier.isStatic(cls.getModifiers())
- && (getEnclosingClass(cls) != null);
- }
-
- /**
- * @since 2.7
- */
- public static boolean isObjectOrPrimitive(Class<?> cls) {
- return (cls == CLS_OBJECT) || cls.isPrimitive();
- }
-
/*
/**********************************************************
/* Access to various Class definition aspects; possibly
@@ -955,6 +1096,36 @@
}
/**
+ * Helper method that gets methods declared in given class; usually a simple thing,
+ * but sometimes (as per [databind#785]) more complicated, depending on classloader
+ * setup.
+ *
+ * @since 2.9
+ */
+ public static Method[] getClassMethods(Class<?> cls)
+ {
+ try {
+ return ClassUtil.getDeclaredMethods(cls);
+ } catch (final NoClassDefFoundError ex) {
+ // One of the methods had a class that was not found in the cls.getClassLoader.
+ // Maybe the developer was nice and has a different class loader for this context.
+ final ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ if (loader == null){
+ // Nope... this is going to end poorly
+ throw ex;
+ }
+ final Class<?> contextClass;
+ try {
+ contextClass = loader.loadClass(cls.getName());
+ } catch (ClassNotFoundException e) {
+ ex.addSuppressed(e);
+ throw ex;
+ }
+ return contextClass.getDeclaredMethods(); // Cross fingers
+ }
+ }
+
+ /**
* @since 2.7
*/
public static Ctor[] getConstructors(Class<?> cls) {
@@ -1038,7 +1209,7 @@
if (enumSetTypeField != null) {
return (Class<? extends Enum<?>>) get(set, enumSetTypeField);
}
- throw new IllegalStateException("Can not figure out type for EnumSet (odd JDK platform?)");
+ throw new IllegalStateException("Cannot figure out type for EnumSet (odd JDK platform?)");
}
@SuppressWarnings("unchecked")
@@ -1047,7 +1218,7 @@
if (enumMapTypeField != null) {
return (Class<? extends Enum<?>>) get(set, enumMapTypeField);
}
- throw new IllegalStateException("Can not figure out type for EnumMap (odd JDK platform?)");
+ throw new IllegalStateException("Cannot figure out type for EnumMap (odd JDK platform?)");
}
private Object get(Object bean, Field field)
@@ -1132,7 +1303,6 @@
return _ctor.getDeclaringClass();
}
- // Modest boost: maybe 1%?
public Annotation[] getDeclaredAnnotations() {
Annotation[] result = _annotations;
if (result == null) {
@@ -1142,7 +1312,6 @@
return result;
}
- // Modest boost: maybe 1%?
public Annotation[][] getParameterAnnotations() {
Annotation[][] result = _paramAnnotations;
if (result == null) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/CompactStringObjectMap.java b/src/main/java/com/fasterxml/jackson/databind/util/CompactStringObjectMap.java
index 022248b..f27c0c7 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/CompactStringObjectMap.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/CompactStringObjectMap.java
@@ -124,6 +124,22 @@
return null;
}
+ /**
+ * @since 2.9
+ */
+ public Object findCaseInsensitive(String key) {
+ for (int i = 0, end = _hashArea.length; i < end; i += 2) {
+ Object k2 = _hashArea[i];
+ if (k2 != null) {
+ String s = (String) k2;
+ if (s.equalsIgnoreCase(key)) {
+ return _hashArea[i+1];
+ }
+ }
+ }
+ return null;
+ }
+
public List<String> keys() {
final int end = _hashArea.length;
List<String> keys = new ArrayList<String>(end >> 2);
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ConstantValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/util/ConstantValueInstantiator.java
new file mode 100644
index 0000000..0f8abb7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/util/ConstantValueInstantiator.java
@@ -0,0 +1,38 @@
+package com.fasterxml.jackson.databind.util;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.ValueInstantiator;
+
+/**
+ * Trivial {@link ValueInstantiator} implementation that will simply return constant
+ * {@code Object} it is configured with. May be used as-is, or as base class to override
+ * simplistic behavior further.
+ *
+ * @since 2.9.4
+ */
+public class ConstantValueInstantiator extends ValueInstantiator
+{
+ protected final Object _value;
+
+ public ConstantValueInstantiator(Object value) {
+ _value = value;
+ }
+
+ @Override
+ public Class<?> getValueClass() {
+ return _value.getClass();
+ }
+
+ @Override // yes, since default ctor works
+ public boolean canInstantiate() { return true; }
+
+ @Override
+ public boolean canCreateUsingDefault() { return true; }
+
+ @Override
+ public Object createUsingDefault(DeserializationContext ctxt) throws IOException {
+ return _value;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/Converter.java b/src/main/java/com/fasterxml/jackson/databind/util/Converter.java
index df5dc6a..102a487 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/Converter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/Converter.java
@@ -30,7 +30,7 @@
* Method that can be used to find out actual input (source) type; this
* usually can be determined from type parameters, but may need
* to be implemented differently from programmatically defined
- * converters (which can not change static type parameter bindings).
+ * converters (which cannot change static type parameter bindings).
*
* @since 2.2
*/
@@ -40,7 +40,7 @@
* Method that can be used to find out actual output (target) type; this
* usually can be determined from type parameters, but may need
* to be implemented differently from programmatically defined
- * converters (which can not change static type parameter bindings).
+ * converters (which cannot change static type parameter bindings).
*
* @since 2.2
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java b/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java
index d6ef5e6..f95ea55 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java
@@ -1,9 +1,9 @@
package com.fasterxml.jackson.databind.util;
-import java.lang.reflect.Method;
import java.util.*;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
/**
* Helper class used to resolve String values (either JSON Object field
@@ -83,17 +83,10 @@
}
/**
- * @deprecated Since 2.8, use {@link #constructUsingMethod(Class, Method, AnnotationIntrospector)} instead
+ * @since 2.9
*/
- @Deprecated
- public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls, Method accessor) {
- return constructUsingMethod(enumCls, accessor, null);
- }
-
- /**
- * @since 2.8
- */
- public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls, Method accessor,
+ public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls,
+ AnnotatedMember accessor,
AnnotationIntrospector ai)
{
Enum<?>[] enumValues = enumCls.getEnumConstants();
@@ -102,7 +95,7 @@
for (int i = enumValues.length; --i >= 0; ) {
Enum<?> en = enumValues[i];
try {
- Object o = accessor.invoke(en);
+ Object o = accessor.getValue(en);
if (o != null) {
map.put(o.toString(), en);
}
@@ -129,15 +122,6 @@
}
/**
- * @deprecated Since 2.8, use {@link #constructUnsafeUsingToString(Class, AnnotationIntrospector)} instead
- */
- @Deprecated
- public static EnumResolver constructUnsafeUsingToString(Class<?> rawEnumCls)
- {
- return constructUnsafeUsingToString(rawEnumCls, null);
- }
-
- /**
* Method that needs to be used instead of {@link #constructUsingToString}
* if static type of enum is not known.
*
@@ -153,21 +137,14 @@
}
/**
- * @deprecated Since 2.8, use {@link #constructUnsafeUsingMethod(Class, Method, AnnotationIntrospector)} instead.
- */
- @Deprecated
- public static EnumResolver constructUnsafeUsingMethod(Class<?> rawEnumCls, Method accessor) {
- return constructUnsafeUsingMethod(rawEnumCls, accessor, null);
- }
-
- /**
* Method used when actual String serialization is indicated using @JsonValue
* on a method.
*
- * @since 2.8
+ * @since 2.9
*/
@SuppressWarnings({ "unchecked" })
- public static EnumResolver constructUnsafeUsingMethod(Class<?> rawEnumCls, Method accessor,
+ public static EnumResolver constructUnsafeUsingMethod(Class<?> rawEnumCls,
+ AnnotatedMember accessor,
AnnotationIntrospector ai)
{
// wrong as ever but:
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/EnumValues.java b/src/main/java/com/fasterxml/jackson/databind/util/EnumValues.java
index 6914511..77e1839 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/EnumValues.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/EnumValues.java
@@ -46,7 +46,7 @@
Class<? extends Enum<?>> enumCls = ClassUtil.findEnumType(enumClass);
Enum<?>[] enumValues = enumCls.getEnumConstants();
if (enumValues == null) {
- throw new IllegalArgumentException("Can not determine enum constants for Class "+enumClass.getName());
+ throw new IllegalArgumentException("Cannot determine enum constants for Class "+enumClass.getName());
}
String[] names = config.getAnnotationIntrospector().findEnumValues(enumCls, enumValues, new String[enumValues.length]);
SerializableString[] textual = new SerializableString[enumValues.length];
@@ -72,7 +72,7 @@
}
return new EnumValues(enumClass, textual);
}
- throw new IllegalArgumentException("Can not determine enum constants for Class "+enumClass.getName());
+ throw new IllegalArgumentException("Cannot determine enum constants for Class "+enumClass.getName());
}
public SerializableString serializedValueFor(Enum<?> key) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ISO8601DateFormat.java b/src/main/java/com/fasterxml/jackson/databind/util/ISO8601DateFormat.java
index 833ef6e..f36004f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/ISO8601DateFormat.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/ISO8601DateFormat.java
@@ -1,7 +1,6 @@
package com.fasterxml.jackson.databind.util;
import java.text.*;
-import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -13,25 +12,21 @@
*<p>
* All other methods but parse and format and clone are undefined behavior.
*
- * @see ISO8601Utils
+ * @deprecated Use {@link com.fasterxml.jackson.databind.util.StdDateFormat} instead
*/
+@Deprecated // since 2.9
public class ISO8601DateFormat extends DateFormat
{
private static final long serialVersionUID = 1L;
- // those classes are to try to allow a consistent behavior for hascode/equals and other methods
- private static Calendar CALENDAR = new GregorianCalendar();
- private static NumberFormat NUMBER_FORMAT = new DecimalFormat();
-
public ISO8601DateFormat() {
- this.numberFormat = NUMBER_FORMAT;
- this.calendar = CALENDAR;
+ this.numberFormat = new DecimalFormat();;
+ this.calendar = new GregorianCalendar();;
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
- String value = ISO8601Utils.format(date);
- toAppendTo.append(value);
+ toAppendTo.append(ISO8601Utils.format(date));
return toAppendTo;
}
@@ -54,13 +49,6 @@
@Override
public Object clone() {
- /* Jackson calls clone for every call. Since this instance is
- * immutable (and hence thread-safe)
- * we can just return this instance
- */
return this;
}
-
- @Override
- public String toString() { return getClass().getName(); }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java b/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
index b6f30c5..8078317 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
@@ -12,54 +12,16 @@
*
* @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a>
*/
+@Deprecated // since 2.9
public class ISO8601Utils
{
- @Deprecated // since 2.7
- private static final String GMT_ID = "GMT";
-
- /**
- * ID to represent the 'UTC' string, default timezone since Jackson 2.7
- *
- * @since 2.7
- */
- private static final String UTC_ID = "UTC";
-
- /**
- * The GMT timezone, prefetched to avoid more lookups.
- *
- * @deprecated Since 2.7 use {@link #TIMEZONE_UTC} instead
- */
- @Deprecated
- private static final TimeZone TIMEZONE_GMT = TimeZone.getTimeZone(GMT_ID);
-
- /**
- * The UTC timezone, prefetched to avoid more lookups.
- *
- * @since 2.7
- */
- private static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone(UTC_ID);
+ protected final static int DEF_8601_LEN = "yyyy-MM-ddThh:mm:ss.SSS+00:00".length();
/**
* Timezone we use for 'Z' in ISO-8601 date/time values: since 2.7
* {@link #TIMEZONE_UTC}; with earlier versions up to 2.7 was {@link #TIMEZONE_GMT}.
*/
- private static final TimeZone TIMEZONE_Z = TIMEZONE_UTC;
-
- /*
- /**********************************************************
- /* Static factories
- /**********************************************************
- */
-
- /**
- * Accessor for static GMT timezone instance.
- *
- * @deprecated since 2.6
- */
- @Deprecated // since 2.6
- public static TimeZone timeZoneGMT() {
- return TIMEZONE_GMT;
- }
+ private static final TimeZone TIMEZONE_Z = TimeZone.getTimeZone("UTC");
/*
/**********************************************************
@@ -74,7 +36,7 @@
* @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ'
*/
public static String format(Date date) {
- return format(date, false, TIMEZONE_UTC);
+ return format(date, false, TIMEZONE_Z);
}
/**
@@ -85,7 +47,12 @@
* @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z'
*/
public static String format(Date date, boolean millis) {
- return format(date, millis, TIMEZONE_UTC);
+ return format(date, millis, TIMEZONE_Z);
+ }
+
+ @Deprecated // since 2.9
+ public static String format(Date date, boolean millis, TimeZone tz) {
+ return format(date, millis, tz, Locale.US);
}
/**
@@ -95,46 +62,39 @@
* @param millis true to include millis precision otherwise false
* @param tz timezone to use for the formatting (UTC will produce 'Z')
* @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
+ *
+ * @since 2.9
*/
- public static String format(Date date, boolean millis, TimeZone tz) {
- Calendar calendar = new GregorianCalendar(tz, Locale.US);
+ public static String format(Date date, boolean millis, TimeZone tz, Locale loc) {
+ Calendar calendar = new GregorianCalendar(tz, loc);
calendar.setTime(date);
// estimate capacity of buffer as close as we can (yeah, that's pedantic ;)
- int capacity = "yyyy-MM-ddThh:mm:ss".length();
- capacity += millis ? ".sss".length() : 0;
- capacity += tz.getRawOffset() == 0 ? "Z".length() : "+hh:mm".length();
- StringBuilder formatted = new StringBuilder(capacity);
-
- padInt(formatted, calendar.get(Calendar.YEAR), "yyyy".length());
- formatted.append('-');
- padInt(formatted, calendar.get(Calendar.MONTH) + 1, "MM".length());
- formatted.append('-');
- padInt(formatted, calendar.get(Calendar.DAY_OF_MONTH), "dd".length());
- formatted.append('T');
- padInt(formatted, calendar.get(Calendar.HOUR_OF_DAY), "hh".length());
- formatted.append(':');
- padInt(formatted, calendar.get(Calendar.MINUTE), "mm".length());
- formatted.append(':');
- padInt(formatted, calendar.get(Calendar.SECOND), "ss".length());
+ StringBuilder sb = new StringBuilder(30);
+ sb.append(String.format(
+ "%04d-%02d-%02dT%02d:%02d:%02d",
+ calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH) + 1,
+ calendar.get(Calendar.DAY_OF_MONTH),
+ calendar.get(Calendar.HOUR_OF_DAY),
+ calendar.get(Calendar.MINUTE),
+ calendar.get(Calendar.SECOND)
+ ));
if (millis) {
- formatted.append('.');
- padInt(formatted, calendar.get(Calendar.MILLISECOND), "sss".length());
+ sb.append(String.format(".%03d", calendar.get(Calendar.MILLISECOND)));
}
int offset = tz.getOffset(calendar.getTimeInMillis());
if (offset != 0) {
int hours = Math.abs((offset / (60 * 1000)) / 60);
int minutes = Math.abs((offset / (60 * 1000)) % 60);
- formatted.append(offset < 0 ? '-' : '+');
- padInt(formatted, hours, "hh".length());
- formatted.append(':');
- padInt(formatted, minutes, "mm".length());
+ sb.append(String.format("%c%02d:%02d",
+ (offset < 0 ? '-' : '+'),
+ hours, minutes));
} else {
- formatted.append('Z');
+ sb.append('Z');
}
-
- return formatted.toString();
+ return sb.toString();
}
/*
@@ -287,11 +247,7 @@
return calendar.getTime();
// If we get a ParseException it'll already have the right message/offset.
// Other exception types can convert here.
- } catch (IndexOutOfBoundsException e) {
- fail = e;
- } catch (NumberFormatException e) {
- fail = e;
- } catch (IllegalArgumentException e) {
+ } catch (Exception e) {
fail = e;
}
String input = (date == null) ? null : ('"' + date + '"');
@@ -352,21 +308,6 @@
}
/**
- * Zero pad a number to a specified length
- *
- * @param buffer buffer to use for padding
- * @param value the integer value to pad if necessary.
- * @param length the length of the string we should zero pad
- */
- private static void padInt(StringBuilder buffer, int value, int length) {
- String strValue = Integer.toString(value);
- for (int i = length - strValue.length(); i > 0; i--) {
- buffer.append('0');
- }
- buffer.append(strValue);
- }
-
- /**
* Returns the index of the first character in the string that is not a digit, starting at offset.
*/
private static int indexOfNonDigit(String string, int offset) {
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/NameTransformer.java b/src/main/java/com/fasterxml/jackson/databind/util/NameTransformer.java
index 646fb87..b190968 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/NameTransformer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/NameTransformer.java
@@ -108,7 +108,7 @@
/**
* Method called when reversal of transformation is needed; should return
- * null if this is not possible, that is, given name can not have been
+ * null if this is not possible, that is, given name cannot have been
* result of calling {@link #transform} of this object.
*/
public abstract String reverse(String transformed);
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ObjectBuffer.java b/src/main/java/com/fasterxml/jackson/databind/util/ObjectBuffer.java
index f04f816..034e487 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/ObjectBuffer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/ObjectBuffer.java
@@ -5,7 +5,7 @@
/**
* Helper class to use for constructing Object arrays by appending entries
- * to create arrays of various lengths (length that is not known a priori).
+ * to create arrays of various lengths (length that is not known a priori).
*/
public final class ObjectBuffer
{
@@ -67,12 +67,25 @@
{
_reset();
if (_freeBuffer == null) {
- return new Object[12];
+ return (_freeBuffer = new Object[12]);
}
return _freeBuffer;
}
/**
+ * @since 2.9
+ */
+ public Object[] resetAndStart(Object[] base, int count)
+ {
+ _reset();
+ if ((_freeBuffer == null) || (_freeBuffer.length < count)) {
+ _freeBuffer = new Object[Math.max(12, count)];
+ }
+ System.arraycopy(base, 0, _freeBuffer, 0, count);
+ return _freeBuffer;
+ }
+
+ /**
* Method called to add a full Object array as a chunk buffered within
* this buffer, and to obtain a new array to fill. Caller is not to use
* the array it gives; but to use the returned array for continued
@@ -121,6 +134,7 @@
int totalSize = lastChunkEntries + _size;
Object[] result = new Object[totalSize];
_copyTo(result, totalSize, lastChunk, lastChunkEntries);
+ _reset();
return result;
}
@@ -154,6 +168,7 @@
for (int i = 0; i < lastChunkEntries; ++i) {
resultList.add(lastChunk[i]);
}
+ _reset();
}
/**
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/RawValue.java b/src/main/java/com/fasterxml/jackson/databind/util/RawValue.java
index b9f1b02..aea4427 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/RawValue.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/RawValue.java
@@ -119,7 +119,6 @@
@Override
public String toString() {
- return String.format("[RawValue of type %s]",
- (_value == null) ? "NULL" : _value.getClass().getName());
+ return String.format("[RawValue of type %s]", ClassUtil.classNameOf(_value));
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/SimpleBeanPropertyDefinition.java b/src/main/java/com/fasterxml/jackson/databind/util/SimpleBeanPropertyDefinition.java
index 575ad68..cd9cdf2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/SimpleBeanPropertyDefinition.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/SimpleBeanPropertyDefinition.java
@@ -4,11 +4,11 @@
import java.util.Iterator;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.AnnotationIntrospector;
-import com.fasterxml.jackson.databind.PropertyMetadata;
-import com.fasterxml.jackson.databind.PropertyName;
+
+import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.*;
+import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Simple immutable {@link BeanPropertyDefinition} implementation that can
@@ -19,7 +19,7 @@
public class SimpleBeanPropertyDefinition
extends BeanPropertyDefinition
{
- protected final AnnotationIntrospector _introspector;
+ protected final AnnotationIntrospector _annotationIntrospector;
/**
* Member that defines logical property. Assumption is that it
@@ -45,67 +45,34 @@
*/
protected final JsonInclude.Value _inclusion;
- /**
- * @deprecated Since 2.5 use <code>_fullName</code> instead.
- */
- @Deprecated
- protected final String _name;
-
/*
/**********************************************************
/* Construction
/**********************************************************
*/
- protected SimpleBeanPropertyDefinition(AnnotatedMember member, PropertyName fullName,
- AnnotationIntrospector intr, PropertyMetadata metadata,
- JsonInclude.Include inclusion)
- {
- this(member, fullName, intr, metadata,
- ((inclusion == null) || (inclusion == JsonInclude.Include.USE_DEFAULTS)
- ? EMPTY_INCLUDE : JsonInclude.Value.construct(inclusion, null)));
- }
-
- protected SimpleBeanPropertyDefinition(AnnotatedMember member, PropertyName fullName,
- AnnotationIntrospector intr, PropertyMetadata metadata,
+ /**
+ * @since 2.9
+ */
+ protected SimpleBeanPropertyDefinition(AnnotationIntrospector intr,
+ AnnotatedMember member, PropertyName fullName, PropertyMetadata metadata,
JsonInclude.Value inclusion)
{
- _introspector = intr;
+ _annotationIntrospector = intr;
_member = member;
_fullName = fullName;
- _name = fullName.getSimpleName();
_metadata = (metadata == null) ? PropertyMetadata.STD_OPTIONAL: metadata;
_inclusion = inclusion;
}
/**
- * @deprecated Since 2.5 Use variant that takes PropertyName
- */
- @Deprecated
- protected SimpleBeanPropertyDefinition(AnnotatedMember member, String name,
- AnnotationIntrospector intr) {
- this(member, new PropertyName(name), intr, null, EMPTY_INCLUDE);
- }
-
- /**
* @since 2.2
*/
public static SimpleBeanPropertyDefinition construct(MapperConfig<?> config,
- AnnotatedMember member) {
- return new SimpleBeanPropertyDefinition(member, PropertyName.construct(member.getName()),
- (config == null) ? null : config.getAnnotationIntrospector(),
- null, EMPTY_INCLUDE);
- }
-
- /**
- * @deprecated Since 2.5
- */
- @Deprecated
- public static SimpleBeanPropertyDefinition construct(MapperConfig<?> config,
- AnnotatedMember member, String name) {
- return new SimpleBeanPropertyDefinition(member, PropertyName.construct(name),
- (config == null) ? null : config.getAnnotationIntrospector(),
- null, EMPTY_INCLUDE);
+ AnnotatedMember member)
+ {
+ return new SimpleBeanPropertyDefinition(config.getAnnotationIntrospector(),
+ member, PropertyName.construct(member.getName()), null, EMPTY_INCLUDE);
}
/**
@@ -117,14 +84,19 @@
}
/**
+ * Method called to create instance for virtual properties.
+ *
* @since 2.5
*/
public static SimpleBeanPropertyDefinition construct(MapperConfig<?> config,
AnnotatedMember member, PropertyName name, PropertyMetadata metadata,
- JsonInclude.Include inclusion) {
- return new SimpleBeanPropertyDefinition(member, name,
- (config == null) ? null : config.getAnnotationIntrospector(),
- metadata, inclusion);
+ JsonInclude.Include inclusion)
+ {
+ JsonInclude.Value inclValue
+ = ((inclusion == null) || (inclusion == JsonInclude.Include.USE_DEFAULTS))
+ ? EMPTY_INCLUDE : JsonInclude.Value.construct(inclusion, null);
+ return new SimpleBeanPropertyDefinition(config.getAnnotationIntrospector(),
+ member, name, metadata, inclValue);
}
/**
@@ -133,9 +105,8 @@
public static SimpleBeanPropertyDefinition construct(MapperConfig<?> config,
AnnotatedMember member, PropertyName name, PropertyMetadata metadata,
JsonInclude.Value inclusion) {
- return new SimpleBeanPropertyDefinition(member, name,
- (config == null) ? null : config.getAnnotationIntrospector(),
- metadata, inclusion);
+ return new SimpleBeanPropertyDefinition(config.getAnnotationIntrospector(),
+ member, name, metadata, inclusion);
}
/*
@@ -144,19 +115,13 @@
/**********************************************************
*/
- // Note: removed from base class in 2.6; left here until 2.7
- @Deprecated // since 2.3 (remove in 2.7)
- public BeanPropertyDefinition withName(String newName) {
- return withSimpleName(newName);
- }
-
@Override
public BeanPropertyDefinition withSimpleName(String newName) {
if (_fullName.hasSimpleName(newName) && !_fullName.hasNamespace()) {
return this;
}
- return new SimpleBeanPropertyDefinition(_member, new PropertyName(newName),
- _introspector, _metadata, _inclusion);
+ return new SimpleBeanPropertyDefinition(_annotationIntrospector,
+ _member, new PropertyName(newName), _metadata, _inclusion);
}
@Override
@@ -164,8 +129,8 @@
if (_fullName.equals(newName)) {
return this;
}
- return new SimpleBeanPropertyDefinition(_member, newName,
- _introspector, _metadata, _inclusion);
+ return new SimpleBeanPropertyDefinition(_annotationIntrospector,
+ _member, newName, _metadata, _inclusion);
}
/**
@@ -175,8 +140,8 @@
if (metadata.equals(_metadata)) {
return this;
}
- return new SimpleBeanPropertyDefinition(_member, _fullName,
- _introspector, metadata, _inclusion);
+ return new SimpleBeanPropertyDefinition(_annotationIntrospector,
+ _member, _fullName, metadata, _inclusion);
}
/**
@@ -186,8 +151,8 @@
if (_inclusion == inclusion) {
return this;
}
- return new SimpleBeanPropertyDefinition(_member, _fullName,
- _introspector, _metadata, inclusion);
+ return new SimpleBeanPropertyDefinition(_annotationIntrospector,
+ _member, _fullName, _metadata, inclusion);
}
/*
@@ -212,8 +177,10 @@
@Override
public PropertyName getWrapperName() {
- return ((_introspector == null) && (_member != null))
- ? null : _introspector.findWrapperName(_member);
+ if ((_annotationIntrospector == null) || (_member == null)) {
+ return null;
+ }
+ return _annotationIntrospector.findWrapperName(_member);
}
// hmmh. what should we claim here?
@@ -231,6 +198,22 @@
}
@Override
+ public JavaType getPrimaryType() {
+ if (_member == null) {
+ return TypeFactory.unknownType();
+ }
+ return _member.getType();
+ }
+
+ @Override
+ public Class<?> getRawPrimaryType() {
+ if (_member == null) {
+ return Object.class;
+ }
+ return _member.getRawType();
+ }
+
+ @Override
public JsonInclude.Value findInclusion() {
return _inclusion;
}
@@ -290,46 +273,6 @@
return Collections.singleton(param).iterator();
}
- /**
- * Method used to find accessor (getter, field to access) to use for accessing
- * value of the property.
- * Null if no such member exists.
- */
- @Override
- public AnnotatedMember getAccessor() {
- AnnotatedMember acc = getGetter();
- if (acc == null) {
- acc = getField();
- }
- return acc;
- }
-
- /**
- * Method used to find mutator (constructor parameter, setter, field) to use for
- * changing value of the property.
- * Null if no such member exists.
- */
- @Override
- public AnnotatedMember getMutator() {
- AnnotatedMember acc = getConstructorParameter();
- if (acc == null) {
- acc = getSetter();
- if (acc == null) {
- acc = getField();
- }
- }
- return acc;
- }
-
- @Override
- public AnnotatedMember getNonConstructorMutator() {
- AnnotatedMember acc = getSetter();
- if (acc == null) {
- acc = getField();
- }
- return acc;
- }
-
@Override
public AnnotatedMember getPrimaryMember() { return _member; }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/StdConverter.java b/src/main/java/com/fasterxml/jackson/databind/util/StdConverter.java
index cd2b2cb..1bc3f02 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/StdConverter.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/StdConverter.java
@@ -36,7 +36,7 @@
JavaType thisType = tf.constructType(getClass());
JavaType convType = thisType.findSuperType(Converter.class);
if (convType == null || convType.containedTypeCount() < 2) {
- throw new IllegalStateException("Can not find OUT type parameter for Converter of type "+getClass().getName());
+ throw new IllegalStateException("Cannot find OUT type parameter for Converter of type "+getClass().getName());
}
return convType;
}
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 46346de..02afe5f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java
@@ -6,6 +6,8 @@
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import com.fasterxml.jackson.core.io.NumberInput;
@@ -19,41 +21,46 @@
public class StdDateFormat
extends DateFormat
{
- /* TODO !!! 24-Nov-2009, tatu: Should rewrite this class:
- * JDK date parsing is awfully brittle, and ISO-8601 is quite
- * permissive. The two don't mix, need to write a better one.
+ /* 24-Jun-2017, tatu: Finally rewrote deserialization to use basic Regex
+ * instead of SimpleDateFormat; partly for better concurrency, partly
+ * for easier enforcing of specific rules. Heavy lifting done by Calendar,
+ * anyway.
*/
- // 02-Oct-2014, tatu: Alas. While spit'n'polished a few times, still
- // not really robust. But still in use.
+ protected final static String PATTERN_PLAIN_STR = "\\d\\d\\d\\d[-]\\d\\d[-]\\d\\d";
+
+ protected final static Pattern PATTERN_PLAIN = Pattern.compile(PATTERN_PLAIN_STR);
+
+ protected final static Pattern PATTERN_ISO8601;
+ static {
+ Pattern p = null;
+ try {
+ p = Pattern.compile(PATTERN_PLAIN_STR
+ +"[T]\\d\\d[:]\\d\\d(?:[:]\\d\\d)?" // hours, minutes, optional seconds
+ +"(\\.\\d+)?" // optional second fractions
+ +"(Z|[+-]\\d\\d(?:[:]?\\d\\d)?)?" // optional timeoffset/Z
+ );
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ PATTERN_ISO8601 = p;
+ }
/**
* Defines a commonly used date format that conforms
* to ISO-8601 date formatting standard, when it includes basic undecorated
- * timezone definition
+ * timezone definition.
*/
public final static String DATE_FORMAT_STR_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
/**
- * Same as 'regular' 8601, but handles 'Z' as an alias for "+0000"
- * (or "UTC")
- */
- protected final static String DATE_FORMAT_STR_ISO8601_Z = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
-
- /**
- * Same as 'regular' 8601 except misses timezone altogether
- *
- * @since 2.8.10
- */
- protected final static String DATE_FORMAT_STR_ISO8601_NO_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS";
-
- /**
- * ISO-8601 with just the Date part, no time
+ * ISO-8601 with just the Date part, no time: needed for error messages
*/
protected final static String DATE_FORMAT_STR_PLAIN = "yyyy-MM-dd";
/**
* This constant defines the date format specified by
- * RFC 1123 / RFC 822.
+ * RFC 1123 / RFC 822. Used for parsing via `SimpleDateFormat` as well as
+ * error messages.
*/
protected final static String DATE_FORMAT_STR_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
@@ -62,8 +69,7 @@
*/
protected final static String[] ALL_FORMATS = new String[] {
DATE_FORMAT_STR_ISO8601,
- DATE_FORMAT_STR_ISO8601_Z,
- DATE_FORMAT_STR_ISO8601_NO_TZ,
+ "yyyy-MM-dd'T'HH:mm:ss.SSS", // ISO-8601 but no timezone
DATE_FORMAT_STR_RFC1123,
DATE_FORMAT_STR_PLAIN
};
@@ -72,47 +78,44 @@
* By default we use UTC for everything, with Jackson 2.7 and later
* (2.6 and earlier relied on GMT)
*/
- private final static TimeZone DEFAULT_TIMEZONE;
+ protected final static TimeZone DEFAULT_TIMEZONE;
static {
DEFAULT_TIMEZONE = TimeZone.getTimeZone("UTC"); // since 2.7
}
- private final static Locale DEFAULT_LOCALE = Locale.US;
+ protected final static Locale DEFAULT_LOCALE = Locale.US;
protected final static DateFormat DATE_FORMAT_RFC1123;
protected final static DateFormat DATE_FORMAT_ISO8601;
- protected final static DateFormat DATE_FORMAT_ISO8601_Z;
- protected final static DateFormat DATE_FORMAT_ISO8601_NO_TZ; // since 2.8.10
- protected final static DateFormat DATE_FORMAT_PLAIN;
-
- /* Let's construct "blueprint" date format instances: can not be used
+ /* Let's construct "blueprint" date format instances: cannot be used
* as is, due to thread-safety issues, but can be used for constructing
* actual instances more cheaply (avoids re-parsing).
*/
static {
- /* Another important thing: let's force use of default timezone for
- * baseline DataFormat objects
- */
-
+ // Another important thing: let's force use of default timezone for
+ // baseline DataFormat objects
DATE_FORMAT_RFC1123 = new SimpleDateFormat(DATE_FORMAT_STR_RFC1123, DEFAULT_LOCALE);
DATE_FORMAT_RFC1123.setTimeZone(DEFAULT_TIMEZONE);
DATE_FORMAT_ISO8601 = new SimpleDateFormat(DATE_FORMAT_STR_ISO8601, DEFAULT_LOCALE);
DATE_FORMAT_ISO8601.setTimeZone(DEFAULT_TIMEZONE);
- DATE_FORMAT_ISO8601_Z = new SimpleDateFormat(DATE_FORMAT_STR_ISO8601_Z, DEFAULT_LOCALE);
- DATE_FORMAT_ISO8601_Z.setTimeZone(DEFAULT_TIMEZONE);
- DATE_FORMAT_ISO8601_NO_TZ = new SimpleDateFormat(DATE_FORMAT_STR_ISO8601_NO_TZ, DEFAULT_LOCALE);
- DATE_FORMAT_ISO8601_NO_TZ.setTimeZone(DEFAULT_TIMEZONE);
- DATE_FORMAT_PLAIN = new SimpleDateFormat(DATE_FORMAT_STR_PLAIN, DEFAULT_LOCALE);
- DATE_FORMAT_PLAIN.setTimeZone(DEFAULT_TIMEZONE);
}
-
+
/**
* A singleton instance can be used for cloning purposes, as a blueprint of sorts.
*/
public final static StdDateFormat instance = new StdDateFormat();
-
+
+ /**
+ * Blueprint "Calendar" instance for use during formatting. Cannot be used as is,
+ * due to thread-safety issues, but can be used for constructing actual instances
+ * more cheaply by cloning.
+ *
+ * @since 2.9.1
+ */
+ protected static final Calendar CALENDAR = new GregorianCalendar(DEFAULT_TIMEZONE, DEFAULT_LOCALE);
+
/**
* Caller may want to explicitly override timezone to use; if so,
* we will have non-null value here.
@@ -124,19 +127,29 @@
/**
* Explicit override for leniency, if specified.
*<p>
- * Can not be `final` because {@link #setLenient(boolean)} returns
+ * Cannot be `final` because {@link #setLenient(boolean)} returns
* `void`.
*
* @since 2.7
*/
protected Boolean _lenient;
-
- protected transient DateFormat _formatRFC1123;
- protected transient DateFormat _formatISO8601;
- protected transient DateFormat _formatISO8601_z;
- protected transient DateFormat _formatISO8601_noTz; // 2.8.10
- protected transient DateFormat _formatPlain;
+ /**
+ * Lazily instantiated calendar used by this instance for serialization ({@link #format(Date)}).
+ *
+ * @since 2.9.1
+ */
+ private transient Calendar _calendar;
+
+ private transient DateFormat _formatRFC1123;
+
+ /**
+ * Whether the TZ offset must be formatted with a colon between hours and minutes ({@code HH:mm} format)
+ *
+ * @since 2.9.1
+ */
+ private boolean _tzSerializedWithColon = false;
+
/*
/**********************************************************
/* Life cycle, accessing singleton "standard" formats
@@ -154,9 +167,18 @@
}
protected StdDateFormat(TimeZone tz, Locale loc, Boolean lenient) {
+ this(tz, loc, lenient, false);
+ }
+
+ /**
+ * @since 2.9.1
+ */
+ protected StdDateFormat(TimeZone tz, Locale loc, Boolean lenient,
+ boolean formatTzOffsetWithColon) {
_timezone = tz;
_locale = loc;
_lenient = lenient;
+ _tzSerializedWithColon = formatTzOffsetWithColon;
}
public static TimeZone getDefaultTimeZone() {
@@ -174,30 +196,61 @@
if ((tz == _timezone) || tz.equals(_timezone)) {
return this;
}
- return new StdDateFormat(tz, _locale, _lenient);
+ return new StdDateFormat(tz, _locale, _lenient, _tzSerializedWithColon);
}
+ /**
+ * "Mutant factory" method that will return an instance that uses specified
+ * {@code Locale}:
+ * either {@code this} instance (if setting would not change), or newly
+ * constructed instance with different {@code Locale} to use.
+ */
public StdDateFormat withLocale(Locale loc) {
if (loc.equals(_locale)) {
return this;
}
- return new StdDateFormat(_timezone, loc, _lenient);
- }
-
- @Override
- public StdDateFormat clone() {
- /* Although there is that much state to share, we do need to
- * orchestrate a bit, mostly since timezones may be changed
- */
- return new StdDateFormat(_timezone, _locale, _lenient);
+ return new StdDateFormat(_timezone, loc, _lenient, _tzSerializedWithColon);
}
/**
- * @deprecated Since 2.4; use variant that takes Locale
+ * "Mutant factory" method that will return an instance that has specified leniency
+ * setting: either {@code this} instance (if setting would not change), or newly
+ * constructed instance.
+ *
+ * @since 2.9
*/
- @Deprecated
- public static DateFormat getISO8601Format(TimeZone tz) {
- return getISO8601Format(tz, DEFAULT_LOCALE);
+ public StdDateFormat withLenient(Boolean b) {
+ if (_equals(b, _lenient)) {
+ return this;
+ }
+ return new StdDateFormat(_timezone, _locale, b, _tzSerializedWithColon);
+ }
+
+ /**
+ * "Mutant factory" method that will return an instance that has specified
+ * handling of colon when serializing timezone (timezone either written
+ * like {@code +0500} or {@code +05:00}):
+ * either {@code this} instance (if setting would not change), or newly
+ * constructed instance with desired setting for colon inclusion.
+ *<p>
+ * NOTE: does NOT affect deserialization as colon is optional accepted
+ * but not required -- put another way, either serialization is accepted
+ * by this class.
+ *
+ * @since 2.9.1
+ */
+ public StdDateFormat withColonInTimeZone(boolean b) {
+ if (_tzSerializedWithColon == b) {
+ return this;
+ }
+ return new StdDateFormat(_timezone, _locale, _lenient, b);
+ }
+
+ @Override
+ public StdDateFormat clone() {
+ // Although there is that much state to share, we do need to
+ // orchestrate a bit, mostly since timezones may be changed
+ return new StdDateFormat(_timezone, _locale, _lenient, _tzSerializedWithColon);
}
/**
@@ -206,7 +259,10 @@
* compliant date format.
*
* @since 2.4
+ *
+ * @deprecated Since 2.9
*/
+ @Deprecated // since 2.9
public static DateFormat getISO8601Format(TimeZone tz, Locale loc) {
return _cloneFormat(DATE_FORMAT_ISO8601, DATE_FORMAT_STR_ISO8601, tz, loc, null);
}
@@ -217,20 +273,15 @@
* compliant date format.
*
* @since 2.4
+ *
+ * @deprecated Since 2.9
*/
+ @Deprecated // since 2.9
public static DateFormat getRFC1123Format(TimeZone tz, Locale loc) {
return _cloneFormat(DATE_FORMAT_RFC1123, DATE_FORMAT_STR_RFC1123,
tz, loc, null);
}
- /**
- * @deprecated Since 2.4; use variant that takes Locale
- */
- @Deprecated
- public static DateFormat getRFC1123Format(TimeZone tz) {
- return getRFC1123Format(tz, DEFAULT_LOCALE);
- }
-
/*
/**********************************************************
/* Public API, configuration
@@ -261,8 +312,8 @@
*/
@Override // since 2.7
public void setLenient(boolean enabled) {
- Boolean newValue = enabled;
- if (_lenient != newValue) {
+ Boolean newValue = Boolean.valueOf(enabled);
+ if (!_equals(newValue, _lenient)) {
_lenient = newValue;
// and since leniency settings may have been used:
_clearFormats();
@@ -271,11 +322,26 @@
@Override // since 2.7
public boolean isLenient() {
- if (_lenient == null) {
- // default is, I believe, true
- return true;
- }
- return _lenient.booleanValue();
+ // default is, I believe, true
+ return (_lenient == null) || _lenient.booleanValue();
+ }
+
+ /**
+ * Accessor for checking whether this instance would include colon
+ * within timezone serialization or not: if {code true}, timezone offset
+ * is serialized like {@code -06:00}; if {code false} as {@code -0600}.
+ *<p>
+ * NOTE: only relevant for serialization (formatting), as deserialization
+ * (parsing) always accepts optional colon but does not require it, regardless
+ * of this setting.
+ *
+ * @return {@code true} if a colon is to be inserted between the hours and minutes
+ * of the TZ offset when serializing as String; otherwise {@code false}
+ *
+ * @since 2.9.1
+ */
+ public boolean isColonIncludedInTimeZone() {
+ return _tzSerializedWithColon;
}
/*
@@ -289,36 +355,10 @@
{
dateStr = dateStr.trim();
ParsePosition pos = new ParsePosition(0);
-
- Date dt;
-
- if (looksLikeISO8601(dateStr)) { // also includes "plain"
- dt = parseAsISO8601(dateStr, pos, true);
- } else {
- // Also consider "stringified" simple time stamp
- int i = dateStr.length();
- while (--i >= 0) {
- char ch = dateStr.charAt(i);
- if (ch < '0' || ch > '9') {
- // 07-Aug-2013, tatu: And [databind#267] points out that negative numbers should also work
- if (i > 0 || ch != '-') {
- break;
- }
- }
- }
- if ((i < 0)
- // let's just assume negative numbers are fine (can't be RFC-1123 anyway); check length for positive
- && (dateStr.charAt(0) == '-' || NumberInput.inLongRange(dateStr, false))) {
- dt = new Date(Long.parseLong(dateStr));
- } else {
- // Otherwise, fall back to using RFC 1123
- dt = parseAsRFC1123(dateStr, pos);
- }
- }
+ Date dt = _parseDate(dateStr, pos);
if (dt != null) {
return dt;
}
-
StringBuilder sb = new StringBuilder();
for (String f : ALL_FORMATS) {
if (sb.length() > 0) {
@@ -330,19 +370,26 @@
}
sb.append('"');
throw new ParseException
- (String.format("Can not parse date \"%s\": not compatible with any of standard forms (%s)",
+ (String.format("Cannot parse date \"%s\": not compatible with any of standard forms (%s)",
dateStr, sb.toString()), pos.getErrorIndex());
}
+ // 24-Jun-2017, tatu: I don't think this ever gets called. So could... just not implement?
@Override
public Date parse(String dateStr, ParsePosition pos)
{
+ try {
+ return _parseDate(dateStr, pos);
+ } catch (ParseException e) {
+ // may look weird but this is what `DateFormat` suggest to do...
+ }
+ return null;
+ }
+
+ protected Date _parseDate(String dateStr, ParsePosition pos) throws ParseException
+ {
if (looksLikeISO8601(dateStr)) { // also includes "plain"
- try {
- return parseAsISO8601(dateStr, pos, false);
- } catch (ParseException e) { // will NOT be thrown due to false but is declared...
- return null;
- }
+ return parseAsISO8601(dateStr, pos);
}
// Also consider "stringified" simple time stamp
int i = dateStr.length();
@@ -355,13 +402,12 @@
}
}
}
- if (i < 0) { // all digits
+ if ((i < 0)
// let's just assume negative numbers are fine (can't be RFC-1123 anyway); check length for positive
- if (dateStr.charAt(0) == '-' || NumberInput.inLongRange(dateStr, false)) {
- return new Date(Long.parseLong(dateStr));
- }
+ && (dateStr.charAt(0) == '-' || NumberInput.inLongRange(dateStr, false))) {
+ return _parseDateFromLong(dateStr, pos);
}
- // Otherwise, fall back to using RFC 1123
+ // Otherwise, fall back to using RFC 1123. NOTE: call will NOT throw, just returns `null`
return parseAsRFC1123(dateStr, pos);
}
@@ -375,28 +421,148 @@
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition fieldPosition)
{
- if (_formatISO8601 == null) {
- _formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601, DATE_FORMAT_STR_ISO8601,
- _timezone, _locale, _lenient);
+ TimeZone tz = _timezone;
+ if (tz == null) {
+ tz = DEFAULT_TIMEZONE;
}
- return _formatISO8601.format(date, toAppendTo, fieldPosition);
+ _format(tz, _locale, date, toAppendTo);
+ return toAppendTo;
+ }
+
+ protected void _format(TimeZone tz, Locale loc, Date date,
+ StringBuffer buffer)
+ {
+ Calendar cal = _getCalendar(tz);
+ cal.setTime(date);
+ // [databind#2167]: handle range beyond [1, 9999]
+ final int year = cal.get(Calendar.YEAR);
+
+ // Assuming GregorianCalendar, special handling needed for BCE (aka BC)
+ if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
+ _formatBCEYear(buffer, year);
+ } else {
+ if (year > 9999) {
+ // 22-Nov-2018, tatu: Handling beyond 4-digits is not well specified wrt ISO-8601, but
+ // it seems that plus prefix IS mandated. Padding is an open question, but since agreeement
+ // for max length would be needed, we ewould need to limit to arbitrary length
+ // like five digits (erroring out if beyond or padding to that as minimum).
+ // Instead, let's just print number out as is and let decoder try to make sense of it.
+ buffer.append('+');
+ }
+ pad4(buffer, year);
+ }
+ buffer.append('-');
+ pad2(buffer, cal.get(Calendar.MONTH) + 1);
+ buffer.append('-');
+ pad2(buffer, cal.get(Calendar.DAY_OF_MONTH));
+ buffer.append('T');
+ pad2(buffer, cal.get(Calendar.HOUR_OF_DAY));
+ buffer.append(':');
+ pad2(buffer, cal.get(Calendar.MINUTE));
+ buffer.append(':');
+ pad2(buffer, cal.get(Calendar.SECOND));
+ buffer.append('.');
+ pad3(buffer, cal.get(Calendar.MILLISECOND));
+
+ int offset = tz.getOffset(cal.getTimeInMillis());
+ if (offset != 0) {
+ int hours = Math.abs((offset / (60 * 1000)) / 60);
+ int minutes = Math.abs((offset / (60 * 1000)) % 60);
+ buffer.append(offset < 0 ? '-' : '+');
+ pad2(buffer, hours);
+ if( _tzSerializedWithColon ) {
+ buffer.append(':');
+ }
+ pad2(buffer, minutes);
+ } else {
+ // 24-Jun-2017, tatu: While `Z` would be conveniently short, older specs
+ // mandate use of full `+0000`
+// formatted.append('Z');
+ if( _tzSerializedWithColon ) {
+ buffer.append("+00:00");
+ }
+ else {
+ buffer.append("+0000");
+ }
+ }
}
+ protected void _formatBCEYear(StringBuffer buffer, int bceYearNoSign) {
+ // Ok. First of all, BCE 1 output (given as value `1` in era BCE) needs to become
+ // "+0000", but rest (from `2` up, in that era) need minus sign.
+ if (bceYearNoSign == 1) {
+ buffer.append("+0000");
+ return;
+ }
+ final int isoYear = bceYearNoSign - 1;
+ buffer.append('-');
+ // as with CE, 4 digit variant needs padding; beyond that not (although that part is
+ // open to debate, needs agreement with receiver)
+ // But `pad4()` deals with "big" numbers now so:
+ pad4(buffer, isoYear);
+ }
+
+ private static void pad2(StringBuffer buffer, int value) {
+ int tens = value / 10;
+ if (tens == 0) {
+ buffer.append('0');
+ } else {
+ buffer.append((char) ('0' + tens));
+ value -= 10 * tens;
+ }
+ buffer.append((char) ('0' + value));
+ }
+
+ private static void pad3(StringBuffer buffer, int value) {
+ int h = value / 100;
+ if (h == 0) {
+ buffer.append('0');
+ } else {
+ buffer.append((char) ('0' + h));
+ value -= (h * 100);
+ }
+ pad2(buffer, value);
+ }
+
+ private static void pad4(StringBuffer buffer, int value) {
+ int h = value / 100;
+ if (h == 0) {
+ buffer.append('0').append('0');
+ } else {
+ if (h > 99) { // [databind#2167]: handle above 9999 correctly
+ buffer.append(h);
+ } else {
+ pad2(buffer, h);
+ }
+ value -= (100 * h);
+ }
+ pad2(buffer, value);
+ }
+
/*
/**********************************************************
/* Std overrides
/**********************************************************
*/
-
+
@Override
public String toString() {
- String str = "DateFormat "+getClass().getName();
- TimeZone tz = _timezone;
- if (tz != null) {
- str += " (timezone: "+tz+")";
- }
- str += "(locale: "+_locale+")";
- return str;
+ return String.format("DateFormat %s: (timezone: %s, locale: %s, lenient: %s)",
+ getClass().getName(), _timezone, _locale, _lenient);
+ }
+
+ public String toPattern() { // same as SimpleDateFormat
+ StringBuilder sb = new StringBuilder(100);
+ sb.append("[one of: '")
+ .append(DATE_FORMAT_STR_ISO8601)
+ .append("', '")
+ .append(DATE_FORMAT_STR_RFC1123)
+ .append("' (")
+ ;
+ sb.append(Boolean.FALSE.equals(_lenient) ?
+ "strict" : "lenient")
+ .append(")]");
+ return sb.toString();
}
@Override // since 2.7[.2], as per [databind#1130]
@@ -411,146 +577,172 @@
/*
/**********************************************************
- /* Helper methods
+ /* Helper methods, parsing
/**********************************************************
*/
/**
- * Overridable helper method used to figure out which of supported
- * formats is the likeliest match.
+ * Helper method used to figure out if input looks like valid
+ * ISO-8601 string.
*/
protected boolean looksLikeISO8601(String dateStr)
{
- if (dateStr.length() >= 5
+ if (dateStr.length() >= 7 // really need 10, but...
&& Character.isDigit(dateStr.charAt(0))
&& Character.isDigit(dateStr.charAt(3))
&& dateStr.charAt(4) == '-'
+ && Character.isDigit(dateStr.charAt(5))
) {
return true;
}
return false;
}
- protected Date parseAsISO8601(String dateStr, ParsePosition pos, boolean throwErrors)
- throws ParseException
+ private Date _parseDateFromLong(String longStr, ParsePosition pos) throws ParseException
{
- /* 21-May-2009, tatu: DateFormat has very strict handling of
- * timezone modifiers for ISO-8601. So we need to do some scrubbing.
- */
+ long ts;
+ try {
+ ts = NumberInput.parseLong(longStr);
+ } catch (NumberFormatException e) {
+ throw new ParseException(String.format(
+ "Timestamp value %s out of 64-bit value range", longStr),
+ pos.getErrorIndex());
+ }
+ return new Date(ts);
+ }
- /* First: do we have "zulu" format ('Z' == "UTC")? If yes, that's
- * quite simple because we already set date format timezone to be
- * UTC, and hence can just strip out 'Z' altogether
- */
- int len = dateStr.length();
- char c = dateStr.charAt(len-1);
- DateFormat df;
+ protected Date parseAsISO8601(String dateStr, ParsePosition pos)
+ throws ParseException
+ {
+ try {
+ return _parseAsISO8601(dateStr, pos);
+ } catch (IllegalArgumentException e) {
+ throw new ParseException(String.format("Cannot parse date \"%s\", problem: %s",
+ dateStr, e.getMessage()),
+ pos.getErrorIndex());
+ }
+ }
+
+ protected Date _parseAsISO8601(String dateStr, ParsePosition bogus)
+ throws IllegalArgumentException, ParseException
+ {
+ final int totalLen = dateStr.length();
+ // actually, one short-cut: if we end with "Z", must be UTC
+ TimeZone tz = DEFAULT_TIMEZONE;
+ if ((_timezone != null) && ('Z' != dateStr.charAt(totalLen-1))) {
+ tz = _timezone;
+ }
+ Calendar cal = _getCalendar(tz);
+ cal.clear();
String formatStr;
+ if (totalLen <= 10) {
+ Matcher m = PATTERN_PLAIN.matcher(dateStr);
+ if (m.matches()) {
+ int year = _parse4D(dateStr, 0);
+ int month = _parse2D(dateStr, 5)-1;
+ int day = _parse2D(dateStr, 8);
- // Need to support "plain" date...
- if (len <= 10 && Character.isDigit(c)) {
- df = _formatPlain;
+ cal.set(year, month, day, 0, 0, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ return cal.getTime();
+ }
formatStr = DATE_FORMAT_STR_PLAIN;
- if (df == null) {
- df = _formatPlain = _cloneFormat(DATE_FORMAT_PLAIN, formatStr,
- _timezone, _locale, _lenient);
- }
- } else if (c == 'Z') {
- df = _formatISO8601_z;
- formatStr = DATE_FORMAT_STR_ISO8601_Z;
- if (df == null) {
- // 10-Jun-2017, tatu: As per [databind#1651], when using this format,
- // must use UTC, not whatever is configured as default timezone
- // (because we know `Z` identifier is used)
- df = _formatISO8601_z = _cloneFormat(DATE_FORMAT_ISO8601_Z, formatStr,
- DEFAULT_TIMEZONE, _locale, _lenient);
- }
- // may be missing milliseconds... if so, add
- if (dateStr.charAt(len-4) == ':') {
- StringBuilder sb = new StringBuilder(dateStr);
- sb.insert(len-1, ".000");
- dateStr = sb.toString();
- }
} else {
- // Let's see if we have timezone indicator or not...
- if (hasTimeZone(dateStr)) {
- c = dateStr.charAt(len-3);
- if (c == ':') { // remove optional colon
- // remove colon
- StringBuilder sb = new StringBuilder(dateStr);
- sb.delete(len-3, len-2);
- dateStr = sb.toString();
- } else if (c == '+' || c == '-') { // missing minutes
- // let's just append '00'
- dateStr += "00";
- }
- // Milliseconds partial or missing; and even seconds are optional
- len = dateStr.length();
- // remove 'T', '+'/'-' and 4-digit timezone-offset
- int timeLen = len - dateStr.lastIndexOf('T') - 6;
- if (timeLen < 12) { // 8 for hh:mm:ss, 4 for .sss
- int offset = len - 5; // insertion offset, before tz-offset
- StringBuilder sb = new StringBuilder(dateStr);
- switch (timeLen) {
- case 11:
- sb.insert(offset, '0'); break;
- case 10:
- sb.insert(offset, "00"); break;
- case 9: // is this legal? (just second fraction marker)
- sb.insert(offset, "000"); break;
- case 8:
- sb.insert(offset, ".000"); break;
- case 7: // not legal to have single-digit second
- break;
- case 6: // probably not legal, but let's allow
- sb.insert(offset, "00.000");
- case 5: // is legal to omit seconds
- sb.insert(offset, ":00.000");
+ Matcher m = PATTERN_ISO8601.matcher(dateStr);
+ if (m.matches()) {
+ // Important! START with optional time zone; otherwise Calendar will explode
+
+ int start = m.start(2);
+ int end = m.end(2);
+ int len = end-start;
+ if (len > 1) { // 0 -> none, 1 -> 'Z'
+ // NOTE: first char is sign; then 2 digits, then optional colon, optional 2 digits
+ int offsetSecs = _parse2D(dateStr, start+1) * 3600; // hours
+ if (len >= 5) {
+ offsetSecs += _parse2D(dateStr, end-2) * 60; // minutes
}
- dateStr = sb.toString();
- }
- df = _formatISO8601;
- formatStr = DATE_FORMAT_STR_ISO8601;
- if (_formatISO8601 == null) {
- df = _formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601, formatStr,
- _timezone, _locale, _lenient);
- }
- } else {
- // If not, plain date, no timezone
- int timeLen = len - dateStr.lastIndexOf('T') - 1;
- // And possible also millisecond part if missing
- if (timeLen < 12) { // missing, or partial
- StringBuilder sb = new StringBuilder(dateStr);
- switch (timeLen) {
- case 11: sb.append('0');
- case 10: sb.append('0');
- case 9: sb.append('0');
- break;
- default:
- sb.append(".000");
+ if (dateStr.charAt(start) == '-') {
+ offsetSecs *= -1000;
+ } else {
+ offsetSecs *= 1000;
}
- dateStr = sb.toString();
+ cal.set(Calendar.ZONE_OFFSET, offsetSecs);
+ // 23-Jun-2017, tatu: Not sure why, but this appears to be needed too:
+ cal.set(Calendar.DST_OFFSET, 0);
}
- df = _formatISO8601_noTz;
- formatStr = DATE_FORMAT_STR_ISO8601_NO_TZ;
- if (df == null) {
- // 10-Jun-2017, tatu: As per [databind#1651], when using this format,
- // must use UTC, not whatever is configured as default timezone
- // (because we know `Z` identifier is used)
- df = _formatISO8601_noTz = _cloneFormat(DATE_FORMAT_ISO8601_NO_TZ, formatStr,
- _timezone, _locale, _lenient);
+
+ int year = _parse4D(dateStr, 0);
+ int month = _parse2D(dateStr, 5)-1;
+ int day = _parse2D(dateStr, 8);
+
+ // So: 10 chars for date, then `T`, so starts at 11
+ int hour = _parse2D(dateStr, 11);
+ int minute = _parse2D(dateStr, 14);
+
+ // Seconds are actually optional... so
+ int seconds;
+ if ((totalLen > 16) && dateStr.charAt(16) == ':') {
+ seconds = _parse2D(dateStr, 17);
+ } else {
+ seconds = 0;
}
+ cal.set(year, month, day, hour, minute, seconds);
+
+ // Optional milliseconds
+ start = m.start(1) + 1;
+ end = m.end(1);
+ int msecs = 0;
+ if (start >= end) { // no fractional
+ cal.set(Calendar.MILLISECOND, 0);
+ } else {
+ // first char is '.', but rest....
+ msecs = 0;
+ final int fractLen = end-start;
+ switch (fractLen) {
+ default: // [databind#1745] Allow longer fractions... for now, cap at nanoseconds tho
+
+ if (fractLen > 9) { // only allow up to nanos
+ throw new ParseException(String.format(
+"Cannot parse date \"%s\": invalid fractional seconds '%s'; can use at most 9 digits",
+ dateStr, m.group(1).substring(1)
+ ), start);
+ }
+ // fall through
+ case 3:
+ msecs += (dateStr.charAt(start+2) - '0');
+ case 2:
+ msecs += 10 * (dateStr.charAt(start+1) - '0');
+ case 1:
+ msecs += 100 * (dateStr.charAt(start) - '0');
+ break;
+ case 0:
+ break;
+ }
+ cal.set(Calendar.MILLISECOND, msecs);
+ }
+ return cal.getTime();
}
+ formatStr = DATE_FORMAT_STR_ISO8601;
}
- Date dt = df.parse(dateStr, pos);
- // 22-Dec-2015, tatu: With non-lenient, may get null
- if (dt == null) {
- throw new ParseException
- (String.format("Can not parse date \"%s\": while it seems to fit format '%s', parsing fails (leniency? %s)",
- dateStr, formatStr, _lenient),
- pos.getErrorIndex());
- }
- return dt;
+
+ throw new ParseException
+ (String.format("Cannot parse date \"%s\": while it seems to fit format '%s', parsing fails (leniency? %s)",
+ dateStr, formatStr, _lenient),
+ // [databind#1742]: Might be able to give actual location, some day, but for now
+ // we can't give anything more indicative
+ 0);
+ }
+
+ private static int _parse4D(String str, int index) {
+ return (1000 * (str.charAt(index) - '0'))
+ + (100 * (str.charAt(index+1) - '0'))
+ + (10 * (str.charAt(index+2) - '0'))
+ + (str.charAt(index+3) - '0');
+ }
+
+ private static int _parse2D(String str, int index) {
+ return (10 * (str.charAt(index) - '0'))
+ + (str.charAt(index+1) - '0');
}
protected Date parseAsRFC1123(String dateStr, ParsePosition pos)
@@ -562,20 +754,11 @@
return _formatRFC1123.parse(dateStr, pos);
}
- private final static boolean hasTimeZone(String str)
- {
- // Only accept "+hh", "+hhmm" and "+hh:mm" (and with minus), so
- int len = str.length();
- if (len >= 6) {
- char c = str.charAt(len-6);
- if (c == '+' || c == '-') return true;
- c = str.charAt(len-5);
- if (c == '+' || c == '-') return true;
- c = str.charAt(len-3);
- if (c == '+' || c == '-') return true;
- }
- return false;
- }
+ /*
+ /**********************************************************
+ /* Helper methods, other
+ /**********************************************************
+ */
private final static DateFormat _cloneFormat(DateFormat df, String format,
TimeZone tz, Locale loc, Boolean lenient)
@@ -597,11 +780,24 @@
protected void _clearFormats() {
_formatRFC1123 = null;
- _formatISO8601 = null;
- _formatISO8601_z = null;
- _formatISO8601_noTz = null;
+ }
- _formatPlain = null;
+ protected Calendar _getCalendar(TimeZone tz) {
+ Calendar cal = _calendar;
+ if (cal == null ) {
+ _calendar = cal = (Calendar)CALENDAR.clone();
+ }
+ if (!cal.getTimeZone().equals(tz) ) {
+ cal.setTimeZone(tz);
+ }
+ cal.setLenient(isLenient());
+ return cal;
+ }
+
+ protected static <T> boolean _equals(T value1, T value2) {
+ if (value1 == value2) {
+ return true;
+ }
+ return (value1 != null) && value1.equals(value2);
}
}
-
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 1f05c32..f31334e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java
@@ -7,7 +7,6 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
-import com.fasterxml.jackson.core.json.JsonReadContext;
import com.fasterxml.jackson.core.json.JsonWriteContext;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.databind.*;
@@ -41,11 +40,19 @@
/**
* Object codec to use for stream-based object
* conversion through parser/generator interfaces. If null,
- * such methods can not be used.
+ * such methods cannot be used.
*/
protected ObjectCodec _objectCodec;
/**
+ * Parse context from "parent" parser (one from which content to buffer is read,
+ * if specified). Used, if available, when reading content, to present full
+ * context as if content was read from the original parser: this is useful
+ * in error reporting and sometimes processing as well.
+ */
+ protected JsonStreamContext _parentContext;
+
+ /**
* Bit flag composed of bits that indicate which
* {@link com.fasterxml.jackson.core.JsonGenerator.Feature}s
* are enabled.
@@ -135,19 +142,7 @@
/**
* @param codec Object codec to use for stream-based object
* conversion through parser/generator interfaces. If null,
- * such methods can not be used.
- *
- * @deprecated since 2.3 preferred variant is one that takes {@link JsonParser} or additional boolean parameter.
- */
- @Deprecated
- public TokenBuffer(ObjectCodec codec) {
- this(codec, false);
- }
-
- /**
- * @param codec Object codec to use for stream-based object
- * conversion through parser/generator interfaces. If null,
- * such methods can not be used.
+ * such methods cannot be used.
* @param hasNativeIds Whether resulting {@link JsonParser} (if created)
* is considered to support native type and object ids
*/
@@ -178,6 +173,7 @@
public TokenBuffer(JsonParser p, DeserializationContext ctxt)
{
_objectCodec = p.getCodec();
+ _parentContext = p.getParsingContext();
_generatorFeatures = DEFAULT_GENERATOR_FEATURES;
_writeContext = JsonWriteContext.createRootContext(null);
// at first we have just one segment
@@ -191,6 +187,35 @@
}
/**
+ * Convenience method, equivalent to:
+ *<pre>
+ * TokenBuffer b = new TokenBuffer(p);
+ * b.copyCurrentStructure(p);
+ * return b;
+ *</pre>
+ *
+ * @since 2.9
+ */
+ public static TokenBuffer asCopyOfValue(JsonParser p) throws IOException {
+ TokenBuffer b = new TokenBuffer(p);
+ b.copyCurrentStructure(p);
+ return b;
+ }
+
+ /**
+ * Method that allows explicitly specifying parent parse context to associate
+ * with contents of this buffer. Usually context is assigned at construction,
+ * based on given parser; but it is not always available, and may not contain
+ * intended context.
+ *
+ * @since 2.9
+ */
+ public TokenBuffer overrideParentContext(JsonStreamContext ctxt) {
+ _parentContext = ctxt;
+ return this;
+ }
+
+ /**
* @since 2.7
*/
public TokenBuffer forceUseOfBigDecimal(boolean b) {
@@ -213,12 +238,27 @@
*
* @return Parser that can be used for reading contents stored in this buffer
*/
- public JsonParser asParser()
- {
+ public JsonParser asParser() {
return asParser(_objectCodec);
}
/**
+ * Same as:
+ *<pre>
+ * JsonParser p = asParser();
+ * p.nextToken();
+ * return p;
+ *</pre>
+ *
+ * @since 2.9
+ */
+ public JsonParser asParserOnFirstToken() throws IOException {
+ JsonParser p = asParser(_objectCodec);
+ p.nextToken();
+ return p;
+ }
+
+ /**
* Method used to create a {@link JsonParser} that can read contents
* stored in this buffer.
*<p>
@@ -227,13 +267,13 @@
*
* @param codec Object codec to use for stream-based object
* conversion through parser/generator interfaces. If null,
- * such methods can not be used.
+ * such methods cannot be used.
*
* @return Parser that can be used for reading contents stored in this buffer
*/
public JsonParser asParser(ObjectCodec codec)
{
- return new Parser(_first, codec, _hasNativeTypeIds, _hasNativeObjectIds);
+ return new Parser(_first, codec, _hasNativeTypeIds, _hasNativeObjectIds, _parentContext);
}
/**
@@ -242,11 +282,11 @@
*/
public JsonParser asParser(JsonParser src)
{
- Parser p = new Parser(_first, src.getCodec(), _hasNativeTypeIds, _hasNativeObjectIds);
+ Parser p = new Parser(_first, src.getCodec(), _hasNativeTypeIds, _hasNativeObjectIds, _parentContext);
p.setLocation(src.getTokenLocation());
return p;
}
-
+
/*
/**********************************************************
/* Additional accessors
@@ -254,12 +294,10 @@
*/
public JsonToken firstToken() {
- if (_first != null) {
- return _first.type(0);
- }
- return null;
+ // no need to null check; never create without `_first`
+ return _first.type(0);
}
-
+
/*
/**********************************************************
/* Other custom methods not needed for implementing interfaces
@@ -291,16 +329,16 @@
}
return this;
}
-
+
/**
* Helper method that will write all contents of this buffer
* using given {@link JsonGenerator}.
*<p>
* Note: this method would be enough to implement
* <code>JsonSerializer</code> for <code>TokenBuffer</code> type;
- * but we can not have upwards
+ * but we cannot have upwards
* references (from core to mapper package); and as such we also
- * can not take second argument.
+ * cannot take second argument.
*/
public void serialize(JsonGenerator gen) throws IOException
{
@@ -397,7 +435,7 @@
gen.writeNumber((String) n);
} else {
throw new JsonGenerationException(String.format(
- "Unrecognized value type for VALUE_NUMBER_FLOAT: %s, can not serialize",
+ "Unrecognized value type for VALUE_NUMBER_FLOAT: %s, cannot serialize",
n.getClass().getName()), gen);
}
}
@@ -453,7 +491,7 @@
copyCurrentStructure(p);
} while ((t = p.nextToken()) == JsonToken.FIELD_NAME);
if (t != JsonToken.END_OBJECT) {
- ctxt.reportWrongTokenException(p, JsonToken.END_OBJECT,
+ ctxt.reportWrongTokenException(TokenBuffer.class, JsonToken.END_OBJECT,
"Expected END_OBJECT after copying contents of a JsonParser into TokenBuffer, got "+t);
// never gets here
}
@@ -909,7 +947,7 @@
/**
* Although we could support this method, it does not necessarily make
- * sense: we can not make good use of streaming because buffer must
+ * sense: we cannot make good use of streaming because buffer must
* hold all the data. Because of this, currently this will simply
* throw {@link UnsupportedOperationException}
*/
@@ -1157,7 +1195,9 @@
_appendAt = 1;
}
}
-
+
+ // 21-Oct-2016, tatu: Does not seem to be used or needed
+ /*
protected final void _appendRaw(int rawType, Object value)
{
Segment next = _hasNativeId
@@ -1170,6 +1210,7 @@
_appendAt = 1;
}
}
+ */
@Override
protected void _reportUnsupportedOperation() {
@@ -1225,7 +1266,7 @@
* Information about parser context, context in which
* the next token is to be parsed (root, array, object).
*/
- protected JsonReadContext _parsingContext;
+ protected TokenBufferReadContext _parsingContext;
protected boolean _closed;
@@ -1239,15 +1280,22 @@
/**********************************************************
*/
+ @Deprecated // since 2.9
public Parser(Segment firstSeg, ObjectCodec codec,
- boolean hasNativeTypeIds,
- boolean hasNativeObjectIds)
+ boolean hasNativeTypeIds, boolean hasNativeObjectIds)
+ {
+ this(firstSeg, codec, hasNativeTypeIds, hasNativeObjectIds, null);
+ }
+
+ public Parser(Segment firstSeg, ObjectCodec codec,
+ boolean hasNativeTypeIds, boolean hasNativeObjectIds,
+ JsonStreamContext parentContext)
{
super(0);
_segment = firstSeg;
_segmentPtr = -1; // not yet read
_codec = codec;
- _parsingContext = JsonReadContext.createRootContext(null);
+ _parsingContext = TokenBufferReadContext.createRootContext(parentContext);
_hasNativeTypeIds = hasNativeTypeIds;
_hasNativeObjectIds = hasNativeObjectIds;
_hasNativeIds = (hasNativeTypeIds | hasNativeObjectIds);
@@ -1327,17 +1375,13 @@
String name = (ob instanceof String) ? ((String) ob) : ob.toString();
_parsingContext.setCurrentName(name);
} else if (_currToken == JsonToken.START_OBJECT) {
- _parsingContext = _parsingContext.createChildObjectContext(-1, -1);
+ _parsingContext = _parsingContext.createChildObjectContext();
} else if (_currToken == JsonToken.START_ARRAY) {
- _parsingContext = _parsingContext.createChildArrayContext(-1, -1);
+ _parsingContext = _parsingContext.createChildArrayContext();
} else if (_currToken == JsonToken.END_OBJECT
|| _currToken == JsonToken.END_ARRAY) {
// Closing JSON Object/Array? Close matching context
- _parsingContext = _parsingContext.getParent();
- // but allow unbalanced cases too (more close markers)
- if (_parsingContext == null) {
- _parsingContext = JsonReadContext.createRootContext(null);
- }
+ _parsingContext = _parsingContext.parentOrCopy();
}
return _currToken;
}
@@ -1346,11 +1390,14 @@
public String nextFieldName() throws IOException
{
// inlined common case from nextToken()
- if (_closed || (_segment == null)) return null;
+ if (_closed || (_segment == null)) {
+ return null;
+ }
int ptr = _segmentPtr+1;
- if (ptr < Segment.TOKENS_PER_SEGMENT && _segment.type(ptr) == JsonToken.FIELD_NAME) {
+ if ((ptr < Segment.TOKENS_PER_SEGMENT) && (_segment.type(ptr) == JsonToken.FIELD_NAME)) {
_segmentPtr = ptr;
+ _currToken = JsonToken.FIELD_NAME;
Object ob = _segment.get(ptr); // inlined _currentObject();
String name = (ob instanceof String) ? ((String) ob) : ob.toString();
_parsingContext.setCurrentName(name);
@@ -1383,7 +1430,7 @@
public String getCurrentName() {
// 25-Jun-2015, tatu: as per [databind#838], needs to be same as ParserBase
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
- JsonReadContext parent = _parsingContext.getParent();
+ JsonStreamContext parent = _parsingContext.getParent();
return parent.getCurrentName();
}
return _parsingContext.getCurrentName();
@@ -1393,14 +1440,16 @@
public void overrideCurrentName(String name)
{
// Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
- JsonReadContext ctxt = _parsingContext;
+ JsonStreamContext ctxt = _parsingContext;
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
ctxt = ctxt.getParent();
}
- try {
- ctxt.setCurrentName(name);
- } catch (IOException e) {
- throw new RuntimeException(e);
+ if (ctxt instanceof TokenBufferReadContext) {
+ try {
+ ((TokenBufferReadContext) ctxt).setCurrentName(name);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
}
@@ -1420,7 +1469,7 @@
if (ob instanceof String) {
return (String) ob;
}
- return (ob == null) ? null : ob.toString();
+ return ClassUtil.nullOrToString(ob);
}
if (_currToken == null) {
return null;
@@ -1428,8 +1477,7 @@
switch (_currToken) {
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
- Object ob = _currentObject();
- return (ob == null) ? null : ob.toString();
+ return ClassUtil.nullOrToString(_currentObject());
default:
return _currToken.asString();
}
@@ -1455,7 +1503,7 @@
// We never have raw buffer available, so:
return false;
}
-
+
/*
/**********************************************************
/* Public API, access to token information, numeric
@@ -1463,6 +1511,23 @@
*/
@Override
+ public boolean isNaN() {
+ // can only occur for floating-point numbers
+ if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
+ Object value = _currentObject();
+ if (value instanceof Double) {
+ Double v = (Double) value;
+ return v.isNaN() || v.isInfinite();
+ }
+ if (value instanceof Float) {
+ Float v = (Float) value;
+ return v.isNaN() || v.isInfinite();
+ }
+ }
+ return false;
+ }
+
+ @Override
public BigInteger getBigIntegerValue() throws IOException
{
Number n = getNumberValue();
@@ -1508,16 +1573,22 @@
@Override
public int getIntValue() throws IOException
{
- // optimize common case:
- if (_currToken == JsonToken.VALUE_NUMBER_INT) {
- return ((Number) _currentObject()).intValue();
+ Number n = (_currToken == JsonToken.VALUE_NUMBER_INT) ?
+ ((Number) _currentObject()) : getNumberValue();
+ if ((n instanceof Integer) || _smallerThanInt(n)) {
+ return n.intValue();
}
- return getNumberValue().intValue();
+ return _convertNumberToInt(n);
}
@Override
public long getLongValue() throws IOException {
- return getNumberValue().longValue();
+ Number n = (_currToken == JsonToken.VALUE_NUMBER_INT) ?
+ ((Number) _currentObject()) : getNumberValue();
+ if ((n instanceof Long) || _smallerThanLong(n)) {
+ return n.longValue();
+ }
+ return _convertNumberToLong(n);
}
@Override
@@ -1542,7 +1613,7 @@
return (Number) value;
}
// Difficult to really support numbers-as-Strings; but let's try.
- // NOTE: no access to DeserializationConfig, unfortunately, so can not
+ // NOTE: no access to DeserializationConfig, unfortunately, so cannot
// try to determine Double/BigDecimal preference...
if (value instanceof String) {
String str = (String) value;
@@ -1558,6 +1629,78 @@
+value.getClass().getName());
}
+ private final boolean _smallerThanInt(Number n) {
+ return (n instanceof Short) || (n instanceof Byte);
+ }
+
+ private final boolean _smallerThanLong(Number n) {
+ return (n instanceof Integer) || (n instanceof Short) || (n instanceof Byte);
+ }
+
+ // 02-Jan-2017, tatu: Modified from method(s) in `ParserBase`
+
+ protected int _convertNumberToInt(Number n) throws IOException
+ {
+ if (n instanceof Long) {
+ long l = n.longValue();
+ int result = (int) l;
+ if (((long) result) != l) {
+ reportOverflowInt();
+ }
+ return result;
+ }
+ if (n instanceof BigInteger) {
+ BigInteger big = (BigInteger) n;
+ if (BI_MIN_INT.compareTo(big) > 0
+ || BI_MAX_INT.compareTo(big) < 0) {
+ reportOverflowInt();
+ }
+ } else if ((n instanceof Double) || (n instanceof Float)) {
+ double d = n.doubleValue();
+ // Need to check boundaries
+ if (d < MIN_INT_D || d > MAX_INT_D) {
+ reportOverflowInt();
+ }
+ return (int) d;
+ } else if (n instanceof BigDecimal) {
+ BigDecimal big = (BigDecimal) n;
+ if (BD_MIN_INT.compareTo(big) > 0
+ || BD_MAX_INT.compareTo(big) < 0) {
+ reportOverflowInt();
+ }
+ } else {
+ _throwInternal();
+ }
+ return n.intValue();
+ }
+
+ protected long _convertNumberToLong(Number n) throws IOException
+ {
+ if (n instanceof BigInteger) {
+ BigInteger big = (BigInteger) n;
+ if (BI_MIN_LONG.compareTo(big) > 0
+ || BI_MAX_LONG.compareTo(big) < 0) {
+ reportOverflowLong();
+ }
+ } else if ((n instanceof Double) || (n instanceof Float)) {
+ double d = n.doubleValue();
+ // Need to check boundaries
+ if (d < MIN_LONG_D || d > MAX_LONG_D) {
+ reportOverflowLong();
+ }
+ return (long) d;
+ } else if (n instanceof BigDecimal) {
+ BigDecimal big = (BigDecimal) n;
+ if (BD_MIN_LONG.compareTo(big) > 0
+ || BD_MAX_LONG.compareTo(big) < 0) {
+ reportOverflowLong();
+ }
+ } else {
+ _throwInternal();
+ }
+ return n.longValue();
+ }
+
/*
/**********************************************************
/* Public API, access to token information, other
@@ -1587,7 +1730,7 @@
// fall through to error case
}
if (_currToken != JsonToken.VALUE_STRING) {
- throw _constructError("Current token ("+_currToken+") not VALUE_STRING (or VALUE_EMBEDDED_OBJECT with byte[]), can not access as binary");
+ throw _constructError("Current token ("+_currToken+") not VALUE_STRING (or VALUE_EMBEDDED_OBJECT with byte[]), cannot access as binary");
}
final String str = getText();
if (str == null) {
@@ -1653,7 +1796,7 @@
protected final void _checkIsNumber() throws JsonParseException
{
if (_currToken == null || !_currToken.isNumeric()) {
- throw _constructError("Current token ("+_currToken+") not numeric, can not use numeric value accessors");
+ throw _constructError("Current token ("+_currToken+") not numeric, cannot use numeric value accessors");
}
}
@@ -1795,6 +1938,7 @@
return _next;
}
+ /*
public Segment appendRaw(int index, int rawTokenType, Object value)
{
if (index < TOKENS_PER_SEGMENT) {
@@ -1818,6 +1962,28 @@
return _next;
}
+ private void set(int index, int rawTokenType, Object value, Object objectId, Object typeId)
+ {
+ _tokens[index] = value;
+ long typeCode = (long) rawTokenType;
+ if (index > 0) {
+ typeCode <<= (index << 2);
+ }
+ _tokenTypes |= typeCode;
+ assignNativeIds(index, objectId, typeId);
+ }
+
+ private void set(int index, int rawTokenType, Object value)
+ {
+ _tokens[index] = value;
+ long typeCode = (long) rawTokenType;
+ if (index > 0) {
+ typeCode <<= (index << 2);
+ }
+ _tokenTypes |= typeCode;
+ }
+ */
+
private void set(int index, JsonToken tokenType)
{
/* Assumption here is that there are no overwrites, just appends;
@@ -1863,27 +2029,6 @@
assignNativeIds(index, objectId, typeId);
}
- private void set(int index, int rawTokenType, Object value)
- {
- _tokens[index] = value;
- long typeCode = (long) rawTokenType;
- if (index > 0) {
- typeCode <<= (index << 2);
- }
- _tokenTypes |= typeCode;
- }
-
- private void set(int index, int rawTokenType, Object value, Object objectId, Object typeId)
- {
- _tokens[index] = value;
- long typeCode = (long) rawTokenType;
- if (index > 0) {
- typeCode <<= (index << 2);
- }
- _tokenTypes |= typeCode;
- assignNativeIds(index, objectId, typeId);
- }
-
private final void assignNativeIds(int index, Object objectId, Object typeId)
{
if (_nativeIds == null) {
@@ -1900,14 +2045,14 @@
/**
* @since 2.3
*/
- public Object findObjectId(int index) {
+ private Object findObjectId(int index) {
return (_nativeIds == null) ? null : _nativeIds.get(_objectIdIndex(index));
}
/**
* @since 2.3
*/
- public Object findTypeId(int index) {
+ private Object findTypeId(int index) {
return (_nativeIds == null) ? null : _nativeIds.get(_typeIdIndex(index));
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/TokenBufferReadContext.java b/src/main/java/com/fasterxml/jackson/databind/util/TokenBufferReadContext.java
new file mode 100644
index 0000000..6652036
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/util/TokenBufferReadContext.java
@@ -0,0 +1,135 @@
+package com.fasterxml.jackson.databind.util;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.json.JsonReadContext;
+
+/**
+ * Implementation of {@link JsonStreamContext} used by {@link TokenBuffer}
+ * to link back to the original context to try to keep location information
+ * consistent between source location and buffered content when it's re-read
+ * from the buffer.
+ *
+ * @since 2.9
+ */
+public class TokenBufferReadContext extends JsonStreamContext
+{
+ protected final JsonStreamContext _parent;
+
+ protected final JsonLocation _startLocation;
+
+ // Benefit for reusing?
+// protected JsonReadContext _child;
+
+ /*
+ /**********************************************************
+ /* Location/state information (minus source reference)
+ /**********************************************************
+ */
+
+ protected String _currentName;
+
+ protected Object _currentValue;
+
+ protected TokenBufferReadContext(JsonStreamContext base, Object srcRef) {
+ super(base);
+ _parent = base.getParent();
+ _currentName = base.getCurrentName();
+ _currentValue = base.getCurrentValue();
+ if (base instanceof JsonReadContext) {
+ JsonReadContext rc = (JsonReadContext) base;
+ _startLocation = rc.getStartLocation(srcRef);
+ } else {
+ _startLocation = JsonLocation.NA;
+ }
+ }
+
+ protected TokenBufferReadContext(JsonStreamContext base, JsonLocation startLoc) {
+ super(base);
+ _parent = base.getParent();
+ _currentName = base.getCurrentName();
+ _currentValue = base.getCurrentValue();
+ _startLocation = startLoc;
+ }
+
+ /**
+ * Constructor for case where there is no real surrounding context: just create
+ * virtual ROOT
+ */
+ protected TokenBufferReadContext() {
+ super(TYPE_ROOT, -1);
+ _parent = null;
+ _startLocation = JsonLocation.NA;
+ }
+
+ protected TokenBufferReadContext(TokenBufferReadContext parent, int type, int index) {
+ super(type, index);
+ _parent = parent;
+ _startLocation = parent._startLocation;
+ }
+
+ @Override
+ public Object getCurrentValue() {
+ return _currentValue;
+ }
+
+ @Override
+ public void setCurrentValue(Object v) {
+ _currentValue = v;
+ }
+
+ /*
+ /**********************************************************
+ /* Factory methods
+ /**********************************************************
+ */
+
+ public static TokenBufferReadContext createRootContext(JsonStreamContext origContext) {
+ // First: possible to have no current context; if so, just create bogus ROOT context
+ if (origContext == null) {
+ return new TokenBufferReadContext();
+ }
+ return new TokenBufferReadContext(origContext, null);
+ }
+
+ public TokenBufferReadContext createChildArrayContext() {
+ return new TokenBufferReadContext(this, TYPE_ARRAY, -1);
+ }
+
+ public TokenBufferReadContext createChildObjectContext() {
+ return new TokenBufferReadContext(this, TYPE_OBJECT, -1);
+ }
+
+ /**
+ * Helper method we need to handle discontinuity between "real" contexts buffer
+ * creates, and ones from parent: problem being they are of different types.
+ */
+ public TokenBufferReadContext parentOrCopy() {
+ // 30-Apr-2017, tatu: This is bit awkward since part on ancestor stack is of different
+ // type (usually `JsonReadContext`)... and so for unbalanced buffers (with extra
+ // END_OBJECT / END_ARRAY), we may need to create
+ if (_parent instanceof TokenBufferReadContext) {
+ return (TokenBufferReadContext) _parent;
+ }
+ if (_parent == null) { // unlikely, but just in case let's support
+ return new TokenBufferReadContext();
+ }
+ return new TokenBufferReadContext(_parent, _startLocation);
+ }
+
+ /*
+ /**********************************************************
+ /* Abstract method implementation
+ /**********************************************************
+ */
+
+ @Override public String getCurrentName() { return _currentName; }
+
+ // @since 2.9
+ @Override public boolean hasCurrentName() { return _currentName != null; }
+
+ @Override public JsonStreamContext getParent() { return _parent; }
+
+ public void setCurrentName(String name) throws JsonProcessingException {
+ _currentName = name;
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java
index 311ffad..4771d79 100644
--- a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java
@@ -6,11 +6,9 @@
import static org.junit.Assert.*;
import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-import com.fasterxml.jackson.core.FormatSchema;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
+
+import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
@@ -41,10 +39,8 @@
protected static class BooleanWrapper {
public Boolean b;
- @JsonCreator
+ public BooleanWrapper() { }
public BooleanWrapper(Boolean value) { b = value; }
-
- @JsonValue public Boolean value() { return b; }
}
protected static class IntWrapper {
@@ -109,9 +105,14 @@
{
public Map<K,V> map;
+ public MapWrapper() { }
public MapWrapper(Map<K,V> m) {
map = m;
}
+ public MapWrapper(K key, V value) {
+ map = new LinkedHashMap<>();
+ map.put(key, value);
+ }
}
protected static class ArrayWrapper<T>
@@ -209,11 +210,11 @@
protected ObjectMapper objectMapper() {
if (SHARED_MAPPER == null) {
- SHARED_MAPPER = new ObjectMapper();
+ SHARED_MAPPER = newObjectMapper();
}
return SHARED_MAPPER;
}
-
+
protected ObjectWriter objectWriter() {
return objectMapper().writer();
}
@@ -226,6 +227,11 @@
return objectMapper().readerFor(cls);
}
+ // @since 2.9
+ protected static ObjectMapper newObjectMapper() {
+ return new ObjectMapper();
+ }
+
// @since 2.7
protected TypeFactory newTypeFactory() {
// this is a work-around; no null modifier added
@@ -243,6 +249,11 @@
assertArrayEquals(exp, act);
}
+ protected void assertEquals(byte[] exp, byte[] act)
+ {
+ assertArrayEquals(exp, act);
+ }
+
/**
* Helper method for verifying 3 basic cookie cutter cases;
* identity comparison (true), and against null (false),
diff --git a/src/test/java/com/fasterxml/jackson/databind/BaseTest.java b/src/test/java/com/fasterxml/jackson/databind/BaseTest.java
index ef03fda..e4d664b 100644
--- a/src/test/java/com/fasterxml/jackson/databind/BaseTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/BaseTest.java
@@ -59,28 +59,28 @@
public static class Name
{
- private String _first, _last;
+ private String _first, _last;
- public Name() { }
- public Name(String f, String l) {
- _first = f;
- _last = l;
- }
-
- public String getFirst() { return _first; }
- public String getLast() { return _last; }
+ public Name() { }
+ public Name(String f, String l) {
+ _first = f;
+ _last = l;
+ }
- public void setFirst(String s) { _first = s; }
- public void setLast(String s) { _last = s; }
+ public String getFirst() { return _first; }
+ public String getLast() { return _last; }
- @Override
- public boolean equals(Object o)
- {
- if (o == this) return true;
- if (o == null || o.getClass() != getClass()) return false;
- Name other = (Name) o;
- return _first.equals(other._first) && _last.equals(other._last);
- }
+ public void setFirst(String s) { _first = s; }
+ public void setLast(String s) { _last = s; }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == this) return true;
+ if (o == null || o.getClass() != getClass()) return false;
+ Name other = (Name) o;
+ return _first.equals(other._first) && _last.equals(other._last);
+ }
}
private Gender _gender;
@@ -267,16 +267,6 @@
assertEquals(String.valueOf(expValue), jp.getText());
}
- /**
- * Method that checks whether Unit tests appear to run from Ant build
- * scripts.
- *
- * @since 1.6
- */
- protected static boolean runsFromAnt() {
- return "true".equals(System.getProperty("FROM_ANT"));
- }
-
/*
/**********************************************************
/* Parser/generator construction
diff --git a/src/test/java/com/fasterxml/jackson/databind/FullStreamReadTest.java b/src/test/java/com/fasterxml/jackson/databind/FullStreamReadTest.java
new file mode 100644
index 0000000..6bfa5cc
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/FullStreamReadTest.java
@@ -0,0 +1,175 @@
+package com.fasterxml.jackson.databind;
+
+import java.util.*;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+public class FullStreamReadTest extends BaseMapTest
+{
+ private final static String JSON_OK_ARRAY = " [ 1, 2, 3] ";
+ private final static String JSON_OK_ARRAY_WITH_COMMENT = JSON_OK_ARRAY + " // stuff ";
+
+ private final static String JSON_FAIL_ARRAY = JSON_OK_ARRAY + " [ ]";
+
+ /*
+ /**********************************************************
+ /* Test methods, config
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testMapperAcceptTrailing() throws Exception
+ {
+ assertFalse(MAPPER.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS));
+
+ // by default, should be ok to read, all
+ _verifyArray(MAPPER.readTree(JSON_OK_ARRAY));
+ _verifyArray(MAPPER.readTree(JSON_OK_ARRAY_WITH_COMMENT));
+ _verifyArray(MAPPER.readTree(JSON_FAIL_ARRAY));
+
+ // and also via "untyped"
+ _verifyCollection(MAPPER.readValue(JSON_OK_ARRAY, List.class));
+ _verifyCollection(MAPPER.readValue(JSON_OK_ARRAY_WITH_COMMENT, List.class));
+ _verifyCollection(MAPPER.readValue(JSON_FAIL_ARRAY, List.class));
+ }
+
+ public void testMapperFailOnTrailing() throws Exception
+ {
+ // but things change if we enforce checks
+ ObjectMapper strict = newObjectMapper()
+ .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
+ assertTrue(strict.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS));
+
+ // some still ok
+ _verifyArray(strict.readTree(JSON_OK_ARRAY));
+ _verifyCollection(strict.readValue(JSON_OK_ARRAY, List.class));
+
+ // but if real content exists, will fail
+ try {
+ strict.readTree(JSON_FAIL_ARRAY);
+ fail("Should not have passed");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Trailing token (of type START_ARRAY)");
+ verifyException(e, "value (bound as `com.fasterxml.jackson.databind.JsonNode`)");
+ }
+
+ try {
+ strict.readValue(JSON_FAIL_ARRAY, List.class);
+ fail("Should not have passed");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Trailing token (of type START_ARRAY)");
+ verifyException(e, "value (bound as `java.util.List`)");
+ }
+
+ // others fail conditionally: will fail on comments unless enabled
+
+ try {
+ strict.readValue(JSON_OK_ARRAY_WITH_COMMENT, List.class);
+ fail("Should not have passed");
+ } catch (JsonParseException e) {
+ verifyException(e, "Unexpected character");
+ verifyException(e, "maybe a (non-standard) comment");
+ }
+ try {
+ strict.readTree(JSON_OK_ARRAY_WITH_COMMENT);
+ fail("Should not have passed");
+ } catch (JsonParseException e) {
+ verifyException(e, "Unexpected character");
+ verifyException(e, "maybe a (non-standard) comment");
+ }
+
+ ObjectMapper strictWithComments = strict.copy();
+ strictWithComments.enable(JsonParser.Feature.ALLOW_COMMENTS);
+ _verifyArray(strictWithComments.readTree(JSON_OK_ARRAY_WITH_COMMENT));
+ _verifyCollection(strictWithComments.readValue(JSON_OK_ARRAY_WITH_COMMENT, List.class));
+ }
+
+ public void testReaderAcceptTrailing() throws Exception
+ {
+ ObjectReader R = MAPPER.reader();
+ assertFalse(R.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS));
+
+ _verifyArray(R.readTree(JSON_OK_ARRAY));
+ _verifyArray(R.readTree(JSON_OK_ARRAY_WITH_COMMENT));
+ _verifyArray(R.readTree(JSON_FAIL_ARRAY));
+ ObjectReader rColl = R.forType(List.class);
+ _verifyCollection((List<?>)rColl.readValue(JSON_OK_ARRAY));
+ _verifyCollection((List<?>)rColl.readValue(JSON_OK_ARRAY_WITH_COMMENT));
+ _verifyCollection((List<?>)rColl.readValue(JSON_FAIL_ARRAY));
+ }
+
+ public void testReaderFailOnTrailing() throws Exception
+ {
+ ObjectReader strictR = MAPPER.reader().with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
+ ObjectReader strictRForList = strictR.forType(List.class);
+ _verifyArray(strictR.readTree(JSON_OK_ARRAY));
+ _verifyCollection((List<?>)strictRForList.readValue(JSON_OK_ARRAY));
+
+ // Will fail hard if there is a trailing token
+ try {
+ strictRForList.readValue(JSON_FAIL_ARRAY);
+ fail("Should not have passed");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Trailing token (of type START_ARRAY)");
+ verifyException(e, "value (bound as `java.util.List`)");
+ }
+ try {
+ strictR.readTree(JSON_FAIL_ARRAY);
+ fail("Should not have passed");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Trailing token (of type START_ARRAY)");
+ verifyException(e, "value (bound as `com.fasterxml.jackson.databind.JsonNode`)");
+ }
+
+ // ... also verify that same happens with "value to update"
+ try {
+ strictR.withValueToUpdate(new ArrayList<Object>())
+ .readValue(JSON_FAIL_ARRAY);
+ fail("Should not have passed");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Trailing token (of type START_ARRAY)");
+ verifyException(e, "value (bound as `java.util.ArrayList`)");
+ }
+
+ // others conditionally: will fail on comments unless enabled
+
+ try {
+ strictRForList.readValue(JSON_OK_ARRAY_WITH_COMMENT);
+ fail("Should not have passed");
+ } catch (JsonParseException e) {
+ verifyException(e, "Unexpected character");
+ verifyException(e, "maybe a (non-standard) comment");
+ }
+ try {
+ strictR.readTree(JSON_OK_ARRAY_WITH_COMMENT);
+ fail("Should not have passed");
+ } catch (JsonParseException e) {
+ verifyException(e, "Unexpected character");
+ verifyException(e, "maybe a (non-standard) comment");
+ }
+
+ // but works if comments enabled etc
+
+ ObjectReader strictRWithComments = strictR.with(JsonParser.Feature.ALLOW_COMMENTS);
+
+ _verifyCollection((List<?>)strictRWithComments.forType(List.class).readValue(JSON_OK_ARRAY_WITH_COMMENT));
+ _verifyArray(strictRWithComments.readTree(JSON_OK_ARRAY_WITH_COMMENT));
+ }
+
+ private void _verifyArray(JsonNode n) throws Exception
+ {
+ assertTrue(n.isArray());
+ assertEquals(3, n.size());
+ }
+
+ private void _verifyCollection(List<?> coll) throws Exception
+ {
+ assertEquals(3, coll.size());
+ assertEquals(Integer.valueOf(1), coll.get(0));
+ assertEquals(Integer.valueOf(2), coll.get(1));
+ assertEquals(Integer.valueOf(3), coll.get(2));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/TestParserUsingMapper.java b/src/test/java/com/fasterxml/jackson/databind/MapperViaParserTest.java
similarity index 85%
rename from src/test/java/com/fasterxml/jackson/databind/TestParserUsingMapper.java
rename to src/test/java/com/fasterxml/jackson/databind/MapperViaParserTest.java
index 1b8f6b1..54400df 100644
--- a/src/test/java/com/fasterxml/jackson/databind/TestParserUsingMapper.java
+++ b/src/test/java/com/fasterxml/jackson/databind/MapperViaParserTest.java
@@ -8,7 +8,7 @@
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.ObjectMapper;
-public class TestParserUsingMapper extends BaseMapTest
+public class MapperViaParserTest extends BaseMapTest
{
final static int TWO_BYTE_ESCAPED = 0x111;
final static int THREE_BYTE_ESCAPED = 0x1111;
@@ -72,27 +72,6 @@
/********************************************************
*/
- public void testReadingArrayAsTree() throws IOException
- {
- JsonFactory jf = new MappingJsonFactory();
- final String JSON = "[ 1, 2, false ]";
-
- for (int i = 0; i < 2; ++i) {
- JsonParser jp = jf.createParser(new StringReader(JSON));
- // whether to try advancing first or not? Try both
- if (i == 0) {
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- }
- JsonNode root = (JsonNode) jp.readValueAsTree();
- jp.close();
- assertTrue(root.isArray());
- assertEquals(3, root.size());
- assertEquals(1, root.get(0).intValue());
- assertEquals(2, root.get(1).intValue());
- assertFalse(root.get(2).booleanValue());
- }
- }
-
public void testPojoReading() throws IOException
{
JsonFactory jf = new MappingJsonFactory();
diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java
index 02a5d76..f59eabc 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java
@@ -3,14 +3,16 @@
import java.io.*;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.core.*;
-
import com.fasterxml.jackson.core.util.MinimalPrettyPrinter;
-import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
+import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.fasterxml.jackson.databind.node.*;
-import com.fasterxml.jackson.databind.type.TypeFactory;
public class ObjectMapperTest extends BaseMapTest
{
@@ -18,19 +20,12 @@
int value = 3;
public void setX(int v) { value = v; }
+
+ protected Bean() { }
+ public Bean(int v) { value = v; }
}
static class EmptyBean { }
-
- // for [databind#206]
- @SuppressWarnings("serial")
- static class CustomMapper extends ObjectMapper {
- @Override
- protected DefaultDeserializationContext createDeserializationContext(JsonParser jp,
- DeserializationConfig cfg) {
- return super.createDeserializationContext(jp, cfg);
- }
- }
@SuppressWarnings("serial")
static class MyAnnotationIntrospector extends JacksonAnnotationIntrospector { }
@@ -48,15 +43,166 @@
g.writeRaw(" , ");
}
}
-
+
+ // for [databind#206]
+ @SuppressWarnings("serial")
+ static class NoCopyMapper extends ObjectMapper { }
+
+ final ObjectMapper MAPPER = new ObjectMapper();
+
/*
/**********************************************************
- /* Test methods
+ /* Test methods, config
/**********************************************************
*/
-
- final static ObjectMapper MAPPER = new ObjectMapper();
-
+
+ public void testFactorFeatures()
+ {
+ assertTrue(MAPPER.isEnabled(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES));
+ }
+
+ public void testGeneratorFeatures()
+ {
+ // and also for mapper
+ ObjectMapper mapper = new ObjectMapper();
+ assertFalse(mapper.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+ assertTrue(mapper.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
+ mapper.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM,
+ JsonGenerator.Feature.QUOTE_FIELD_NAMES);
+ }
+
+ public void testParserFeatures()
+ {
+ // and also for mapper
+ ObjectMapper mapper = new ObjectMapper();
+
+ assertTrue(mapper.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+ assertFalse(mapper.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+
+ mapper.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE,
+ JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
+ assertFalse(mapper.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, mapper.copy()
+ /**********************************************************
+ */
+
+ // [databind#28]: ObjectMapper.copy()
+ public void testCopy() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ assertTrue(m.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
+ m.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ assertFalse(m.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
+ InjectableValues inj = new InjectableValues.Std();
+ m.setInjectableValues(inj);
+ assertFalse(m.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+ m.enable(JsonParser.Feature.ALLOW_COMMENTS);
+ assertTrue(m.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+
+ // // First: verify that handling of features is decoupled:
+
+ ObjectMapper m2 = m.copy();
+ assertFalse(m2.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
+ m2.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ assertTrue(m2.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
+ assertSame(inj, m2.getInjectableValues());
+
+ // but should NOT change the original
+ assertFalse(m.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
+
+ // nor vice versa:
+ assertFalse(m.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
+ assertFalse(m2.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
+ m.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
+ assertTrue(m.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
+ assertFalse(m2.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
+
+ // // Also, underlying JsonFactory instances should be distinct
+
+ assertNotSame(m.getFactory(), m2.getFactory());
+
+ // [databind#122]: Need to ensure mix-ins are not shared
+ assertEquals(0, m.getSerializationConfig().mixInCount());
+ assertEquals(0, m2.getSerializationConfig().mixInCount());
+ assertEquals(0, m.getDeserializationConfig().mixInCount());
+ assertEquals(0, m2.getDeserializationConfig().mixInCount());
+
+ m.addMixIn(String.class, Integer.class);
+ assertEquals(1, m.getSerializationConfig().mixInCount());
+ assertEquals(0, m2.getSerializationConfig().mixInCount());
+ assertEquals(1, m.getDeserializationConfig().mixInCount());
+ assertEquals(0, m2.getDeserializationConfig().mixInCount());
+
+ // [databind#913]: Ensure JsonFactory Features copied
+ assertTrue(m2.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+ }
+
+ // [databind#1580]
+ public void testCopyOfConfigOverrides() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ SerializationConfig config = m.getSerializationConfig();
+ assertEquals(JsonInclude.Value.empty(), config.getDefaultPropertyInclusion());
+ assertEquals(JsonSetter.Value.empty(), config.getDefaultSetterInfo());
+ assertNull(config.getDefaultMergeable());
+ VisibilityChecker<?> defaultVis = config.getDefaultVisibilityChecker();
+ assertEquals(VisibilityChecker.Std.class, defaultVis.getClass());
+
+ // change
+ JsonInclude.Value customIncl = JsonInclude.Value.empty().withValueInclusion(JsonInclude.Include.NON_DEFAULT);
+ m.setDefaultPropertyInclusion(customIncl);
+ JsonSetter.Value customSetter = JsonSetter.Value.forValueNulls(Nulls.SKIP);
+ m.setDefaultSetterInfo(customSetter);
+ m.setDefaultMergeable(Boolean.TRUE);
+ VisibilityChecker<?> customVis = VisibilityChecker.Std.defaultInstance()
+ .withFieldVisibility(Visibility.ANY);
+ m.setVisibility(customVis);
+ assertSame(customVis, m.getVisibilityChecker());
+
+ // and verify that copy retains these settings
+ ObjectMapper m2 = m.copy();
+ SerializationConfig config2 = m2.getSerializationConfig();
+ assertSame(customIncl, config2.getDefaultPropertyInclusion());
+ assertSame(customSetter, config2.getDefaultSetterInfo());
+ assertEquals(Boolean.TRUE, config2.getDefaultMergeable());
+ assertSame(customVis, config2.getDefaultVisibilityChecker());
+ }
+
+ public void testFailedCopy() throws Exception
+ {
+ NoCopyMapper src = new NoCopyMapper();
+ try {
+ src.copy();
+ fail("Should not pass");
+ } catch (IllegalStateException e) {
+ verifyException(e, "does not override copy()");
+ }
+ }
+
+ public void testAnnotationIntrospectorCopyin()
+ {
+ ObjectMapper m = new ObjectMapper();
+ m.setAnnotationIntrospector(new MyAnnotationIntrospector());
+ assertEquals(MyAnnotationIntrospector.class,
+ m.getDeserializationConfig().getAnnotationIntrospector().getClass());
+ ObjectMapper m2 = m.copy();
+
+ assertEquals(MyAnnotationIntrospector.class,
+ m2.getDeserializationConfig().getAnnotationIntrospector().getClass());
+ assertEquals(MyAnnotationIntrospector.class,
+ m2.getSerializationConfig().getAnnotationIntrospector().getClass());
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, other
+ /**********************************************************
+ */
+
public void testProps()
{
ObjectMapper m = new ObjectMapper();
@@ -68,25 +214,6 @@
assertSame(nf, m.getNodeFactory());
}
- public void testSupport()
- {
- assertTrue(MAPPER.canSerialize(String.class));
- assertTrue(MAPPER.canDeserialize(TypeFactory.defaultInstance().constructType(String.class)));
- }
-
- public void testTreeRead() throws Exception
- {
- String JSON = "{ }";
- JsonNode n = MAPPER.readTree(JSON);
- assertTrue(n instanceof ObjectNode);
-
- n = MAPPER.readTree(new StringReader(JSON));
- assertTrue(n instanceof ObjectNode);
-
- n = MAPPER.readTree(new ByteArrayInputStream(JSON.getBytes("UTF-8")));
- assertTrue(n instanceof ObjectNode);
- }
-
// Test to ensure that we can check property ordering defaults...
public void testConfigForPropertySorting() throws Exception
{
@@ -123,10 +250,7 @@
assertSame(f, m.getFactory());
assertSame(m, f.getCodec());
}
-
- /**
- * Test for verifying working of [JACKSON-191]
- */
+
public void testProviderConfig() throws Exception
{
ObjectMapper m = new ObjectMapper();
@@ -149,72 +273,6 @@
// 4 deserializers (int, List<?>, Map<?,?>, Object)
assertEquals(4, m._deserializationContext._cache.cachedDeserializersCount());
}
-
- // [databind#28]: ObjectMapper.copy()
- public void testCopy() throws Exception
- {
- ObjectMapper m = new ObjectMapper();
- assertTrue(m.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
- m.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
- assertFalse(m.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
- InjectableValues inj = new InjectableValues.Std();
- m.setInjectableValues(inj);
- assertFalse(m.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
- m.enable(JsonParser.Feature.ALLOW_COMMENTS);
- assertTrue(m.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
-
- // // First: verify that handling of features is decoupled:
-
- ObjectMapper m2 = m.copy();
- assertFalse(m2.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
- m2.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
- assertTrue(m2.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
- assertSame(inj, m2.getInjectableValues());
-
- // but should NOT change the original
- assertFalse(m.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
-
- // nor vice versa:
- assertFalse(m.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
- assertFalse(m2.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
- m.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
- assertTrue(m.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
- assertFalse(m2.isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE));
-
- // // Also, underlying JsonFactory instances should be distinct
-
- assertNotSame(m.getFactory(), m2.getFactory());
-
- // [Issue#122]: Need to ensure mix-ins are not shared
- assertEquals(0, m.getSerializationConfig().mixInCount());
- assertEquals(0, m2.getSerializationConfig().mixInCount());
- assertEquals(0, m.getDeserializationConfig().mixInCount());
- assertEquals(0, m2.getDeserializationConfig().mixInCount());
-
- m.addMixIn(String.class, Integer.class);
- assertEquals(1, m.getSerializationConfig().mixInCount());
- assertEquals(0, m2.getSerializationConfig().mixInCount());
- assertEquals(1, m.getDeserializationConfig().mixInCount());
- assertEquals(0, m2.getDeserializationConfig().mixInCount());
-
- // [Issue#913]: Ensure JsonFactory Features copied
- assertTrue(m2.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
-
- }
-
- public void testAnnotationIntrospectorCopyin()
- {
- ObjectMapper m = new ObjectMapper();
- m.setAnnotationIntrospector(new MyAnnotationIntrospector());
- assertEquals(MyAnnotationIntrospector.class,
- m.getDeserializationConfig().getAnnotationIntrospector().getClass());
- ObjectMapper m2 = m.copy();
-
- assertEquals(MyAnnotationIntrospector.class,
- m2.getDeserializationConfig().getAnnotationIntrospector().getClass());
- assertEquals(MyAnnotationIntrospector.class,
- m2.getSerializationConfig().getAnnotationIntrospector().getClass());
- }
// For [databind#689]
public void testCustomDefaultPrettyPrinter() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java
new file mode 100644
index 0000000..1d67d55
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java
@@ -0,0 +1,327 @@
+package com.fasterxml.jackson.databind;
+
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.cfg.ContextAttributes;
+import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class ObjectReaderTest extends BaseMapTest
+{
+ final ObjectMapper MAPPER = new ObjectMapper();
+
+ static class POJO {
+ public Map<String, Object> name;
+ }
+
+ public void testSimpleViaParser() throws Exception
+ {
+ final String JSON = "[1]";
+ JsonParser p = MAPPER.getFactory().createParser(JSON);
+ Object ob = MAPPER.readerFor(Object.class)
+ .readValue(p);
+ p.close();
+ assertTrue(ob instanceof List<?>);
+ }
+
+ public void testSimpleAltSources() throws Exception
+ {
+ final String JSON = "[1]";
+ final byte[] BYTES = JSON.getBytes("UTF-8");
+ Object ob = MAPPER.readerFor(Object.class)
+ .readValue(BYTES);
+ assertTrue(ob instanceof List<?>);
+
+ ob = MAPPER.readerFor(Object.class)
+ .readValue(BYTES, 0, BYTES.length);
+ assertTrue(ob instanceof List<?>);
+ assertEquals(1, ((List<?>) ob).size());
+ }
+
+ public void testParserFeatures() throws Exception
+ {
+ final String JSON = "[ /* foo */ 7 ]";
+ // default won't accept comments, let's change that:
+ ObjectReader reader = MAPPER.readerFor(int[].class)
+ .with(JsonParser.Feature.ALLOW_COMMENTS);
+
+ int[] value = reader.readValue(JSON);
+ assertNotNull(value);
+ assertEquals(1, value.length);
+ assertEquals(7, value[0]);
+
+ // but also can go back
+ try {
+ reader.without(JsonParser.Feature.ALLOW_COMMENTS).readValue(JSON);
+ fail("Should not have passed");
+ } catch (JsonProcessingException e) {
+ verifyException(e, "foo");
+ }
+ }
+
+ public void testNodeHandling() throws Exception
+ {
+ JsonNodeFactory nodes = new JsonNodeFactory(true);
+ ObjectReader r = MAPPER.reader().with(nodes);
+ // but also no further changes if attempting again
+ assertSame(r, r.with(nodes));
+ assertTrue(r.createArrayNode().isArray());
+ assertTrue(r.createObjectNode().isObject());
+ }
+
+ public void testFeatureSettings() throws Exception
+ {
+ ObjectReader r = MAPPER.reader();
+ assertFalse(r.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ assertFalse(r.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
+
+ r = r.withoutFeatures(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,
+ DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
+ assertFalse(r.isEnabled(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES));
+ assertFalse(r.isEnabled(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE));
+ r = r.withFeatures(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,
+ DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
+ assertTrue(r.isEnabled(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES));
+ assertTrue(r.isEnabled(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE));
+
+ // alternative method too... can't recall why two
+ assertSame(r, r.with(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,
+ DeserializationFeature.FAIL_ON_INVALID_SUBTYPE));
+ }
+
+ public void testMiscSettings() throws Exception
+ {
+ ObjectReader r = MAPPER.reader();
+ assertSame(MAPPER.getFactory(), r.getFactory());
+
+ JsonFactory f = new JsonFactory();
+ r = r.with(f);
+ assertSame(f, r.getFactory());
+ assertSame(r, r.with(f));
+
+ assertNotNull(r.getTypeFactory());
+ assertNull(r.getInjectableValues());
+
+ r = r.withAttributes(Collections.emptyMap());
+ ContextAttributes attrs = r.getAttributes();
+ assertNotNull(attrs);
+ assertNull(attrs.getAttribute("abc"));
+ assertSame(r, r.withoutAttribute("foo"));
+
+ ObjectReader newR = r.forType(MAPPER.constructType(String.class));
+ assertNotSame(r, newR);
+ assertSame(newR, newR.forType(String.class));
+
+ DeserializationProblemHandler probH = new DeserializationProblemHandler() {
+ };
+ newR = r.withHandler(probH);
+ assertNotSame(r, newR);
+ assertSame(newR, newR.withHandler(probH));
+ r = newR;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void testDeprecatedSettings() throws Exception
+ {
+ ObjectReader r = MAPPER.reader();
+
+ // and deprecated variants
+ ObjectReader newR = r.forType(MAPPER.constructType(String.class));
+ assertSame(newR, newR.withType(String.class));
+ assertSame(newR, newR.withType(MAPPER.constructType(String.class)));
+
+ newR = newR.withRootName(PropertyName.construct("foo"));
+ assertNotSame(r, newR);
+ assertSame(newR, newR.withRootName(PropertyName.construct("foo")));
+ }
+
+ public void testNoPrefetch() throws Exception
+ {
+ ObjectReader r = MAPPER.reader()
+ .without(DeserializationFeature.EAGER_DESERIALIZER_FETCH);
+ Number n = r.forType(Integer.class).readValue("123 ");
+ assertEquals(Integer.valueOf(123), n);
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, JsonPointer
+ /**********************************************************
+ */
+
+ public void testNoPointerLoading() throws Exception {
+ final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
+
+ JsonNode tree = MAPPER.readTree(source);
+ JsonNode node = tree.at("/foo/bar/caller");
+ POJO pojo = MAPPER.treeToValue(node, POJO.class);
+ assertTrue(pojo.name.containsKey("value"));
+ assertEquals(1234, pojo.name.get("value"));
+ }
+
+ public void testPointerLoading() throws Exception {
+ final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
+
+ ObjectReader reader = MAPPER.readerFor(POJO.class).at("/foo/bar/caller");
+
+ POJO pojo = reader.readValue(source);
+ assertTrue(pojo.name.containsKey("value"));
+ assertEquals(1234, pojo.name.get("value"));
+ }
+
+ public void testPointerLoadingAsJsonNode() throws Exception {
+ final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
+
+ ObjectReader reader = MAPPER.readerFor(POJO.class).at(JsonPointer.compile("/foo/bar/caller"));
+
+ JsonNode node = reader.readTree(source);
+ assertTrue(node.has("name"));
+ assertEquals("{\"value\":1234}", node.get("name").toString());
+ }
+
+ public void testPointerLoadingMappingIteratorOne() throws Exception {
+ final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
+
+ ObjectReader reader = MAPPER.readerFor(POJO.class).at("/foo/bar/caller");
+
+ MappingIterator<POJO> itr = reader.readValues(source);
+
+ POJO pojo = itr.next();
+
+ assertTrue(pojo.name.containsKey("value"));
+ assertEquals(1234, pojo.name.get("value"));
+ assertFalse(itr.hasNext());
+ itr.close();
+ }
+
+ public void testPointerLoadingMappingIteratorMany() throws Exception {
+ final String source = "{\"foo\":{\"bar\":{\"caller\":[{\"name\":{\"value\":1234}}, {\"name\":{\"value\":5678}}]}}}";
+
+ ObjectReader reader = MAPPER.readerFor(POJO.class).at("/foo/bar/caller");
+
+ MappingIterator<POJO> itr = reader.readValues(source);
+
+ POJO pojo = itr.next();
+
+ assertTrue(pojo.name.containsKey("value"));
+ assertEquals(1234, pojo.name.get("value"));
+ assertTrue(itr.hasNext());
+
+ pojo = itr.next();
+
+ assertNotNull(pojo.name);
+ assertTrue(pojo.name.containsKey("value"));
+ assertEquals(5678, pojo.name.get("value"));
+ assertFalse(itr.hasNext());
+ itr.close();
+ }
+
+ // [databind#1637]
+ public void testPointerWithArrays() throws Exception
+ {
+ final String json = aposToQuotes("{\n'wrapper1': {\n" +
+ " 'set1': ['one', 'two', 'three'],\n" +
+ " 'set2': ['four', 'five', 'six']\n" +
+ "},\n" +
+ "'wrapper2': {\n" +
+ " 'set1': ['one', 'two', 'three'],\n" +
+ " 'set2': ['four', 'five', 'six']\n" +
+ "}\n}");
+
+ final Pojo1637 testObject = MAPPER.readerFor(Pojo1637.class)
+ .at("/wrapper1")
+ .readValue(json);
+ assertNotNull(testObject);
+
+ assertNotNull(testObject.set1);
+ assertTrue(!testObject.set1.isEmpty());
+
+ assertNotNull(testObject.set2);
+ assertTrue(!testObject.set2.isEmpty());
+ }
+
+ public static class Pojo1637 {
+ public Set<String> set1;
+ public Set<String> set2;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, ObjectCodec
+ /**********************************************************
+ */
+
+ public void testTreeToValue() throws Exception
+ {
+ ArrayNode n = MAPPER.createArrayNode();
+ n.add("xyz");
+ ObjectReader r = MAPPER.readerFor(String.class);
+ List<?> list = r.treeToValue(n, List.class);
+ assertEquals(1, list.size());
+ }
+
+ public void testCodecUnsupportedWrites() throws Exception
+ {
+ ObjectReader r = MAPPER.readerFor(String.class);
+ JsonGenerator g = MAPPER.getFactory().createGenerator(new StringWriter());
+ ObjectNode n = MAPPER.createObjectNode();
+ try {
+ r.writeTree(g, n);
+ fail("Should not pass");
+ } catch (UnsupportedOperationException e) {
+ ;
+ }
+ try {
+ r.writeValue(g, "Foo");
+ fail("Should not pass");
+ } catch (UnsupportedOperationException e) {
+ ;
+ }
+ g.close();
+
+ g.close();
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, failures, other
+ /**********************************************************
+ */
+
+ public void testMissingType() throws Exception
+ {
+ ObjectReader r = MAPPER.reader();
+ try {
+ r.readValue("1");
+ fail("Should not pass");
+ } catch (JsonMappingException e) {
+ verifyException(e, "No value type configured");
+ }
+ }
+
+ public void testSchema() throws Exception
+ {
+ ObjectReader r = MAPPER.readerFor(String.class);
+
+ // Ok to try to set `null` schema, always:
+ r = r.with((FormatSchema) null);
+
+ try {
+ // but not schema that doesn't match format (no schema exists for json)
+ r = r.with(new BogusSchema())
+ .readValue(quote("foo"));
+
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot use FormatSchema");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/seq/ObjectWriterTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectWriterTest.java
similarity index 60%
rename from src/test/java/com/fasterxml/jackson/databind/seq/ObjectWriterTest.java
rename to src/test/java/com/fasterxml/jackson/databind/ObjectWriterTest.java
index b4cc2a8..b9d2e5d 100644
--- a/src/test/java/com/fasterxml/jackson/databind/seq/ObjectWriterTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ObjectWriterTest.java
@@ -1,8 +1,9 @@
-package com.fasterxml.jackson.databind.seq;
+package com.fasterxml.jackson.databind;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
+import java.io.StringWriter;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@@ -10,7 +11,6 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.SerializedString;
-import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
@@ -146,13 +146,32 @@
assertNotNull(json);
assertTrue(input.closed);
input.close();
+
+ // and via explicitly passed generator
+ JsonGenerator g = MAPPER.getFactory().createGenerator(new StringWriter());
+ input = new CloseableValue();
+ assertFalse(input.closed);
+ w.writeValue(g, input);
+ assertTrue(input.closed);
+ g.close();
+ input.close();
}
- public void testSettings() throws Exception
+ public void testViewSettings() throws Exception
{
ObjectWriter w = MAPPER.writer();
- assertFalse(w.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
- assertFalse(w.isEnabled(JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION));
+ ObjectWriter newW = w.withView(String.class);
+ assertNotSame(w, newW);
+ assertSame(newW, newW.withView(String.class));
+
+ newW = w.with(Locale.CANADA);
+ assertNotSame(w, newW);
+ assertSame(newW, newW.with(Locale.CANADA));
+ }
+
+ public void testMiscSettings() throws Exception
+ {
+ ObjectWriter w = MAPPER.writer();
assertSame(MAPPER.getFactory(), w.getFactory());
assertFalse(w.hasPrefetchedSerializer());
assertNotNull(w.getTypeFactory());
@@ -160,16 +179,96 @@
JsonFactory f = new JsonFactory();
w = w.with(f);
assertSame(f, w.getFactory());
-
- w = w.withView(String.class);
+ ObjectWriter newW = w.with(Base64Variants.MODIFIED_FOR_URL);
+ assertNotSame(w, newW);
+ assertSame(newW, newW.with(Base64Variants.MODIFIED_FOR_URL));
+
w = w.withAttributes(Collections.emptyMap());
w = w.withAttribute("a", "b");
assertEquals("b", w.getAttributes().getAttribute("a"));
w = w.withoutAttribute("a");
assertNull(w.getAttributes().getAttribute("a"));
- w = w.withRootValueSeparator(new SerializedString(","));
+
+ FormatSchema schema = new BogusSchema();
+ try {
+ newW = w.with(schema);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot use FormatSchema");
+ }
}
+ public void testRootValueSettings() throws Exception
+ {
+ ObjectWriter w = MAPPER.writer();
+
+ // First, root name:
+ ObjectWriter newW = w.withRootName("foo");
+ assertNotSame(w, newW);
+ assertSame(newW, newW.withRootName(PropertyName.construct("foo")));
+ w = newW;
+ newW = w.withRootName((String) null);
+ assertNotSame(w, newW);
+ assertSame(newW, newW.withRootName((PropertyName) null));
+
+ // Then root value separator
+
+ w = w.withRootValueSeparator(new SerializedString(","));
+ assertSame(w, w.withRootValueSeparator(new SerializedString(",")));
+ assertSame(w, w.withRootValueSeparator(","));
+
+ newW = w.withRootValueSeparator("/");
+ assertNotSame(w, newW);
+ assertSame(newW, newW.withRootValueSeparator("/"));
+
+ newW = w.withRootValueSeparator((String) null);
+ assertNotSame(w, newW);
+
+ newW = w.withRootValueSeparator((SerializableString) null);
+ assertNotSame(w, newW);
+ }
+
+ public void testFeatureSettings() throws Exception
+ {
+ ObjectWriter w = MAPPER.writer();
+ assertFalse(w.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ assertFalse(w.isEnabled(JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION));
+ ObjectWriter newW = w.with(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS,
+ SerializationFeature.INDENT_OUTPUT);
+ assertNotSame(w, newW);
+ assertTrue(newW.isEnabled(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS));
+ assertTrue(newW.isEnabled(SerializationFeature.INDENT_OUTPUT));
+ assertSame(newW, newW.with(SerializationFeature.INDENT_OUTPUT));
+ assertSame(newW, newW.withFeatures(SerializationFeature.INDENT_OUTPUT));
+
+ newW = w.withFeatures(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS,
+ SerializationFeature.INDENT_OUTPUT);
+ assertNotSame(w, newW);
+
+ newW = w.without(SerializationFeature.FAIL_ON_EMPTY_BEANS,
+ SerializationFeature.EAGER_SERIALIZER_FETCH);
+ assertNotSame(w, newW);
+ assertFalse(newW.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS));
+ assertFalse(newW.isEnabled(SerializationFeature.EAGER_SERIALIZER_FETCH));
+ assertSame(newW, newW.without(SerializationFeature.FAIL_ON_EMPTY_BEANS));
+ assertSame(newW, newW.withoutFeatures(SerializationFeature.FAIL_ON_EMPTY_BEANS));
+
+ assertNotSame(w, w.withoutFeatures(SerializationFeature.FAIL_ON_EMPTY_BEANS,
+ SerializationFeature.EAGER_SERIALIZER_FETCH));
+ }
+
+ public void testGeneratorFeatures() throws Exception
+ {
+ ObjectWriter w = MAPPER.writer();
+ assertFalse(w.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+ assertNotSame(w, w.with(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+ assertNotSame(w, w.withFeatures(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+
+ assertTrue(w.isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET));
+ assertNotSame(w, w.without(JsonGenerator.Feature.AUTO_CLOSE_TARGET));
+ assertNotSame(w, w.withoutFeatures(JsonGenerator.Feature.AUTO_CLOSE_TARGET));
+ }
+
/*
/**********************************************************
/* Test methods, failures
@@ -195,7 +294,7 @@
.writeValueAsBytes("foo");
fail("Should not pass");
} catch (IllegalArgumentException e) {
- verifyException(e, "Can not use FormatSchema");
+ verifyException(e, "Cannot use FormatSchema");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/TestGeneratorUsingMapper.java b/src/test/java/com/fasterxml/jackson/databind/TestGeneratorUsingMapper.java
deleted file mode 100644
index 78955c8..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/TestGeneratorUsingMapper.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.fasterxml.jackson.databind;
-
-import java.io.IOException;
-import java.io.StringWriter;
-
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.SerializableString;
-import com.fasterxml.jackson.core.io.CharacterEscapes;
-
-public class TestGeneratorUsingMapper extends BaseMapTest
-{
- final static class Pojo
- {
- public int getX() { return 4; }
- }
-
- /*
- /**********************************************************
- /* Tests for data binding integration
- /**********************************************************
- */
-
- public void testPojoWriting()
- throws IOException
- {
- JsonFactory jf = new MappingJsonFactory();
- StringWriter sw = new StringWriter();
- JsonGenerator gen = jf.createGenerator(sw);
- gen.writeObject(new Pojo());
- gen.close();
- // trimming needed if main-level object has leading space
- String act = sw.toString().trim();
- assertEquals("{\"x\":4}", act);
- }
-
- public void testPojoWritingFailing()
- throws IOException
- {
- // regular factory can't do it, without a call to setCodec()
- JsonFactory jf = new JsonFactory();
- try {
- StringWriter sw = new StringWriter();
- JsonGenerator gen = jf.createGenerator(sw);
- gen.writeObject(new Pojo());
- gen.close();
- fail("Expected an exception: got sw '"+sw.toString()+"'");
- } catch (IllegalStateException e) {
- verifyException(e, "No ObjectCodec defined");
- }
- }
-
- public void testIssue820() throws IOException
- {
- StringBuffer sb = new StringBuffer();
- while (sb.length() <= 5000) {
- sb.append("Yet another line of text...\n");
- }
- String sampleText = sb.toString();
- assertTrue(
- "Sanity check so I don't mess up the sample text later.",
- sampleText.contains("\n"));
-
- final ObjectMapper mapper = new ObjectMapper();
- final CharacterEscapes defaultCharacterEscapes = new CharacterEscapes() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public int[] getEscapeCodesForAscii() {
- return standardAsciiEscapesForJSON();
- }
-
- @Override
- public SerializableString getEscapeSequence(final int ch) {
- return null;
- }
- };
-
- mapper.getFactory().setCharacterEscapes(defaultCharacterEscapes);
- String jacksonJson = mapper.writeValueAsString(sampleText);
- boolean hasLFs = jacksonJson.indexOf('\n') > 0;
- assertFalse("Should NOT contain linefeeds, should have been escaped", hasLFs);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/TestJDKSerialization.java b/src/test/java/com/fasterxml/jackson/databind/TestJDKSerialization.java
index dd5c4fe..d42abe0 100644
--- a/src/test/java/com/fasterxml/jackson/databind/TestJDKSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/TestJDKSerialization.java
@@ -3,6 +3,14 @@
import java.io.*;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.LRUMap;
@@ -35,6 +43,25 @@
public Map<String,ABC> stuff = new LinkedHashMap<String,ABC>();
}
+ static class AnyBean {
+ HashMap<String,Object> _map;
+
+ public AnyBean() {
+ _map = new HashMap<String,Object>();
+ }
+
+ @JsonAnySetter
+ AnyBean addEntry(String key, Object value) {
+ _map.put(key, value);
+ return this;
+ }
+
+ @JsonAnyGetter
+ public Map<String,Object> properties() {
+ return _map;
+ }
+ }
+
/*
/**********************************************************
/* Tests for individual objects
@@ -107,6 +134,9 @@
final String EXP_JSON = "{\"x\":2,\"y\":3}";
final MyPojo p = new MyPojo(2, 3);
assertEquals(EXP_JSON, origWriter.writeValueAsString(p));
+ String json = origWriter.writeValueAsString(new AnyBean()
+ .addEntry("a", "b"));
+ assertNotNull(json);
byte[] bytes = jdkSerialize(origWriter);
ObjectWriter writer2 = jdkDeserialize(bytes);
assertEquals(EXP_JSON, writer2.writeValueAsString(p));
@@ -115,13 +145,21 @@
public void testObjectReader() throws IOException
{
ObjectReader origReader = MAPPER.readerFor(MyPojo.class);
- final String JSON = "{\"x\":1,\"y\":2}";
+ String JSON = "{\"x\":1,\"y\":2}";
MyPojo p1 = origReader.readValue(JSON);
assertEquals(2, p1.y);
- byte[] bytes = jdkSerialize(origReader);
- ObjectReader reader2 = jdkDeserialize(bytes);
+ ObjectReader anyReader = MAPPER.readerFor(AnyBean.class);
+ AnyBean any = anyReader.readValue(JSON);
+ assertEquals(Integer.valueOf(2), any.properties().get("y"));
+
+ byte[] readerBytes = jdkSerialize(origReader);
+ ObjectReader reader2 = jdkDeserialize(readerBytes);
MyPojo p2 = reader2.readValue(JSON);
assertEquals(2, p2.y);
+
+ ObjectReader anyReader2 = jdkDeserialize(jdkSerialize(anyReader));
+ AnyBean any2 = anyReader2.readValue(JSON);
+ assertEquals(Integer.valueOf(2), any2.properties().get("y"));
}
public void testObjectMapper() throws IOException
@@ -129,6 +167,8 @@
final String EXP_JSON = "{\"x\":2,\"y\":3}";
final MyPojo p = new MyPojo(2, 3);
assertEquals(EXP_JSON, MAPPER.writeValueAsString(p));
+ assertNotNull(MAPPER.getFactory());
+ assertNotNull(MAPPER.getFactory().getCodec());
byte[] bytes = jdkSerialize(MAPPER);
ObjectMapper mapper2 = jdkDeserialize(bytes);
@@ -136,6 +176,10 @@
MyPojo p2 = mapper2.readValue(EXP_JSON, MyPojo.class);
assertEquals(p.x, p2.x);
assertEquals(p.y, p2.y);
+
+ // [databind#2038]: verify that codec is not lost
+ assertNotNull(mapper2.getFactory());
+ assertNotNull(mapper2.getFactory().getCodec());
}
public void testTypeFactory() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanDeserializer.java
deleted file mode 100644
index 5d96b07..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanDeserializer.java
+++ /dev/null
@@ -1,329 +0,0 @@
-package com.fasterxml.jackson.databind;
-
-import java.io.*;
-import java.net.URI;
-import java.util.*;
-
-import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.JsonSerializable;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
-
-/**
- * Unit tests for verifying deserialization of Beans.
- */
-public class TestObjectMapperBeanDeserializer
- extends BaseTest
-{
- /*
- /**********************************************************
- /* Helper classes
- /**********************************************************
- */
-
- final static class CtorValueBean
- implements JsonSerializable // so we can output as simple String
- {
- final String _desc;
-
- public CtorValueBean(String d) { _desc = d; }
- public CtorValueBean(int value) { _desc = String.valueOf(value); }
- public CtorValueBean(long value) { _desc = String.valueOf(value); }
- public CtorValueBean(double value) { _desc = String.valueOf(value); }
-
- @Override
- public void serialize(JsonGenerator jgen, SerializerProvider provider)
- throws IOException, JsonGenerationException
- {
- jgen.writeString(_desc);
- }
-
- @Override public String toString() { return _desc; }
-
- @Override public boolean equals(Object o) {
- if (!(o instanceof CtorValueBean)) return false;
- CtorValueBean other = (CtorValueBean) o;
- return _desc.equals(other._desc);
- }
- @Override
- public void serializeWithType(JsonGenerator jgen,
- SerializerProvider provider, TypeSerializer typeSer)
- throws IOException, JsonProcessingException {
- }
- }
-
- final static class FactoryValueBean
- {
- final String _desc;
-
- protected FactoryValueBean(String desc, int dummy) { _desc = desc; }
-
- public static FactoryValueBean valueOf(String v) { return new FactoryValueBean(v, 0); }
- public static FactoryValueBean valueOf(int v) { return new FactoryValueBean(String.valueOf(v), 0); }
- public static FactoryValueBean valueOf(long v) { return new FactoryValueBean(String.valueOf(v), 0); }
-
- @Override public String toString() { return _desc; }
- }
-
- static class OtherBean {
- SomeIncompatibleType o;
-
- protected OtherBean(SomeIncompatibleType o) {
- this.o = o;
- }
-
- static class SomeIncompatibleType { }
- }
-
- /**
- * Simple test bean
- */
- public final static class TestBean
- {
- int _x;
- long _y;
- String _desc;
- URI _uri;
- Collection<?> _misc;
-
- // Explicit constructor
- public TestBean(int x, long y, String desc, URI uri, Collection<?> misc)
- {
- _x = x;
- _y = y;
- _desc = desc;
- _uri = uri;
- _misc = misc;
- }
-
- // plus default one that is needed for deserialization
- public TestBean() { }
-
- public String getDesc() { return _desc; }
- public int getX() { return _x; }
- public long getY() { return _y; }
- public URI getURI() { return _uri; }
- public Collection<?> getMisc() { return _misc; }
-
- public void setDesc(String value) { _desc = value; }
- public void setX(int value) { _x = value; }
- public void setY(long value) { _y = value; }
- public void setURI(URI value) { _uri = value; }
- public void setMisc(Collection<?> value) { _misc = value; }
-
- @Override
- public boolean equals(Object o)
- {
- if (o == null || o.getClass() != getClass()) return false;
- TestBean other = (TestBean) o;
- return (other._x == _x)
- && (other._y == _y)
- && (other._desc.equals(_desc))
- && (other._uri.equals(_uri))
- && (other._misc.equals(_misc))
- ;
- }
-
- @Override
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
- sb.append("[TestBean ");
- sb.append("x=").append(_x);
- sb.append(" y=").append(_y);
- sb.append(" desc=").append(_desc);
- sb.append(" uri=").append(_uri);
- sb.append(" misc=").append(_misc);
- sb.append("]");
- return sb.toString();
- }
- }
-
- /**
- * Another test bean, this one containing a typed list. Needed to ensure
- * that generics type information is properly accessed via mutator methods.
- * Note: List elements must be something other than what 'untyped' mapper
- * would produce from serialization.
- */
- public final static class BeanWithList
- {
- List<CtorValueBean> _beans;
-
- public BeanWithList() { }
- public BeanWithList(List<CtorValueBean> beans) { _beans = beans; }
-
- public List<CtorValueBean> getBeans() { return _beans; }
-
- public void setBeans(List<CtorValueBean> beans) {
- _beans = beans;
- }
-
- @Override
- public int hashCode() { return (_beans == null) ? -1 : _beans.size(); }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof BeanWithList)) return false;
- BeanWithList other = BeanWithList.class.cast(o);
- return _beans.equals(other._beans);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("[Bean, list ");
- if (_beans == null) {
- sb.append("NULL");
- } else {
- sb.append('(').append(_beans.size()).append('/');
- sb.append(_beans.getClass().getName()).append(") ");
- boolean type = false;
- for (CtorValueBean bean : _beans) {
- if (!type) {
- sb.append("(").append(bean.getClass().getSimpleName()).append(")");
- type = true;
- }
- sb.append(bean);
- sb.append(' ');
- }
- }
- sb.append(']');
- return sb.toString();
- }
- }
-
- /*
- /**********************************************************
- /* Deserialization from simple types (String, int)
- /**********************************************************
- */
-
- private final ObjectMapper MAPPER = new ObjectMapper();
-
- public void testFromStringCtor() throws Exception
- {
- CtorValueBean result = MAPPER.readValue("\"abc\"", CtorValueBean.class);
- assertEquals("abc", result.toString());
- }
-
- public void testFromIntCtor() throws Exception
- {
- CtorValueBean result = MAPPER.readValue("13", CtorValueBean.class);
- assertEquals("13", result.toString());
-
- try {
- OtherBean otherResult = MAPPER.readValue("13", OtherBean.class);
- fail("Expected an exception, but got result value: "+otherResult.o);
- } catch (JsonMappingException e) {
- verifyException(e, "deserialize from Number value",
- "no int/Integer-argument constructor/factory method");
- assertValidLocation(e.getLocation());
- }
- }
-
- public void testFromLongCtor() throws Exception
- {
- // Must use something that is forced as Long...
- long value = 12345678901244L;
- CtorValueBean result = MAPPER.readValue(""+value, CtorValueBean.class);
- assertEquals(""+value, result.toString());
-
- try {
- OtherBean otherResult = MAPPER.readValue(""+value, OtherBean.class);
- fail("Expected an exception, but got result value: "+otherResult.o);
- } catch (JsonMappingException e) {
- verifyException(e, "deserialize from Number value",
- "no long/Long-argument constructor/factory method");
- assertValidLocation(e.getLocation());
- }
- }
-
- public void testFromDoubleCtor() throws Exception
- {
- CtorValueBean result = MAPPER.readValue("13.5", CtorValueBean.class);
- assertEquals("13.5", result.toString());
-
- try {
- OtherBean otherResult = MAPPER.readValue("13.5", OtherBean.class);
- fail("Expected an exception, but got result value: "+otherResult.o);
- } catch (JsonMappingException e) {
- verifyException(e, "deserialize from Number value",
- "no double/Double-argument constructor/factory method");
- assertValidLocation(e.getLocation());
- }
- }
-
- public void testFromStringFactory() throws Exception
- {
- FactoryValueBean result = MAPPER.readValue("\"abc\"", FactoryValueBean.class);
- assertEquals("abc", result.toString());
- }
-
- public void testFromIntFactory() throws Exception
- {
- FactoryValueBean result = MAPPER.readValue("13", FactoryValueBean.class);
- assertEquals("13", result.toString());
- }
-
- public void testFromLongFactory() throws Exception
- {
- // Must use something that is forced as Long...
- long value = 12345678901244L;
- FactoryValueBean result = MAPPER.readValue(""+value, FactoryValueBean.class);
- assertEquals(""+value, result.toString());
- }
-
- /*
- /**********************************************************
- /* Deserialization from JSON Object
- /**********************************************************
- */
-
- public void testSimpleBean() throws Exception
- {
- ArrayList<Object> misc = new ArrayList<Object>();
- misc.add("xyz");
- misc.add(42);
- misc.add(null);
- misc.add(Boolean.TRUE);
- TestBean bean = new TestBean(13, -900L, "\"test\"", new URI("http://foobar.com"), misc);
-
- // Hmmh. We probably should use serializer too... easier
- String json = MAPPER.writeValueAsString(bean);
-
- TestBean result = MAPPER.readValue(json, TestBean.class);
- assertEquals(bean, result);
- }
-
- public void testListBean() throws Exception
- {
- final int COUNT = 13;
- ArrayList<CtorValueBean> beans = new ArrayList<CtorValueBean>();
- for (int i = 0; i < COUNT; ++i) {
- beans.add(new CtorValueBean(i));
- }
- BeanWithList bean = new BeanWithList(beans);
-
- StringWriter sw = new StringWriter();
- MAPPER.writeValue(sw, bean);
-
- BeanWithList result = MAPPER.readValue(sw.toString(), BeanWithList.class);
- assertEquals(bean, result);
- }
-
- /**
- * Also, let's verify that unknown fields cause an exception with default
- * settings.
- */
- public void testUnknownFields() throws Exception
- {
- try {
- TestBean bean = MAPPER.readValue("{ \"foobar\" : 3 }", TestBean.class);
- fail("Expected an exception, got bean: "+bean);
- } catch (JsonMappingException jse) {
- ;
- }
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanSerializer.java b/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanSerializer.java
deleted file mode 100644
index 11deefe..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanSerializer.java
+++ /dev/null
@@ -1,231 +0,0 @@
-package com.fasterxml.jackson.databind;
-
-
-import java.io.*;
-import java.net.*;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/**
- * This unit test suite tries to verify that the "Native" java type
- * mapper can properly serialize Java core objects to JSON.
- *
- * @author Scott Dixon
- */
-public class TestObjectMapperBeanSerializer
- extends BaseTest
-{
- /**
- * Sanity test to ensure the pieces all work when put together.
- */
- public void testComplexObject()
- throws Exception
- {
- FixtureObject aTestObj = new FixtureObject();
- ObjectMapper aMapper = new ObjectMapper();
- StringWriter aWriter = new StringWriter();
- JsonGenerator aGen = new JsonFactory().createGenerator(aWriter);
- aMapper.writeValue(aGen, aTestObj);
- aGen.close();
- JsonParser jp = new JsonFactory().createParser(new StringReader(aWriter.toString()));
-
- assertEquals(JsonToken.START_OBJECT, jp.nextToken());
-
- while (jp.nextToken() != JsonToken.END_OBJECT) {
- assertEquals(JsonToken.FIELD_NAME, jp.getCurrentToken());
- String name = jp.getCurrentName();
- JsonToken t = jp.nextToken();
-
- if (name.equals("uri") || name.equals("url")) {
- assertToken(JsonToken.VALUE_STRING, t);
- assertEquals(FixtureObjectBase.VALUE_URSTR, getAndVerifyText(jp));
- } else if (name.equals("testNull")) {
- assertToken(JsonToken.VALUE_NULL, t);
- } else if (name.equals("testString")) {
- assertToken(JsonToken.VALUE_STRING, t);
- assertEquals(FixtureObjectBase.VALUE_STRING, getAndVerifyText(jp));
- } else if (name.equals("testBoolean")) {
- assertToken(JsonToken.VALUE_TRUE, t);
- } else if (name.equals("testEnum")) {
- assertToken(JsonToken.VALUE_STRING, t);
- assertEquals(FixtureObjectBase.VALUE_ENUM.toString(),getAndVerifyText(jp));
- } else if (name.equals("testInteger")) {
- assertToken(JsonToken.VALUE_NUMBER_INT, t);
- assertEquals(jp.getIntValue(),FixtureObjectBase.VALUE_INT);
- } else if (name.equals("testLong")) {
- assertToken(JsonToken.VALUE_NUMBER_INT, t);
- assertEquals(jp.getLongValue(),FixtureObjectBase.VALUE_LONG);
- } else if (name.equals("testBigInteger")) {
- assertToken(JsonToken.VALUE_NUMBER_INT, t);
- assertEquals(jp.getLongValue(),FixtureObjectBase.VALUE_BIGINT.longValue());
- } else if (name.equals("testBigDecimal")) {
- assertToken(JsonToken.VALUE_NUMBER_FLOAT, t);
- assertEquals(jp.getText(), FixtureObjectBase.VALUE_BIGDEC.toString());
- } else if (name.equals("testCharacter")) {
- assertToken(JsonToken.VALUE_STRING, t);
- assertEquals(String.valueOf(FixtureObjectBase.VALUE_CHAR), getAndVerifyText(jp));
- } else if (name.equals("testShort")) {
- assertToken(JsonToken.VALUE_NUMBER_INT, t);
- assertEquals(jp.getIntValue(),FixtureObjectBase.VALUE_SHORT);
- } else if (name.equals("testByte")) {
- assertToken(JsonToken.VALUE_NUMBER_INT, t);
- assertEquals(jp.getIntValue(),FixtureObjectBase.VALUE_BYTE);
- } else if (name.equals("testFloat")) {
- assertToken(JsonToken.VALUE_NUMBER_FLOAT, t);
- assertEquals(jp.getDecimalValue().floatValue(),FixtureObjectBase.VALUE_FLOAT);
- } else if (name.equals("testDouble")) {
- assertToken(JsonToken.VALUE_NUMBER_FLOAT, t);
- assertEquals(jp.getDoubleValue(),FixtureObjectBase.VALUE_DBL);
- } else if (name.equals("testStringBuffer")) {
- assertToken(JsonToken.VALUE_STRING, t);
- assertEquals(FixtureObjectBase.VALUE_STRING, getAndVerifyText(jp));
- } else if (name.equals("testError")) {
- // More complicated...
- assertToken(JsonToken.START_OBJECT, t);
-
- //getTestError->Exception::getCause
-
- while (jp.nextToken() == JsonToken.FIELD_NAME) {
- name = jp.getCurrentName();
- if (name.equals("cause")) {
- assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
- } else if (name.equals("message")) {
- assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
- assertEquals(FixtureObjectBase.VALUE_ERRTXT, getAndVerifyText(jp));
- } else if (name.equals("localizedMessage")) {
- assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
- } else if (name.equals("stackTrace")) {
- assertEquals(JsonToken.START_ARRAY,jp.nextToken());
- int i = 0;
- while(jp.nextToken() != JsonToken.END_ARRAY) {
- if(i >= 100000) {
- assertTrue("Probably run away loop in test. StackTrack Array was not properly closed.",false);
- }
- }
- } else if (name.equals("suppressed")) {
- // JDK 7 has introduced a new property 'suppressed' to Throwable; skip if seen
- assertEquals(JsonToken.START_ARRAY,jp.nextToken());
- assertEquals(JsonToken.END_ARRAY,jp.nextToken());
- } else {
- fail("Unexpected field name '"+name+"'");
- }
- }
- //CLOSE OF THE EXCEPTION
- assertEquals(JsonToken.END_OBJECT, jp.getCurrentToken());
- } else {
- fail("Unexpected field, name '"+name+"'");
- }
- }
-
- //END OF TOKEN PARSING
- assertNull(jp.nextToken());
- jp.close();
- }
-
- private static enum EFixtureEnum
- {
- THIS_IS_AN_ENUM_VALUE_0,
- THIS_IS_AN_ENUM_VALUE_1,
- THIS_IS_AN_ENUM_VALUE_2,
- THIS_IS_AN_ENUM_VALUE_3,
- }
-
- static class FixtureObjectBase
- {
- public static final String VALUE_STRING = "foobar";
- public static final EFixtureEnum VALUE_ENUM = EFixtureEnum.THIS_IS_AN_ENUM_VALUE_2;
- public static final int VALUE_INT = Integer.MIN_VALUE;
- public static final long VALUE_LONG = Long.MIN_VALUE;
- public static final BigInteger VALUE_BIGINT = new BigInteger((new Long(Long.MAX_VALUE)).toString());
- public static final BigDecimal VALUE_BIGDEC = new BigDecimal((new Double(Double.MAX_VALUE)).toString());
- // this is not necessarily a good char to check
- public static final char VALUE_CHAR = Character.MAX_VALUE;
- public static final short VALUE_SHORT = Short.MAX_VALUE;
- public static final byte VALUE_BYTE = Byte.MAX_VALUE;
- public static final float VALUE_FLOAT = Float.MAX_VALUE;
- public static final double VALUE_DBL = Double.MAX_VALUE;
- public static final String VALUE_ERRTXT = "This is the message text for the test error.";
-
- public static final String VALUE_URSTR = "http://jackson.codehaus.org/hi?var1=foo%20bar";
-
- public URL getURL() throws IOException
- {
- return new URL(VALUE_URSTR);
- }
-
- public URI getURI() throws IOException
- {
- try {
- return new URI(VALUE_URSTR);
- } catch (Exception e) {
- throw new IllegalArgumentException(e);
- }
- }
- public String getTestNull()
- {
- return null;
- }
- public String getTestString()
- {
- return VALUE_STRING;
- }
- public boolean getTestBoolean()
- {
- return true;
- }
- public EFixtureEnum getTestEnum()
- {
- return VALUE_ENUM;
- }
- public int getTestInteger()
- {
- return VALUE_INT;
- }
- public long getTestLong()
- {
- return VALUE_LONG;
- }
- public BigInteger getTestBigInteger()
- {
- return VALUE_BIGINT;
- }
- public BigDecimal getTestBigDecimal()
- {
- return VALUE_BIGDEC;
- }
- public char getTestCharacter()
- {
- return VALUE_CHAR;
- }
- public short getTestShort()
- {
- return VALUE_SHORT;
- }
- public byte getTestByte()
- {
- return VALUE_BYTE;
- }
- public float getTestFloat()
- {
- return VALUE_FLOAT;
- }
- public double getTestDouble()
- {
- return VALUE_DBL;
- }
- public StringBuffer getTestStringBuffer()
- {
- return new StringBuffer(VALUE_STRING);
- }
- }
-
- static class FixtureObject extends FixtureObjectBase
- {
- public Exception getTestError() {
- return new Exception(VALUE_ERRTXT);
- }
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/TestStdDateFormat.java b/src/test/java/com/fasterxml/jackson/databind/TestStdDateFormat.java
deleted file mode 100644
index adff361..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/TestStdDateFormat.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.fasterxml.jackson.databind;
-
-import java.text.ParseException;
-import java.util.*;
-
-import com.fasterxml.jackson.databind.util.StdDateFormat;
-
-public class TestStdDateFormat
- extends BaseMapTest
-{
- public void testFactories() {
- TimeZone tz = TimeZone.getTimeZone("GMT");
- Locale loc = Locale.US;
- assertNotNull(StdDateFormat.getISO8601Format(tz, loc));
- assertNotNull(StdDateFormat.getRFC1123Format(tz, loc));
- }
-
- // [databind#803
- public void testLenient() throws Exception
- {
- StdDateFormat f = StdDateFormat.instance;
-
- // default should be lenient
- assertTrue(f.isLenient());
-
- StdDateFormat f2 = f.clone();
- assertTrue(f2.isLenient());
-
- f2.setLenient(false);
- assertFalse(f2.isLenient());
-
- f2.setLenient(true);
- assertTrue(f2.isLenient());
-
- // and for testing, finally, leave as non-lenient
- f2.setLenient(false);
- assertFalse(f2.isLenient());
- StdDateFormat f3 = f2.clone();
- assertFalse(f3.isLenient());
-
- // first, legal dates are... legal
- Date dt = f3.parse("2015-11-30");
- assertNotNull(dt);
-
- // but as importantly, when not lenient, do not allow
- try {
- f3.parse("2015-11-32");
- fail("Should not pass");
- } catch (ParseException e) {
- verifyException(e, "can not parse date");
- }
-
- // ... yet, with lenient, do allow
- f3.setLenient(true);
- dt = f3.parse("2015-11-32");
- assertNotNull(dt);
- }
-
- public void testInvalid() {
- StdDateFormat std = new StdDateFormat();
- try {
- std.parse("foobar");
- } catch (java.text.ParseException e) {
- verifyException(e, "Can not parse");
- }
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/access/TestSerAnyGetter.java b/src/test/java/com/fasterxml/jackson/databind/access/TestAnyGetterAccess.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/access/TestSerAnyGetter.java
rename to src/test/java/com/fasterxml/jackson/databind/access/TestAnyGetterAccess.java
index 4ca471b..311a86b 100644
--- a/src/test/java/com/fasterxml/jackson/databind/access/TestSerAnyGetter.java
+++ b/src/test/java/com/fasterxml/jackson/databind/access/TestAnyGetterAccess.java
@@ -9,7 +9,7 @@
* Separate tests located in different package than code being
* exercised; needed to trigger some access-related failures.
*/
-public class TestSerAnyGetter
+public class TestAnyGetterAccess
extends BaseMapTest
{
/*
diff --git a/src/test/java/com/fasterxml/jackson/databind/cfg/BogusFormatFeature.java b/src/test/java/com/fasterxml/jackson/databind/cfg/BogusFormatFeature.java
new file mode 100644
index 0000000..c059148
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/cfg/BogusFormatFeature.java
@@ -0,0 +1,31 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import com.fasterxml.jackson.core.FormatFeature;
+
+public enum BogusFormatFeature
+ implements FormatFeature
+{
+ FF_ENABLED_BY_DEFAULT(true),
+ FF_DISABLED_BY_DEFAULT(false);
+
+ private boolean _default;
+
+ private BogusFormatFeature(boolean d) {
+ _default = d;
+ }
+
+ @Override
+ public boolean enabledByDefault() {
+ return _default;
+ }
+
+ @Override
+ public int getMask() {
+ return (1 << ordinal());
+ }
+
+ @Override
+ public boolean enabledIn(int flags) {
+ return (flags & getMask()) != 0;
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/cfg/ConfigObjectsTest.java b/src/test/java/com/fasterxml/jackson/databind/cfg/ConfigObjectsTest.java
new file mode 100644
index 0000000..0ace3b0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/cfg/ConfigObjectsTest.java
@@ -0,0 +1,30 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import com.fasterxml.jackson.databind.*;
+
+import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
+import com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver;
+
+public class ConfigObjectsTest extends BaseMapTest
+{
+ static class Base { }
+ static class Sub extends Base { }
+
+ public void testSubtypeResolver() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ SubtypeResolver res = mapper.getSubtypeResolver();
+ assertTrue(res instanceof StdSubtypeResolver);
+
+ StdSubtypeResolver repl = new StdSubtypeResolver();
+ repl.registerSubtypes(Sub.class);
+ mapper.setSubtypeResolver(repl);
+ assertSame(repl, mapper.getSubtypeResolver());
+ }
+
+ public void testMics() throws Exception
+ {
+ assertFalse(MapperFeature.AUTO_DETECT_FIELDS.enabledIn(0));
+ assertTrue(MapperFeature.AUTO_DETECT_FIELDS.enabledIn(-1));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/cfg/DatabindContextTest.java b/src/test/java/com/fasterxml/jackson/databind/cfg/DatabindContextTest.java
new file mode 100644
index 0000000..9da87a0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/cfg/DatabindContextTest.java
@@ -0,0 +1,22 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import com.fasterxml.jackson.databind.*;
+
+public class DatabindContextTest extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = objectMapper();
+
+ public void testDeserializationContext() throws Exception
+ {
+ DeserializationContext ctxt = MAPPER.getDeserializationContext();
+ // should be ok to try to resolve `null`
+ assertNull(ctxt.constructType((Class<?>) null));
+ assertNull(ctxt.constructType((java.lang.reflect.Type) null));
+ }
+
+ public void testSerializationContext() throws Exception
+ {
+ SerializerProvider ctxt = MAPPER.getSerializerProvider();
+ assertNull(ctxt.constructType(null));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/cfg/DeserializationConfigTest.java b/src/test/java/com/fasterxml/jackson/databind/cfg/DeserializationConfigTest.java
new file mode 100644
index 0000000..5a78e89
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/cfg/DeserializationConfigTest.java
@@ -0,0 +1,127 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import java.util.Collections;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
+
+public class DeserializationConfigTest extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testFeatureDefaults()
+ {
+ ObjectMapper m = new ObjectMapper();
+ DeserializationConfig cfg = m.getDeserializationConfig();
+
+ // Expected defaults:
+ assertTrue(cfg.isEnabled(MapperFeature.USE_ANNOTATIONS));
+ assertTrue(cfg.isEnabled(MapperFeature.AUTO_DETECT_SETTERS));
+ assertTrue(cfg.isEnabled(MapperFeature.AUTO_DETECT_CREATORS));
+ assertTrue(cfg.isEnabled(MapperFeature.USE_GETTERS_AS_SETTERS));
+ assertTrue(cfg.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS));
+
+ assertFalse(cfg.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS));
+ assertFalse(cfg.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS));
+
+ assertTrue(cfg.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
+ }
+
+ public void testBasicFeatures() throws Exception
+ {
+ DeserializationConfig config = MAPPER.getDeserializationConfig();
+ assertTrue(config.hasDeserializationFeatures(DeserializationFeature.EAGER_DESERIALIZER_FETCH.getMask()));
+ assertFalse(config.hasDeserializationFeatures(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY.getMask()));
+ assertTrue(config.hasSomeOfFeatures(DeserializationFeature.EAGER_DESERIALIZER_FETCH.getMask()
+ + DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY.getMask()));
+ assertFalse(config.hasSomeOfFeatures(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY.getMask()));
+
+ // if no changes then same config object
+ assertSame(config, config.without());
+ assertSame(config, config.with());
+ assertSame(config, config.with(MAPPER.getSubtypeResolver()));
+
+ // and then change
+ DeserializationConfig newConfig = config.with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
+ assertNotSame(config, newConfig);
+ config = newConfig;
+
+ // but another attempt with no real change returns same
+ assertSame(config, config.with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ assertNotSame(config, config.with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, false));
+
+ assertNotSame(config, config.with(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT,
+ DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES));
+ }
+
+ public void testParserFeatures() throws Exception
+ {
+ DeserializationConfig config = MAPPER.getDeserializationConfig();
+ assertNotSame(config, config.with(JsonParser.Feature.ALLOW_COMMENTS));
+ assertNotSame(config, config.withFeatures(JsonParser.Feature.ALLOW_COMMENTS,
+ JsonParser.Feature.ALLOW_MISSING_VALUES));
+
+ assertNotSame(config, config.without(JsonParser.Feature.ALLOW_COMMENTS));
+ assertNotSame(config, config.withoutFeatures(JsonParser.Feature.ALLOW_COMMENTS,
+ JsonParser.Feature.ALLOW_MISSING_VALUES));
+ }
+
+ public void testFormatFeatures() throws Exception
+ {
+ DeserializationConfig config = MAPPER.getDeserializationConfig();
+ assertNotSame(config, config.with(BogusFormatFeature.FF_DISABLED_BY_DEFAULT));
+ assertNotSame(config, config.withFeatures(BogusFormatFeature.FF_DISABLED_BY_DEFAULT,
+ BogusFormatFeature.FF_ENABLED_BY_DEFAULT));
+ assertNotSame(config, config.without(BogusFormatFeature.FF_ENABLED_BY_DEFAULT));
+ assertNotSame(config, config.withoutFeatures(BogusFormatFeature.FF_DISABLED_BY_DEFAULT,
+ BogusFormatFeature.FF_ENABLED_BY_DEFAULT));
+ }
+
+ /* Test to verify that we don't overflow number of features; if we
+ * hit the limit, need to change implementation -- this test just
+ * gives low-water mark
+ */
+ public void testEnumIndexes()
+ {
+ int max = 0;
+
+ for (DeserializationFeature f : DeserializationFeature.values()) {
+ max = Math.max(max, f.ordinal());
+ }
+ if (max >= 31) { // 31 is actually ok; 32 not
+ fail("Max number of DeserializationFeature enums reached: "+max);
+ }
+ }
+
+ public void testOverrideIntrospectors()
+ {
+ ObjectMapper m = new ObjectMapper();
+ DeserializationConfig cfg = m.getDeserializationConfig();
+ // and finally, ensure we could override introspectors
+ cfg = cfg.with((ClassIntrospector) null); // no way to verify tho
+ cfg = cfg.with((AnnotationIntrospector) null);
+ assertNull(cfg.getAnnotationIntrospector());
+ }
+
+ public void testMisc() throws Exception
+ {
+ DeserializationConfig config = MAPPER.getDeserializationConfig();
+ assertEquals(JsonInclude.Value.empty(), config.getDefaultPropertyInclusion());
+ assertEquals(JsonInclude.Value.empty(), config.getDefaultPropertyInclusion(String.class));
+
+ assertSame(config, config.withRootName((PropertyName) null)); // defaults to 'none'
+
+ DeserializationConfig newConfig = config.withRootName(PropertyName.construct("foobar"));
+ assertNotSame(config, newConfig);
+ config = newConfig;
+ assertSame(config, config.withRootName(PropertyName.construct("foobar")));
+
+ assertSame(config, config.with(config.getAttributes()));
+ assertNotSame(config, config.with(new ContextAttributes.Impl(Collections.singletonMap("a", "b"))));
+
+ // should also be able to introspect:
+ assertNotNull(config.introspectDirectClassAnnotations(getClass()));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/cfg/SerConfigTest.java b/src/test/java/com/fasterxml/jackson/databind/cfg/SerConfigTest.java
new file mode 100644
index 0000000..adb3b18
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/cfg/SerConfigTest.java
@@ -0,0 +1,77 @@
+package com.fasterxml.jackson.databind.cfg;
+
+import java.util.Collections;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+
+import com.fasterxml.jackson.databind.*;
+
+public class SerConfigTest extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testSerConfig() throws Exception
+ {
+ SerializationConfig config = MAPPER.getSerializationConfig();
+ assertTrue(config.hasSerializationFeatures(SerializationFeature.FAIL_ON_EMPTY_BEANS.getMask()));
+ assertFalse(config.hasSerializationFeatures(SerializationFeature.CLOSE_CLOSEABLE.getMask()));
+ assertEquals(JsonInclude.Value.empty(), config.getDefaultPropertyInclusion());
+ assertEquals(JsonInclude.Value.empty(), config.getDefaultPropertyInclusion(String.class));
+ assertFalse(config.useRootWrapping());
+
+ // if no changes then same config object
+ assertSame(config, config.without());
+ assertSame(config, config.with());
+ assertSame(config, config.with(MAPPER.getSubtypeResolver()));
+
+ // and then change
+ SerializationConfig newConfig = config.with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
+ assertNotSame(config, newConfig);
+ config = newConfig;
+ assertSame(config, config.with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ assertNotSame(config, config.with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, false));
+
+ assertNotSame(config, config.with(SerializationFeature.INDENT_OUTPUT,
+ SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS));
+
+ assertSame(config, config.withRootName((PropertyName) null)); // defaults to 'none'
+
+ newConfig = config.withRootName(PropertyName.construct("foobar"));
+ assertNotSame(config, newConfig);
+ assertTrue(newConfig.useRootWrapping());
+
+ assertSame(config, config.with(config.getAttributes()));
+ assertNotSame(config, config.with(new ContextAttributes.Impl(Collections.singletonMap("a", "b"))));
+
+ assertNotNull(config.introspectDirectClassAnnotations(getClass()));
+ }
+
+ public void testGeneratorFeatures() throws Exception
+ {
+ SerializationConfig config = MAPPER.getSerializationConfig();
+ JsonFactory f = MAPPER.getFactory();
+ assertFalse(config.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII, f));
+ assertNotSame(config, config.with(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+ SerializationConfig newConfig = config.withFeatures(JsonGenerator.Feature.ESCAPE_NON_ASCII,
+ JsonGenerator.Feature.IGNORE_UNKNOWN);
+ assertNotSame(config, newConfig);
+ assertTrue(newConfig.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII, f));
+
+ assertNotSame(config, config.without(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+ assertNotSame(config, config.withoutFeatures(JsonGenerator.Feature.ESCAPE_NON_ASCII,
+ JsonGenerator.Feature.IGNORE_UNKNOWN));
+ }
+
+ public void testFormatFeatures() throws Exception
+ {
+ SerializationConfig config = MAPPER.getSerializationConfig();
+ assertNotSame(config, config.with(BogusFormatFeature.FF_DISABLED_BY_DEFAULT));
+ assertNotSame(config, config.withFeatures(BogusFormatFeature.FF_DISABLED_BY_DEFAULT,
+ BogusFormatFeature.FF_ENABLED_BY_DEFAULT));
+ assertNotSame(config, config.without(BogusFormatFeature.FF_ENABLED_BY_DEFAULT));
+ assertNotSame(config, config.withoutFeatures(BogusFormatFeature.FF_DISABLED_BY_DEFAULT,
+ BogusFormatFeature.FF_ENABLED_BY_DEFAULT));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java
index 8055146..287ad4b 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/NumericConversionTest.java
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.databind.convert;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
public class NumericConversionTest extends BaseMapTest
{
@@ -14,26 +15,32 @@
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, "Can not coerce a floating-point");
+ verifyException(e, "Cannot coerce a floating-point");
}
-
try {
R.forType(Integer.TYPE).readValue("1.5");
fail("Should not pass");
} catch (JsonMappingException e) {
- verifyException(e, "Can not coerce a floating-point");
+ 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, "Can not coerce a floating-point");
+ 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");
}
}
@@ -44,26 +51,34 @@
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 (JsonMappingException e) {
- verifyException(e, "Can not coerce a floating-point");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce a floating-point");
}
try {
R.forType(Long.TYPE).readValue("1.5");
fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not coerce a floating-point");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce a floating-point");
}
try {
R.forType(LongWrapper.class).readValue("{\"l\": 7.7 }");
fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not coerce a floating-point");
+ } 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/ScalarConversionTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/ScalarConversionTest.java
new file mode 100644
index 0000000..0157f97
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/ScalarConversionTest.java
@@ -0,0 +1,34 @@
+package com.fasterxml.jackson.databind.convert;
+
+import com.fasterxml.jackson.databind.*;
+
+public class ScalarConversionTest extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ // [databind#1433]
+ public void testConvertValueNullPrimitive() throws Exception
+ {
+ assertEquals(Byte.valueOf((byte) 0), MAPPER.convertValue(null, Byte.TYPE));
+ assertEquals(Short.valueOf((short) 0), MAPPER.convertValue(null, Short.TYPE));
+ assertEquals(Integer.valueOf(0), MAPPER.convertValue(null, Integer.TYPE));
+ assertEquals(Long.valueOf(0L), MAPPER.convertValue(null, Long.TYPE));
+ assertEquals(Float.valueOf(0f), MAPPER.convertValue(null, Float.TYPE));
+ assertEquals(Double.valueOf(0d), MAPPER.convertValue(null, Double.TYPE));
+ assertEquals(Character.valueOf('\0'), MAPPER.convertValue(null, Character.TYPE));
+ assertEquals(Boolean.FALSE, MAPPER.convertValue(null, Boolean.TYPE));
+ }
+
+ // [databind#1433]
+ public void testConvertValueNullBoxed() throws Exception
+ {
+ assertNull(MAPPER.convertValue(null, Byte.class));
+ assertNull(MAPPER.convertValue(null, Short.class));
+ assertNull(MAPPER.convertValue(null, Integer.class));
+ assertNull(MAPPER.convertValue(null, Long.class));
+ assertNull(MAPPER.convertValue(null, Float.class));
+ assertNull(MAPPER.convertValue(null, Double.class));
+ assertNull(MAPPER.convertValue(null, Character.class));
+ assertNull(MAPPER.convertValue(null, Boolean.class));
+ }
+}
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 a4156eb..fbc4751 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestArrayConversions.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestArrayConversions.java
@@ -14,16 +14,19 @@
final static String OVERFLOW_MSG_BYTE = "out of range of Java byte";
final static String OVERFLOW_MSG = "overflow";
- final ObjectMapper mapper = new ObjectMapper();
+ final static String OVERFLOW_MSG_INT = "out of range of int";
+ final static String OVERFLOW_MSG_LONG = "out of range of long";
+
+ final ObjectMapper MAPPER = new ObjectMapper();
public void testNullXform() throws Exception
{
/* when given null, null should be returned without conversion
* (Java null has no type)
*/
- assertNull(mapper.convertValue(null, Integer.class));
- assertNull(mapper.convertValue(null, String.class));
- assertNull(mapper.convertValue(null, byte[].class));
+ assertNull(MAPPER.convertValue(null, Integer.class));
+ assertNull(MAPPER.convertValue(null, String.class));
+ assertNull(MAPPER.convertValue(null, byte[].class));
}
/**
@@ -72,7 +75,7 @@
List<Number> expNums = _numberList(data, data.length);
// Alas, due to type erasure, need to use TypeRef, not just class
- List<Integer> actNums = mapper.convertValue(data, new TypeReference<List<Integer>>() {});
+ List<Integer> actNums = MAPPER.convertValue(data, new TypeReference<List<Integer>>() {});
assertEquals(expNums, actNums);
}
@@ -84,7 +87,7 @@
verifyLongArrayConversion(data, int[].class);
List<Number> expNums = _numberList(data, data.length);
- List<Long> actNums = mapper.convertValue(data, new TypeReference<List<Long>>() {});
+ List<Long> actNums = MAPPER.convertValue(data, new TypeReference<List<Long>>() {});
assertEquals(expNums, actNums);
}
@@ -92,21 +95,21 @@
{
// Byte overflow
try {
- mapper.convertValue(new int[] { 1000 }, byte[].class);
+ MAPPER.convertValue(new int[] { 1000 }, byte[].class);
} catch (IllegalArgumentException e) {
verifyException(e, OVERFLOW_MSG_BYTE);
}
// Short overflow
try {
- mapper.convertValue(new int[] { -99999 }, short[].class);
+ MAPPER.convertValue(new int[] { -99999 }, short[].class);
} catch (IllegalArgumentException e) {
verifyException(e, OVERFLOW_MSG);
}
// Int overflow
try {
- mapper.convertValue(new long[] { Long.MAX_VALUE }, int[].class);
+ MAPPER.convertValue(new long[] { Long.MAX_VALUE }, int[].class);
} catch (IllegalArgumentException e) {
- verifyException(e, OVERFLOW_MSG);
+ verifyException(e, OVERFLOW_MSG_INT);
}
// Longs need help of BigInteger...
BigInteger biggie = BigInteger.valueOf(Long.MAX_VALUE);
@@ -114,13 +117,12 @@
List<BigInteger> l = new ArrayList<BigInteger>();
l.add(biggie);
try {
- mapper.convertValue(l, int[].class);
+ MAPPER.convertValue(l, long[].class);
} catch (IllegalArgumentException e) {
- verifyException(e, OVERFLOW_MSG);
+ verifyException(e, OVERFLOW_MSG_LONG);
}
-
}
-
+
/*
/********************************************************
/* Helper methods
@@ -171,7 +173,7 @@
// must be a primitive array, like "int[].class"
if (!outputType.isArray()) throw new IllegalArgumentException();
if (!outputType.getComponentType().isPrimitive()) throw new IllegalArgumentException();
- T result = mapper.convertValue(input, outputType);
+ T result = MAPPER.convertValue(input, outputType);
// sanity check first:
assertNotNull(result);
assertEquals(outputType, result.getClass());
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java
index 34c161e..ac08d94 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java
@@ -3,18 +3,22 @@
import java.util.LinkedHashMap;
import java.util.Map;
+import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.StdConverter;
+/**
+ * Tests for various conversions, especially ones using
+ * {@link ObjectMapper#convertValue(Object, Class)}.
+ */
public class TestBeanConversions
extends com.fasterxml.jackson.databind.BaseMapTest
{
- final ObjectMapper MAPPER = new ObjectMapper();
-
static class PointZ {
public int x, y;
@@ -64,7 +68,7 @@
public Leaf(int v) { value = v; }
}
- // [Issue#288]
+ // [databind#288]
@JsonSerialize(converter = ConvertingBeanConverter.class)
static class ConvertingBean {
@@ -90,6 +94,23 @@
return new DummyBean(cb.x, cb.y);
}
}
+
+ @JsonDeserialize(using = NullBeanDeserializer.class)
+ static class NullBean {
+ public static final NullBean NULL_INSTANCE = new NullBean();
+ }
+
+ static class NullBeanDeserializer extends JsonDeserializer<NullBean> {
+ @Override
+ public NullBean getNullValue(final DeserializationContext context) {
+ return NullBean.NULL_INSTANCE;
+ }
+
+ @Override
+ public NullBean deserialize(final JsonParser parser, final DeserializationContext context) {
+ throw new UnsupportedOperationException();
+ }
+ }
/*
/**********************************************************
@@ -97,6 +118,8 @@
/**********************************************************
*/
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
public void testBeanConvert()
{
// should have no problems convert between compatible beans...
@@ -124,7 +147,7 @@
try {
MAPPER.readValue("{\"boolProp\":\"foobar\"}", BooleanBean.class);
} catch (JsonMappingException e) {
- verifyException(e, "Can not deserialize value of type boolean from String");
+ verifyException(e, "Cannot deserialize value of type `boolean` from String");
}
}
@@ -252,5 +275,15 @@
String json = MAPPER.writeValueAsString(new ConvertingBean(1, 2));
// must be {"a":2,"b":4}
assertEquals("{\"a\":2,\"b\":4}", json);
- }
+ }
+
+ // Test null conversions from [databind#1433]
+ public void testConversionIssue1433() throws Exception
+ {
+ assertNull(MAPPER.convertValue(null, Object.class));
+ assertNull(MAPPER.convertValue(null, PointZ.class));
+
+ assertSame(NullBean.NULL_INSTANCE,
+ MAPPER.convertValue(null, NullBean.class));
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingDeserializer.java
index d88e397..5db3bdf 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingDeserializer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingDeserializer.java
@@ -114,7 +114,7 @@
@JsonDeserialize(converter=ToNumberConverter.class)
public Number value;
}
-
+
/*
/**********************************************************
/* Test methods
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java
index 938b67b..89b9e2f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java
@@ -95,7 +95,22 @@
}
}
- // [Issue#359]
+ // [databind#357]
+ static class Value { }
+
+ static class ListWrapper {
+ @JsonSerialize(contentConverter = ValueToStringListConverter.class)
+ public List<Value> list = Arrays.asList(new Value());
+ }
+
+ static class ValueToStringListConverter extends StdConverter<Value, List<String>> {
+ @Override
+ public List<String> convert(Value value) {
+ return Arrays.asList("Hello world!");
+ }
+ }
+
+ // [databind#359]
static class Bean359 {
@JsonSerialize(as = List.class, contentAs = Source.class)
public List<Source> stuff = Arrays.asList(new Source());
@@ -124,7 +139,7 @@
}
}
- // [Issue#731]
+ // [databind#731]
public static class DummyBean {
public final int a, b;
public DummyBean(int v1, int v2) {
@@ -150,21 +165,6 @@
}
}
- // [databind#357]
- static class Value { }
-
- static class ListWrapper {
- @JsonSerialize(contentConverter = ValueToStringListConverter.class)
- public List<Value> list = Arrays.asList(new Value());
- }
-
- static class ValueToStringListConverter extends StdConverter<Value, List<String>> {
- @Override
- public List<String> convert(Value value) {
- return Arrays.asList("Hello world!");
- }
- }
-
/*
/**********************************************************
/* Test methods
@@ -205,6 +205,12 @@
assertEquals("{\"values\":{\"a\":[1,2]}}", json);
}
+ // [databind#357]
+ public void testConverterForList357() throws Exception {
+ String json = objectWriter().writeValueAsString(new ListWrapper());
+ assertEquals("{\"list\":[[\"Hello world!\"]]}", json);
+ }
+
// [databind#359]
public void testIssue359() throws Exception {
String json = objectWriter().writeValueAsString(new Bean359());
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateValue.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateViaObjectReader.java
similarity index 73%
rename from src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateValue.java
rename to src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateViaObjectReader.java
index ef86c5f..1a09c09 100644
--- a/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateValue.java
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateViaObjectReader.java
@@ -3,9 +3,11 @@
import java.io.IOException;
import java.util.*;
-import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
@@ -15,14 +17,9 @@
* Unit tests for verifying that "updating reader" works as
* expected.
*/
-public class TestUpdateValue extends BaseMapTest
+@SuppressWarnings("serial")
+public class TestUpdateViaObjectReader extends BaseMapTest
{
- /*
- /********************************************************
- /* Helper types
- /********************************************************
- */
-
static class Bean {
public String a = "a";
public String b = "b";
@@ -36,7 +33,6 @@
public int x, y;
}
- // [JACKSON-824]
public class TextView {}
public class NumView {}
@@ -70,9 +66,9 @@
@Override
public DataA deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.getCurrentToken() != JsonToken.START_OBJECT) {
- ctxt.reportWrongTokenException(p, JsonToken.START_OBJECT,
- "Wrong current token, expected START_OBJECT, got: %s",
- p.getCurrentToken());
+ ctxt.reportInputMismatch(DataA.class,
+ "Wrong current token, expected START_OBJECT, got: "
+ +p.getCurrentToken());
// never gets here
}
/*JsonNode node =*/ p.readValueAsTree();
@@ -82,10 +78,46 @@
return da;
}
}
-
+
+ // [databind#1831]
+ @JsonTypeInfo(use = Id.NAME)
+ @JsonSubTypes({ @JsonSubTypes.Type(value = Cat.class) })
+ static abstract public class AbstractAnimal { }
+
+ @JsonDeserialize(using = AnimalWrapperDeserializer.class)
+ static class AnimalWrapper {
+ @JsonUnwrapped
+ protected AbstractAnimal animal;
+
+ public void setAnimal(AbstractAnimal animal) {
+ this.animal = animal;
+ }
+ }
+
+ static class Cat extends AbstractAnimal { }
+
+ static class AnimalWrapperDeserializer extends StdDeserializer<AnimalWrapper> {
+ public AnimalWrapperDeserializer() {
+ super(AnimalWrapper.class);
+ }
+
+ @Override
+ public AnimalWrapper deserialize(JsonParser json, DeserializationContext context) throws IOException {
+ AnimalWrapper msg = new AnimalWrapper();
+ msg.setAnimal(json.readValueAs(AbstractAnimal.class));
+ return msg;
+ }
+
+ @Override
+ public AnimalWrapper deserialize(JsonParser json, DeserializationContext context, AnimalWrapper intoValue) throws IOException {
+ intoValue.setAnimal(json.readValueAs(AbstractAnimal.class));
+ return intoValue;
+ }
+ }
+
/*
/********************************************************
- /* Unit tests
+ /* Test methods
/********************************************************
*/
@@ -225,4 +257,21 @@
assertEquals(5, dbUpdViaNode.da.i);
assertEquals(13, dbUpdViaNode.k);
}
+
+ // [databind#1831]
+ public void test1831UsingNode() throws IOException {
+ String catJson = MAPPER.writeValueAsString(new Cat());
+ JsonNode jsonNode = MAPPER.readTree(catJson);
+ AnimalWrapper optionalCat = new AnimalWrapper();
+ ObjectReader r = MAPPER.readerForUpdating(optionalCat);
+ AnimalWrapper result = r.readValue(jsonNode);
+ assertSame(optionalCat, result);
+ }
+
+ public void test1831UsingString() throws IOException {
+ String catJson = MAPPER.writeValueAsString(new Cat());
+ AnimalWrapper optionalCat = new AnimalWrapper();
+ AnimalWrapper result = MAPPER.readerForUpdating(optionalCat).readValue(catJson);
+ assertSame(optionalCat, result);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/UpdateValueTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/UpdateValueTest.java
new file mode 100644
index 0000000..99db96c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/convert/UpdateValueTest.java
@@ -0,0 +1,101 @@
+package com.fasterxml.jackson.databind.convert;
+
+import java.util.*;
+
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * Tests for {@link ObjectMapper#updateValue}.
+ *
+ * @since 2.9
+ */
+public class UpdateValueTest extends BaseMapTest
+{
+ /*
+ /********************************************************
+ /* Test methods; simple containers
+ /********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testMapUpdate() throws Exception
+ {
+ Map<String,Object> base = new LinkedHashMap<>();
+ base.put("a", 345);
+ Map<String,Object> overrides = new LinkedHashMap<>();
+ overrides.put("xyz", Boolean.TRUE);
+ overrides.put("foo", "bar");
+
+ Map<String,Object> ob = MAPPER.updateValue(base, overrides);
+ // first: should return first argument
+ assertSame(base, ob);
+ assertEquals(3, ob.size());
+ assertEquals(Integer.valueOf(345), ob.get("a"));
+ assertEquals("bar", ob.get("foo"));
+ assertEquals(Boolean.TRUE, ob.get("xyz"));
+ }
+
+ public void testListUpdate() throws Exception
+ {
+ List<Object> base = new ArrayList<>();
+ base.add(123456);
+ base.add(Boolean.FALSE);
+ Object[] overrides = new Object[] { Boolean.TRUE, "zoink!" };
+
+ List<Object> ob = MAPPER.updateValue(base, overrides);
+ // first: should return first argument
+ assertSame(base, ob);
+ assertEquals(4, ob.size());
+ assertEquals(Integer.valueOf(123456), ob.get(0));
+ assertEquals(Boolean.FALSE, ob.get(1));
+ assertEquals(overrides[0], ob.get(2));
+ assertEquals(overrides[1], ob.get(3));
+ }
+
+ public void testArrayUpdate() throws Exception
+ {
+ // Since Arrays are immutable, not sure what "right answer" ought to be
+ Object[] base = new Object[] { Boolean.FALSE, Integer.valueOf(3) };
+ Object[] overrides = new Object[] { Boolean.TRUE, "zoink!" };
+
+ Object[] ob = MAPPER.updateValue(base, overrides);
+ assertEquals(4, ob.length);
+ assertEquals(base[0], ob[0]);
+ assertEquals(base[1], ob[1]);
+ assertEquals(overrides[0], ob[2]);
+ assertEquals(overrides[1], ob[3]);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods; POJOs
+ /********************************************************
+ */
+
+ public void testPOJO() throws Exception
+ {
+ Point base = new Point(42, 28);
+ Map<String,Object> overrides = new LinkedHashMap<>();
+ overrides.put("y", 1234);
+ Point result = MAPPER.updateValue(base, overrides);
+ assertSame(base, result);
+ assertEquals(42, result.x);
+ assertEquals(1234, result.y);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods; other
+ /********************************************************
+ */
+
+ public void testMisc() throws Exception
+ {
+ // if either is `null`, should return first arg
+ assertNull(MAPPER.updateValue(null, "foo"));
+ List<String> input = new ArrayList<>();
+ assertSame(input, MAPPER.updateValue(input, null));
+ }
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/Creator1476Test.java b/src/test/java/com/fasterxml/jackson/databind/creators/Creator1476Test.java
deleted file mode 100644
index 94a1eee..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/creators/Creator1476Test.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.fasterxml.jackson.databind.creators;
-
-import com.fasterxml.jackson.annotation.*;
-
-import com.fasterxml.jackson.databind.*;
-
-public class Creator1476Test extends BaseMapTest
-{
- static final class SimplePojo {
- private final int intField;
- private final String stringField;
-
- public SimplePojo(@JsonProperty("intField") int intField) {
- this(intField, "empty");
- }
-
- public SimplePojo(@JsonProperty("stringField") String stringField) {
- this(-1, stringField);
- }
-
- @JsonCreator
- public SimplePojo(@JsonProperty("intField") int intField, @JsonProperty("stringField") String stringField) {
- this.intField = intField;
- this.stringField = stringField;
- }
-
- public int getIntField() {
- return intField;
- }
-
- public String getStringField() {
- return stringField;
- }
- }
-
- public void testConstructorChoice() throws Exception {
- ObjectMapper mapper = new ObjectMapper();
- SimplePojo pojo = mapper.readValue("{ \"intField\": 1, \"stringField\": \"foo\" }", SimplePojo.class);
-
- assertEquals(1, pojo.getIntField());
- assertEquals("foo", pojo.getStringField());
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators421.java b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators421.java
deleted file mode 100644
index 27de087..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators421.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.fasterxml.jackson.databind.creators;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
-import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
-import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
-
-public class TestCreators421 extends BaseMapTest
-{
- static class MultiCtor
- {
- protected String _a, _b;
-
- private MultiCtor() { }
- private MultiCtor(String a, String b, Boolean c) {
- if (c == null) {
- throw new RuntimeException("Wrong factory!");
- }
- _a = a;
- _b = b;
- }
-
- @JsonCreator
- static MultiCtor factory(@JsonProperty("a") String a, @JsonProperty("b") String b) {
- return new MultiCtor(a, b, Boolean.TRUE);
- }
- }
-
- @SuppressWarnings("serial")
- static class MyParamIntrospector extends JacksonAnnotationIntrospector
- {
- @Override
- public String findImplicitPropertyName(AnnotatedMember param) {
- if (param instanceof AnnotatedParameter) {
- AnnotatedParameter ap = (AnnotatedParameter) param;
- switch (ap.getIndex()) {
- case 0: return "a";
- case 1: return "b";
- case 2: return "c";
- default:
- return "param"+ap.getIndex();
- }
- }
- return super.findImplicitPropertyName(param);
- }
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- // [Issue#421]
- public void testMultiCtor421() throws Exception
- {
- final ObjectMapper mapper = new ObjectMapper();
- mapper.setAnnotationIntrospector(new MyParamIntrospector());
-
- MultiCtor bean = mapper.readValue(aposToQuotes("{'a':'123','b':'foo'}"), MultiCtor.class);
- assertNotNull(bean);
- assertEquals("123", bean._a);
- assertEquals("foo", bean._b);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators541.java b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators541.java
deleted file mode 100644
index e0f1214..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators541.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.fasterxml.jackson.databind.creators;
-
-import java.util.*;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import com.fasterxml.jackson.databind.*;
-
-public class TestCreators541 extends BaseMapTest
-{
- static final class Foo {
-
- @JsonProperty("foo")
- protected Map<Integer, Bar> foo;
- @JsonProperty("anumber")
- protected long anumber;
-
- public Foo() {
- anumber = 0;
- }
-
- public Map<Integer, Bar> getFoo() {
- return foo;
- }
-
- public long getAnumber() {
- return anumber;
- }
- }
-
- static final class Bar {
-
- private final long p;
- private final List<String> stuff;
-
- @JsonCreator
- public Bar(@JsonProperty("p") long p, @JsonProperty("stuff") List<String> stuff) {
- this.p = p;
- this.stuff = stuff;
- }
-
- @JsonProperty("s")
- public List<String> getStuff() {
- return stuff;
- }
-
- @JsonProperty("stuff")
- private List<String> getStuffDeprecated() {
- return stuff;
- }
-
- public long getP() {
- return p;
- }
- }
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- public void testCreator541() throws Exception
- {
- ObjectMapper mapper = new ObjectMapper();
-
- mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
- mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
- mapper.disable(
- MapperFeature.AUTO_DETECT_CREATORS,
- MapperFeature.AUTO_DETECT_FIELDS,
- MapperFeature.AUTO_DETECT_GETTERS,
- MapperFeature.AUTO_DETECT_IS_GETTERS,
- MapperFeature.AUTO_DETECT_SETTERS,
- MapperFeature.USE_GETTERS_AS_SETTERS
- );
- mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
-
- final String JSON = "{\n"
- + " \"foo\": {\n"
- + " \"0\": {\n"
- + " \"p\": 0,\n"
- + " \"stuff\": [\n"
- + " \"a\", \"b\" \n"
- + " ] \n"
- + " },\n"
- + " \"1\": {\n"
- + " \"p\": 1000,\n"
- + " \"stuff\": [\n"
- + " \"c\", \"d\" \n"
- + " ] \n"
- + " },\n"
- + " \"2\": {\n"
- + " \"p\": 2000,\n"
- + " \"stuff\": [\n"
- + " ] \n"
- + " }\n"
- + " },\n"
- + " \"anumber\": 25385874\n"
- + "}";
-
- Foo obj = mapper.readValue(JSON, Foo.class);
- assertNotNull(obj);
- assertNotNull(obj.foo);
- assertEquals(3, obj.foo.size());
- assertEquals(25385874L, obj.getAnumber());
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/AnySetter349Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/AnySetter349Test.java
index 5ff1d2b..7cfa4d4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/AnySetter349Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/AnySetter349Test.java
@@ -12,6 +12,7 @@
static class Bean349
{
public String type;
+ public int x, y;
private Map<String, Object> props = new HashMap<>();
@@ -32,18 +33,34 @@
static class IdentityDTO349 {
public int x, y;
}
+
+ final static String UNWRAPPED_JSON_349 = aposToQuotes(
+"{ 'type' : 'IST',\n"
++" 'x' : 3,\n"
+//+" 'name' : 'BLAH-New',\n"
+//+" 'description' : 'namespace.name: X THIN FIR.DR-WD12-New',\n"
++" 'ZoomLinks': [ 'foofoofoofoo', 'barbarbarbar' ],\n"
++" 'y' : 4, 'z' : 8 }"
+ );
public void testUnwrappedWithAny() throws Exception
{
final ObjectMapper mapper = objectMapper();
- final String json = aposToQuotes(
-"{ 'type' : 'IST',\n"
-//+" 'spacename' : 'Foo Models',\n"
-//+" 'name' : 'BLAH-New',\n"
-//+" 'description' : 'namespace.name: X THIN FIR.DR-WD12-New',\n"
-+" 'ZoomLinks': [ 'foofoofoofoo', 'barbarbarbar' ] }"
- );
- Bean349 value = mapper.readValue(json, Bean349.class);
+ Bean349 value = mapper.readValue(UNWRAPPED_JSON_349, Bean349.class);
assertNotNull(value);
+ assertEquals(3, value.x);
+ assertEquals(4, value.y);
+ assertEquals(2, value.props.size());
+ }
+
+ public void testUnwrappedWithAnyAsUpdate() throws Exception
+ {
+ final ObjectMapper mapper = objectMapper();
+ Bean349 bean = mapper.readerFor(Bean349.class)
+ .withValueToUpdate(new Bean349())
+ .readValue(UNWRAPPED_JSON_349);
+ assertEquals(3, bean.x);
+ assertEquals(4, bean.y);
+ assertEquals(2, bean.props.size());
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestAnyProperties.java b/src/test/java/com/fasterxml/jackson/databind/deser/AnySetterTest.java
similarity index 62%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestAnyProperties.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/AnySetterTest.java
index fb7ef8e..7e5dc85 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestAnyProperties.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/AnySetterTest.java
@@ -10,7 +10,7 @@
* Unit tests for verifying that {@link JsonAnySetter} annotation
* works as expected.
*/
-public class TestAnyProperties
+public class AnySetterTest
extends BaseMapTest
{
static class MapImitator
@@ -28,6 +28,16 @@
}
}
+ // for [databind#1376]
+ static class MapImitatorDisabled extends MapImitator
+ {
+ @Override
+ @JsonAnySetter(enabled=false)
+ void addEntry(String key, Object value) {
+ throw new RuntimeException("Should not get called");
+ }
+ }
+
/**
* Let's also verify that it is possible to define different
* value: not often useful, but possible.
@@ -135,34 +145,77 @@
}
}
- static class JsonAnySetterOnMap {
- public int id;
+ static class JsonAnySetterOnMap {
+ public int id;
- @JsonAnySetter
- protected HashMap<String, String> other = new HashMap<String, String>();
+ @JsonAnySetter
+ protected HashMap<String, String> other = new HashMap<String, String>();
- @JsonAnyGetter
- public Map<String, String> any() {
- return other;
- }
+ @JsonAnyGetter
+ public Map<String, String> any() {
+ return other;
+ }
+ }
- }
+ static class JsonAnySetterOnNullMap {
+ public int id;
- static class JsonAnySetterOnNullMap {
- public int id;
+ @JsonAnySetter
+ protected HashMap<String, String> other;
- @JsonAnySetter
- protected HashMap<String, String> other;
+ @JsonAnyGetter
+ public Map<String, String> any() {
+ return other;
+ }
+ }
- @JsonAnyGetter
- public Map<String, String> any() {
- return other;
- }
-
- }
-
+ static class MyGeneric<T>
+ {
+ private String staticallyMappedProperty;
+ private Map<T, Integer> dynamicallyMappedProperties = new HashMap<T, Integer>();
- /*
+ public String getStaticallyMappedProperty() {
+ return staticallyMappedProperty;
+ }
+
+ @JsonAnySetter
+ public void addDynamicallyMappedProperty(T key, int value) {
+ dynamicallyMappedProperties.put(key, value);
+ }
+
+ public void setStaticallyMappedProperty(String staticallyMappedProperty) {
+ this.staticallyMappedProperty = staticallyMappedProperty;
+ }
+
+ @JsonAnyGetter
+ public Map<T, Integer> getDynamicallyMappedProperties() {
+ return dynamicallyMappedProperties;
+ }
+ }
+
+ static class MyWrapper
+ {
+ private MyGeneric<String> myStringGeneric;
+ private MyGeneric<Integer> myIntegerGeneric;
+
+ public MyGeneric<String> getMyStringGeneric() {
+ return myStringGeneric;
+ }
+
+ public void setMyStringGeneric(MyGeneric<String> myStringGeneric) {
+ this.myStringGeneric = myStringGeneric;
+ }
+
+ public MyGeneric<Integer> getMyIntegerGeneric() {
+ return myIntegerGeneric;
+ }
+
+ public void setMyIntegerGeneric(MyGeneric<Integer> myIntegerGeneric) {
+ this.myIntegerGeneric = myIntegerGeneric;
+ }
+ }
+
+ /*
/**********************************************************
/* Test methods
/**********************************************************
@@ -185,6 +238,18 @@
assertEquals(Integer.valueOf(3), l.get(2));
}
+ public void testAnySetterDisable() throws Exception
+ {
+ try {
+ MAPPER.readValue(aposToQuotes("{'value':3}"),
+ MapImitatorDisabled.class);
+ fail("Should not pass");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Unrecognized field \"value\"");
+ }
+
+ }
+
public void testSimpleTyped() throws Exception
{
MapImitatorWithValue mapHolder = MAPPER.readValue
@@ -202,7 +267,7 @@
Broken b = MAPPER.readValue("{ \"a\" : 3 }", Broken.class);
fail("Should have gotten an exception");
} catch (JsonMappingException e) {
- verifyException(e, "Multiple 'any-setters'");
+ verifyException(e, "Multiple 'any-setter' methods");
}
}
@@ -239,6 +304,7 @@
{
PolyAnyBean input = new PolyAnyBean();
input.props.put("a", new Impl("xyz"));
+
String json = MAPPER.writeValueAsString(input);
// System.err.println("JSON: "+json);
@@ -264,8 +330,43 @@
JsonAnySetterOnNullMap.class);
assertEquals(2, result.id);
assertNull(result.other);
- }
-
+ }
+
+ // [databind#1035]
+ public void testGenericAnySetter() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+
+ Map<String, Integer> stringGenericMap = new HashMap<String, Integer>();
+ stringGenericMap.put("testStringKey", 5);
+ Map<Integer, Integer> integerGenericMap = new HashMap<Integer, Integer>();
+ integerGenericMap.put(111, 6);
+
+ MyWrapper deserialized = mapper.readValue(aposToQuotes(
+ "{'myStringGeneric':{'staticallyMappedProperty':'Test','testStringKey':5},'myIntegerGeneric':{'staticallyMappedProperty':'Test2','111':6}}"
+ ), MyWrapper.class);
+ MyGeneric<String> stringGeneric = deserialized.getMyStringGeneric();
+ MyGeneric<Integer> integerGeneric = deserialized.getMyIntegerGeneric();
+
+ assertNotNull(stringGeneric);
+ assertEquals(stringGeneric.getStaticallyMappedProperty(), "Test");
+ for(Map.Entry<String, Integer> entry : stringGeneric.getDynamicallyMappedProperties().entrySet()) {
+ assertTrue("A key in MyGeneric<String> is not an String.", entry.getKey() instanceof String);
+ assertTrue("A value in MyGeneric<Integer> is not an Integer.", entry.getValue() instanceof Integer);
+ }
+ assertEquals(stringGeneric.getDynamicallyMappedProperties(), stringGenericMap);
+
+ assertNotNull(integerGeneric);
+ assertEquals(integerGeneric.getStaticallyMappedProperty(), "Test2");
+ for(Map.Entry<Integer, Integer> entry : integerGeneric.getDynamicallyMappedProperties().entrySet()) {
+ Object key = entry.getKey();
+ assertEquals("A key in MyGeneric<Integer> is not an Integer.", Integer.class, key.getClass());
+ Object value = entry.getValue();
+ assertEquals("A value in MyGeneric<Integer> is not an Integer.", Integer.class, value.getClass());
+ }
+ assertEquals(integerGeneric.getDynamicallyMappedProperties(), integerGenericMap);
+ }
+
/*
/**********************************************************
/* Private helper methods
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestAnnotationIgnore.java b/src/test/java/com/fasterxml/jackson/databind/deser/IgnoreWithDeserTest.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestAnnotationIgnore.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/IgnoreWithDeserTest.java
index 74c656c..e1dfa5b 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestAnnotationIgnore.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/IgnoreWithDeserTest.java
@@ -8,7 +8,7 @@
* This unit test suite that tests use of {@link JsonIgnore}
* annotation with deserialization.
*/
-public class TestAnnotationIgnore
+public class IgnoreWithDeserTest
extends BaseMapTest
{
// Class for testing {@link JsonIgnore} annotations with setters
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/JDKScalarsTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/JDKScalarsTest.java
deleted file mode 100644
index 7679745..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/deser/JDKScalarsTest.java
+++ /dev/null
@@ -1,1002 +0,0 @@
-package com.fasterxml.jackson.databind.deser;
-
-import java.io.*;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-import org.junit.Assert;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.*;
-
-/**
- * Unit tests for verifying handling of simple basic non-structured
- * types; primitives (and/or their wrappers), Strings.
- */
-public class JDKScalarsTest
- extends BaseMapTest
-{
- final static String NAN_STRING = "NaN";
-
- final static class BooleanBean {
- boolean _v;
- void setV(boolean v) { _v = v; }
- }
-
- static class BooleanWrapper {
- public Boolean wrapper;
- public boolean primitive;
-
- protected Boolean ctor;
-
- @JsonCreator
- public BooleanWrapper(@JsonProperty("ctor") Boolean foo) {
- ctor = foo;
- }
- }
-
- static class IntBean {
- int _v;
- void setV(int v) { _v = v; }
- }
-
- final static class DoubleBean {
- double _v;
- void setV(double v) { _v = v; }
- }
-
- final static class FloatBean {
- float _v;
- void setV(float v) { _v = v; }
- }
-
- final static class CharacterBean {
- char _v;
- void setV(char v) { _v = v; }
- char getV() { return _v; }
- }
-
- final static class CharacterWrapperBean {
- Character _v;
- void setV(Character v) { _v = v; }
- Character getV() { return _v; }
- }
-
- /**
- * Also, let's ensure that it's ok to override methods.
- */
- static class IntBean2
- extends IntBean
- {
- @Override
- void setV(int v2) { super.setV(v2+1); }
- }
-
- static class PrimitivesBean
- {
- public boolean booleanValue = true;
- public byte byteValue = 3;
- public char charValue = 'a';
- public short shortValue = 37;
- public int intValue = 1;
- public long longValue = 100L;
- public float floatValue = 0.25f;
- public double doubleValue = -1.0;
- }
-
- static class WrappersBean
- {
- public Boolean booleanValue;
- public Byte byteValue;
- public Character charValue;
- public Short shortValue;
- public Integer intValue;
- public Long longValue;
- public Float floatValue;
- public Double doubleValue;
- }
-
- private final ObjectMapper MAPPER = new ObjectMapper();
-
- /*
- /**********************************************************
- /* Scalar tests for boolean
- /**********************************************************
- */
-
- public void testBooleanPrimitive() throws Exception
- {
- // first, simple case:
- BooleanBean result = MAPPER.readValue(new StringReader("{\"v\":true}"), BooleanBean.class);
- assertTrue(result._v);
- result = MAPPER.readValue(new StringReader("{\"v\":null}"), BooleanBean.class);
- assertNotNull(result);
- assertFalse(result._v);
- // [databind#1480]
- result = MAPPER.readValue(new StringReader("{\"v\":1}"), BooleanBean.class);
- assertNotNull(result);
- assertTrue(result._v);
-
- // should work with arrays too..
- boolean[] array = MAPPER.readValue(new StringReader("[ null ]"), boolean[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertFalse(array[0]);
-
- }
-
- public void testBooleanPrimitiveArrayUnwrap() throws Exception
- {
- // [databind#381]
- final ObjectMapper mapper = new ObjectMapper();
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
- BooleanBean result = mapper.readValue(new StringReader("{\"v\":[true]}"), BooleanBean.class);
- assertTrue(result._v);
-
- try {
- mapper.readValue(new StringReader("[{\"v\":[true,true]}]"), BooleanBean.class);
- fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled");
- } catch (JsonMappingException exp) {
- //threw exception as required
- }
-
- result = mapper.readValue(new StringReader("{\"v\":[null]}"), BooleanBean.class);
- assertNotNull(result);
- assertFalse(result._v);
-
- result = mapper.readValue(new StringReader("[{\"v\":[null]}]"), BooleanBean.class);
- assertNotNull(result);
- assertFalse(result._v);
-
- boolean[] array = mapper.readValue(new StringReader("[ [ null ] ]"), boolean[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertFalse(array[0]);
- }
-
- /**
- * Simple unit test to verify that we can map boolean values to
- * java.lang.Boolean.
- */
- public void testBooleanWrapper() throws Exception
- {
- Boolean result = MAPPER.readValue(new StringReader("true"), Boolean.class);
- assertEquals(Boolean.TRUE, result);
- result = MAPPER.readValue(new StringReader("false"), Boolean.class);
- assertEquals(Boolean.FALSE, result);
-
- // should accept ints too, (0 == false, otherwise true)
- result = MAPPER.readValue("0", Boolean.class);
- assertEquals(Boolean.FALSE, result);
- result = MAPPER.readValue("1", Boolean.class);
- assertEquals(Boolean.TRUE, result);
- }
-
- // Test for verifying that Long values are coerced to boolean correctly as well
- public void testLongToBoolean() throws Exception
- {
- long value = 1L + Integer.MAX_VALUE;
- BooleanWrapper b = MAPPER.readValue("{\"primitive\" : "+value+", \"wrapper\":"+value+", \"ctor\":"+value+"}",
- BooleanWrapper.class);
- assertEquals(Boolean.TRUE, b.wrapper);
- assertTrue(b.primitive);
- assertEquals(Boolean.TRUE, b.ctor);
-
- // but ensure we can also get `false`
- b = MAPPER.readValue("{\"primitive\" : 0 , \"wrapper\":0, \"ctor\":0}",
- BooleanWrapper.class);
- assertEquals(Boolean.FALSE, b.wrapper);
- assertFalse(b.primitive);
- assertEquals(Boolean.FALSE, b.ctor);
- }
-
- /*
- /**********************************************************
- /* Scalar tests for integral types
- /**********************************************************
- */
-
- public void testIntPrimitive() throws Exception
- {
- // first, simple case:
- IntBean result = MAPPER.readValue(new StringReader("{\"v\":3}"), IntBean.class);
- assertEquals(3, result._v);
- result = MAPPER.readValue(new StringReader("{\"v\":null}"), IntBean.class);
- assertNotNull(result);
- assertEquals(0, result._v);
-
- // should work with arrays too..
- int[] array = MAPPER.readValue(new StringReader("[ null ]"), int[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertEquals(0, array[0]);
-
- // [Issue#381]
- final ObjectMapper mapper = new ObjectMapper();
- mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
- try {
- mapper.readValue(new StringReader("{\"v\":[3]}"), IntBean.class);
- fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled");
- } catch (JsonMappingException exp) {
- //Correctly threw exception
- }
-
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- result = mapper.readValue(new StringReader("{\"v\":[3]}"), IntBean.class);
- assertEquals(3, result._v);
-
- result = mapper.readValue(new StringReader("[{\"v\":[3]}]"), IntBean.class);
- assertEquals(3, result._v);
-
- try {
- mapper.readValue("[{\"v\":[3,3]}]", IntBean.class);
- fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled");
- } catch (JsonMappingException exp) {
- //threw exception as required
- }
-
- result = mapper.readValue("{\"v\":[null]}", IntBean.class);
- assertNotNull(result);
- assertEquals(0, result._v);
-
- array = mapper.readValue("[ [ null ] ]", int[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertEquals(0, array[0]);
- }
-
- public void testByteWrapper() throws Exception
- {
- Byte result = MAPPER.readValue(new StringReader(" -42\t"), Byte.class);
- assertEquals(Byte.valueOf((byte)-42), result);
-
- // Also: should be able to coerce floats, strings:
- result = MAPPER.readValue(new StringReader(" \"-12\""), Byte.class);
- assertEquals(Byte.valueOf((byte)-12), result);
-
- result = MAPPER.readValue(new StringReader(" 39.07"), Byte.class);
- assertEquals(Byte.valueOf((byte)39), result);
- }
-
- public void testShortWrapper() throws Exception
- {
- Short result = MAPPER.readValue(new StringReader("37"), Short.class);
- assertEquals(Short.valueOf((short)37), result);
-
- // Also: should be able to coerce floats, strings:
- result = MAPPER.readValue(new StringReader(" \"-1009\""), Short.class);
- assertEquals(Short.valueOf((short)-1009), result);
-
- result = MAPPER.readValue(new StringReader("-12.9"), Short.class);
- assertEquals(Short.valueOf((short)-12), result);
- }
-
- public void testCharacterWrapper() throws Exception
- {
- // First: canonical value is 1-char string
- Character result = MAPPER.readValue(new StringReader("\"a\""), Character.class);
- assertEquals(Character.valueOf('a'), result);
-
- // But can also pass in ascii code
- result = MAPPER.readValue(new StringReader(" "+((int) 'X')), Character.class);
- assertEquals(Character.valueOf('X'), result);
-
- final CharacterWrapperBean wrapper = MAPPER.readValue(new StringReader("{\"v\":null}"), CharacterWrapperBean.class);
- assertNotNull(wrapper);
- assertNull(wrapper.getV());
-
- final ObjectMapper mapper = new ObjectMapper();
- mapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
- try {
- mapper.readValue("{\"v\":null}", CharacterBean.class);
- fail("Attempting to deserialize a 'null' JSON reference into a 'char' property did not throw an exception");
- } catch (JsonMappingException exp) {
- //Exception thrown as required
- }
-
- mapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
- final CharacterBean charBean = MAPPER.readValue(new StringReader("{\"v\":null}"), CharacterBean.class);
- assertNotNull(wrapper);
- assertEquals('\u0000', charBean.getV());
- }
-
- public void testIntWrapper() throws Exception
- {
- Integer result = MAPPER.readValue(new StringReader(" -42\t"), Integer.class);
- assertEquals(Integer.valueOf(-42), result);
-
- // Also: should be able to coerce floats, strings:
- result = MAPPER.readValue(new StringReader(" \"-1200\""), Integer.class);
- assertEquals(Integer.valueOf(-1200), result);
-
- result = MAPPER.readValue(new StringReader(" 39.07"), Integer.class);
- assertEquals(Integer.valueOf(39), result);
- }
-
- public void testLongWrapper() throws Exception
- {
- Long result = MAPPER.readValue(new StringReader("12345678901"), Long.class);
- assertEquals(Long.valueOf(12345678901L), result);
-
- // Also: should be able to coerce floats, strings:
- result = MAPPER.readValue(new StringReader(" \"-9876\""), Long.class);
- assertEquals(Long.valueOf(-9876), result);
-
- result = MAPPER.readValue(new StringReader("1918.3"), Long.class);
- assertEquals(Long.valueOf(1918), result);
- }
-
- /**
- * Beyond simple case, let's also ensure that method overriding works as
- * expected.
- */
- public void testIntWithOverride() throws Exception
- {
- IntBean2 result = MAPPER.readValue(new StringReader("{\"v\":8}"), IntBean2.class);
- assertEquals(9, result._v);
- }
-
- /*
- /**********************************************************
- /* Scalar tests for floating point types
- /**********************************************************
- */
-
- public void testDoublePrimitive() throws Exception
- {
- // first, simple case:
- // bit tricky with binary fps but...
- final double value = 0.016;
- DoubleBean result = MAPPER.readValue(new StringReader("{\"v\":"+value+"}"), DoubleBean.class);
- assertEquals(value, result._v);
- // then [JACKSON-79]:
- result = MAPPER.readValue(new StringReader("{\"v\":null}"), DoubleBean.class);
- assertNotNull(result);
- assertEquals(0.0, result._v);
-
- // should work with arrays too..
- double[] array = MAPPER.readValue(new StringReader("[ null ]"), double[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertEquals(0.0, array[0]);
- }
-
- /* Note: dealing with floating-point values is tricky; not sure if
- * we can really use equality tests here... JDK does have decent
- * conversions though, to retain accuracy and round-trippability.
- * But still...
- */
- public void testFloatWrapper() throws Exception
- {
- // Also: should be able to coerce floats, strings:
- String[] STRS = new String[] {
- "1.0", "0.0", "-0.3", "0.7", "42.012", "-999.0", NAN_STRING
- };
-
- for (String str : STRS) {
- Float exp = Float.valueOf(str);
- Float result;
-
- if (NAN_STRING != str) {
- // First, as regular floating point value
- result = MAPPER.readValue(new StringReader(str), Float.class);
- assertEquals(exp, result);
- }
-
- // and then as coerced String:
- result = MAPPER.readValue(new StringReader(" \""+str+"\""), Float.class);
- assertEquals(exp, result);
- }
- }
-
- public void testDoubleWrapper() throws Exception
- {
- // Also: should be able to coerce doubles, strings:
- String[] STRS = new String[] {
- "1.0", "0.0", "-0.3", "0.7", "42.012", "-999.0", NAN_STRING
- };
-
- for (String str : STRS) {
- Double exp = Double.valueOf(str);
- Double result;
-
- // First, as regular double value
- if (NAN_STRING != str) {
- result = MAPPER.readValue(str, Double.class);
- assertEquals(exp, result);
- }
- // and then as coerced String:
- result = MAPPER.readValue(new StringReader(" \""+str+"\""), Double.class);
- assertEquals(exp, result);
- }
- }
-
- public void testDoubleAsArray() throws Exception
- {
- final ObjectMapper mapper = new ObjectMapper();
- mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
- final double value = 0.016;
- try {
- mapper.readValue(new StringReader("{\"v\":[" + value + "]}"), DoubleBean.class);
- fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled");
- } catch (JsonMappingException exp) {
- //Correctly threw exception
- }
-
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- DoubleBean result = mapper.readValue(new StringReader("{\"v\":[" + value + "]}"),
- DoubleBean.class);
- assertEquals(value, result._v);
-
- result = mapper.readValue(new StringReader("[{\"v\":[" + value + "]}]"), DoubleBean.class);
- assertEquals(value, result._v);
-
- try {
- mapper.readValue(new StringReader("[{\"v\":[" + value + "," + value + "]}]"), DoubleBean.class);
- fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled");
- } catch (JsonMappingException exp) {
- //threw exception as required
- }
-
- result = mapper.readValue(new StringReader("{\"v\":[null]}"), DoubleBean.class);
- assertNotNull(result);
- assertEquals(0d, result._v);
-
- double[] array = mapper.readValue(new StringReader("[ [ null ] ]"), double[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertEquals(0d, array[0]);
- }
-
- public void testDoublePrimitiveNonNumeric() throws Exception
- {
- // first, simple case:
- // bit tricky with binary fps but...
- double value = Double.POSITIVE_INFINITY;
- DoubleBean result = MAPPER.readValue(new StringReader("{\"v\":\""+value+"\"}"), DoubleBean.class);
- assertEquals(value, result._v);
-
- // should work with arrays too..
- double[] array = MAPPER.readValue(new StringReader("[ \"Infinity\" ]"), double[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertEquals(Double.POSITIVE_INFINITY, array[0]);
- }
-
- public void testFloatPrimitiveNonNumeric() throws Exception
- {
- // bit tricky with binary fps but...
- float value = Float.POSITIVE_INFINITY;
- FloatBean result = MAPPER.readValue(new StringReader("{\"v\":\""+value+"\"}"), FloatBean.class);
- assertEquals(value, result._v);
-
- // should work with arrays too..
- float[] array = MAPPER.readValue(new StringReader("[ \"Infinity\" ]"), float[].class);
- assertNotNull(array);
- assertEquals(1, array.length);
- assertEquals(Float.POSITIVE_INFINITY, array[0]);
- }
-
- /*
- /**********************************************************
- /* Scalar tests, other
- /**********************************************************
- */
-
- 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 (JsonMappingException e) {
- verifyException(e, "Can not map Empty String");
- }
- }
-
- public void testBase64Variants() throws Exception
- {
- final byte[] INPUT = "abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890X".getBytes("UTF-8");
-
- // default encoding is "MIME, no linefeeds", so:
- Assert.assertArrayEquals(INPUT, MAPPER.readValue(
- quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA=="),
- byte[].class));
- ObjectReader reader = MAPPER.readerFor(byte[].class);
- Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.MIME_NO_LINEFEEDS).readValue(
- quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA=="
- )));
-
- // but others should be slightly different
- Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.MIME).readValue(
- quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1\\ndnd4eXoxMjM0NTY3ODkwWA=="
- )));
- Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.MODIFIED_FOR_URL).readValue(
- quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA"
- )));
- // PEM mandates 64 char lines:
- Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.PEM).readValue(
- quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamts\\nbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA=="
- )));
- }
- /*
- /**********************************************************
- /* Simple non-primitive types
- /**********************************************************
- */
-
- public void testSingleString() throws Exception
- {
- String value = "FOO!";
- String result = MAPPER.readValue(new StringReader("\""+value+"\""), String.class);
- assertEquals(value, result);
- }
-
- public void testSingleStringWrapped() throws Exception
- {
- final ObjectMapper mapper = new ObjectMapper();
- mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- String value = "FOO!";
- try {
- mapper.readValue(new StringReader("[\""+value+"\"]"), String.class);
- fail("Exception not thrown when attempting to unwrap a single value 'String' array into a simple String");
- } catch (JsonMappingException exp) {
- //exception thrown correctly
- }
-
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- try {
- mapper.readValue(new StringReader("[\""+value+"\",\""+value+"\"]"), String.class);
- fail("Exception not thrown when attempting to unwrap a single value 'String' array that contained more than one value into a simple String");
- } catch (JsonMappingException exp) {
- //exception thrown correctly
- }
-
- String result = mapper.readValue(new StringReader("[\""+value+"\"]"), String.class);
- assertEquals(value, result);
- }
-
- public void testBigDecimal() throws Exception
- {
- final ObjectMapper mapper = objectMapper();
- mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- BigDecimal value = new BigDecimal("0.001");
- BigDecimal result = mapper.readValue(value.toString(), BigDecimal.class);
- assertEquals(value, result);
- try {
- mapper.readValue("[" + value.toString() + "]", BigDecimal.class);
- fail("Exception was not thrown when attempting to read a single value array of BigDecimal when UNWRAP_SINGLE_VALUE_ARRAYS feature is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
- result = mapper.readValue("[" + value.toString() + "]", BigDecimal.class);
- assertEquals(value, result);
-
- try {
- mapper.readValue("[" + value.toString() + "," + value.toString() + "]", BigDecimal.class);
- fail("Exception was not thrown when attempting to read a muti value array of BigDecimal when UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- }
-
- public void testBigInteger() throws Exception
- {
- final ObjectMapper mapper = objectMapper();
- mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- BigInteger value = new BigInteger("-1234567890123456789012345567809");
- BigInteger result = mapper.readValue(new StringReader(value.toString()), BigInteger.class);
- assertEquals(value, result);
-
- //Issue#381
- try {
- mapper.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 (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
- result = mapper.readValue("[" + value.toString() + "]", BigInteger.class);
- assertEquals(value, result);
-
- try {
- mapper.readValue("[" + value.toString() + "," + value.toString() + "]", BigInteger.class);
- fail("Exception was not thrown when attempting to read a muti value array of BigInteger when UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- }
-
- /*
- /**********************************************************
- /* Sequence tests
- /**********************************************************
- */
-
- /**
- * Then a unit test to verify that we can conveniently bind sequence of
- * space-separate simple values
- */
- public void testSequenceOfInts() throws Exception
- {
- final int NR_OF_INTS = 100;
-
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < NR_OF_INTS; ++i) {
- sb.append(" ");
- sb.append(i);
- }
- JsonParser jp = MAPPER.getFactory().createParser(sb.toString());
- for (int i = 0; i < NR_OF_INTS; ++i) {
- Integer result = MAPPER.readValue(jp, Integer.class);
- assertEquals(Integer.valueOf(i), result);
- }
- jp.close();
- }
-
- /*
- /**********************************************************
- /* Single-element as array tests
- /**********************************************************
- */
-
- // [databind#381]
- public void testSingleElementScalarArrays() throws Exception {
- final int intTest = 932832;
- final double doubleTest = 32.3234;
- final long longTest = 2374237428374293423L;
- final short shortTest = (short) intTest;
- final float floatTest = 84.3743f;
- final byte byteTest = (byte) 43;
- final char charTest = 'c';
-
- final ObjectMapper mapper = new ObjectMapper();
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- final int intValue = mapper.readValue(asArray(intTest), Integer.TYPE);
- assertEquals(intTest, intValue);
- final Integer integerWrapperValue = mapper.readValue(asArray(Integer.valueOf(intTest)), Integer.class);
- assertEquals(Integer.valueOf(intTest), integerWrapperValue);
-
- final double doubleValue = mapper.readValue(asArray(doubleTest), Double.class);
- assertEquals(doubleTest, doubleValue);
- final Double doubleWrapperValue = mapper.readValue(asArray(Double.valueOf(doubleTest)), Double.class);
- assertEquals(Double.valueOf(doubleTest), doubleWrapperValue);
-
- final long longValue = mapper.readValue(asArray(longTest), Long.TYPE);
- assertEquals(longTest, longValue);
- final Long longWrapperValue = mapper.readValue(asArray(Long.valueOf(longTest)), Long.class);
- assertEquals(Long.valueOf(longTest), longWrapperValue);
-
- final short shortValue = mapper.readValue(asArray(shortTest), Short.TYPE);
- assertEquals(shortTest, shortValue);
- final Short shortWrapperValue = mapper.readValue(asArray(Short.valueOf(shortTest)), Short.class);
- assertEquals(Short.valueOf(shortTest), shortWrapperValue);
-
- final float floatValue = mapper.readValue(asArray(floatTest), Float.TYPE);
- assertEquals(floatTest, floatValue);
- final Float floatWrapperValue = mapper.readValue(asArray(Float.valueOf(floatTest)), Float.class);
- assertEquals(Float.valueOf(floatTest), floatWrapperValue);
-
- final byte byteValue = mapper.readValue(asArray(byteTest), Byte.TYPE);
- assertEquals(byteTest, byteValue);
- final Byte byteWrapperValue = mapper.readValue(asArray(Byte.valueOf(byteTest)), Byte.class);
- assertEquals(Byte.valueOf(byteTest), byteWrapperValue);
-
- final char charValue = mapper.readValue(asArray(quote(String.valueOf(charTest))), Character.TYPE);
- assertEquals(charTest, charValue);
- final Character charWrapperValue = mapper.readValue(asArray(quote(String.valueOf(charTest))), Character.class);
- assertEquals(Character.valueOf(charTest), charWrapperValue);
-
- final boolean booleanTrueValue = mapper.readValue(asArray(true), Boolean.TYPE);
- assertTrue(booleanTrueValue);
-
- final boolean booleanFalseValue = mapper.readValue(asArray(false), Boolean.TYPE);
- assertFalse(booleanFalseValue);
-
- final Boolean booleanWrapperTrueValue = mapper.readValue(asArray(Boolean.valueOf(true)), Boolean.class);
- assertEquals(Boolean.TRUE, booleanWrapperTrueValue);
- }
-
- public void testSingleElementArrayDisabled() throws Exception {
- final ObjectMapper mapper = new ObjectMapper();
- mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
- try {
- mapper.readValue("[42]", Integer.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42]", Integer.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[42.273]", Double.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42.2723]", Double.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[42342342342342]", Long.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42342342342342342]", Long.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[42]", Short.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42]", Short.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[327.2323]", Float.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[82.81902]", Float.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[22]", Byte.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[22]", Byte.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("['d']", Character.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("['d']", Character.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[true]", Boolean.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[true]", Boolean.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- }
-
- public void testMultiValueArrayException() throws IOException {
- final ObjectMapper mapper = new ObjectMapper();
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- try {
- mapper.readValue("[42,42]", Integer.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42,42]", Integer.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[42.273,42.273]", Double.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42.2723,42.273]", Double.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[42342342342342,42342342342342]", Long.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42342342342342342,42342342342342]", Long.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[42,42]", Short.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[42,42]", Short.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[327.2323,327.2323]", Float.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[82.81902,327.2323]", Float.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[22,23]", Byte.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[22,23]", Byte.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue(asArray(quote("c") + "," + quote("d")), Character.class);
-
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue(asArray(quote("c") + "," + quote("d")), Character.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
-
- try {
- mapper.readValue("[true,false]", Boolean.class);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- try {
- mapper.readValue("[true,false]", Boolean.TYPE);
- fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException exp) {
- //Exception was thrown correctly
- }
- }
-
- private static String asArray(Object value) {
- final String stringVal = value.toString();
- return new StringBuilder(stringVal.length() + 2).append("[").append(stringVal).append("]").toString();
- }
-
- /*
- /**********************************************************
- /* Empty String coercion, handling
- /**********************************************************
- */
-
- // by default, should return nulls, n'est pas?
- public void testEmptyStringForWrappers() throws IOException
- {
- WrappersBean bean;
-
- // by default, ok to rely on defaults
- bean = MAPPER.readValue("{\"booleanValue\":\"\"}", WrappersBean.class);
- assertNull(bean.booleanValue);
- bean = MAPPER.readValue("{\"byteValue\":\"\"}", WrappersBean.class);
- assertNull(bean.byteValue);
-
- // char/Character is different... not sure if this should work or not:
- bean = MAPPER.readValue("{\"charValue\":\"\"}", WrappersBean.class);
- assertNull(bean.charValue);
-
- bean = MAPPER.readValue("{\"shortValue\":\"\"}", WrappersBean.class);
- assertNull(bean.shortValue);
- bean = MAPPER.readValue("{\"intValue\":\"\"}", WrappersBean.class);
- assertNull(bean.intValue);
- bean = MAPPER.readValue("{\"longValue\":\"\"}", WrappersBean.class);
- assertNull(bean.longValue);
- bean = MAPPER.readValue("{\"floatValue\":\"\"}", WrappersBean.class);
- assertNull(bean.floatValue);
- bean = MAPPER.readValue("{\"doubleValue\":\"\"}", WrappersBean.class);
- assertNull(bean.doubleValue);
- }
-
- public void testEmptyStringForPrimitives() throws IOException
- {
- PrimitivesBean bean;
- bean = MAPPER.readValue("{\"booleanValue\":\"\"}", PrimitivesBean.class);
- assertFalse(bean.booleanValue);
- bean = MAPPER.readValue("{\"byteValue\":\"\"}", PrimitivesBean.class);
- assertEquals((byte) 0, bean.byteValue);
- bean = MAPPER.readValue("{\"charValue\":\"\"}", PrimitivesBean.class);
- assertEquals((char) 0, bean.charValue);
- bean = MAPPER.readValue("{\"shortValue\":\"\"}", PrimitivesBean.class);
- assertEquals((short) 0, bean.shortValue);
- bean = MAPPER.readValue("{\"intValue\":\"\"}", PrimitivesBean.class);
- assertEquals(0, bean.intValue);
- bean = MAPPER.readValue("{\"longValue\":\"\"}", PrimitivesBean.class);
- assertEquals(0L, bean.longValue);
- bean = MAPPER.readValue("{\"floatValue\":\"\"}", PrimitivesBean.class);
- assertEquals(0.0f, bean.floatValue);
- bean = MAPPER.readValue("{\"doubleValue\":\"\"}", PrimitivesBean.class);
- assertEquals(0.0, bean.doubleValue);
- }
-}
-
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/KeyDeser1429Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/KeyDeser1429Test.java
deleted file mode 100644
index 7922928..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/deser/KeyDeser1429Test.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.fasterxml.jackson.databind.deser;
-
-import java.util.Map;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.*;
-
-public class KeyDeser1429Test extends BaseMapTest
-{
- static class FullName {
- private String _firstname, _lastname;
-
- private FullName(String firstname, String lastname) {
- _firstname = firstname;
- _lastname = lastname;
- }
-
- @JsonCreator
- public static FullName valueOf(String value) {
- String[] mySplit = value.split("\\.");
- return new FullName(mySplit[0], mySplit[1]);
- }
-
- public static FullName valueOf(String firstname, String lastname) {
- return new FullName(firstname, lastname);
- }
-
- @JsonValue
- @Override
- public String toString() {
- return _firstname + "." + _lastname;
- }
- }
-
- public void testDeserializeKeyViaFactory() throws Exception
- {
- Map<FullName, Double> map =
- new ObjectMapper().readValue("{\"first.last\": 42}",
- new TypeReference<Map<FullName, Double>>() { });
- Map.Entry<FullName, Double> entry = map.entrySet().iterator().next();
- FullName key = entry.getKey();
- assertEquals(key._firstname, "first");
- assertEquals(key._lastname, "last");
- assertEquals(entry.getValue().doubleValue(), 42, 0);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/NullHandlingTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/NullHandlingTest.java
index 21eeb3c..836e786 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/NullHandlingTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/NullHandlingTest.java
@@ -12,7 +12,6 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.deser.JDKScalarsTest.PrimitivesBean;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class NullHandlingTest extends BaseMapTest
@@ -138,82 +137,6 @@
assertEquals("funny", str);
}
- public void testNullForPrimitives() throws IOException
- {
- // by default, ok to rely on defaults
- PrimitivesBean bean = MAPPER.readValue(
- "{\"intValue\":null, \"booleanValue\":null, \"doubleValue\":null}",
- PrimitivesBean.class);
- assertNotNull(bean);
- assertEquals(0, bean.intValue);
- assertEquals(false, bean.booleanValue);
- assertEquals(0.0, bean.doubleValue);
-
- bean = MAPPER.readValue("{\"byteValue\":null, \"longValue\":null, \"floatValue\":null}",
- PrimitivesBean.class);
- assertNotNull(bean);
- assertEquals((byte) 0, bean.byteValue);
- assertEquals(0L, bean.longValue);
- assertEquals(0.0f, bean.floatValue);
-
- // but not when enabled
- final ObjectReader reader = MAPPER
- .readerFor(PrimitivesBean.class)
- .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
- // boolean
- try {
- reader.readValue("{\"booleanValue\":null}");
- fail("Expected failure for boolean + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type boolean");
- }
- // byte/char/short/int/long
- try {
- reader.readValue("{\"byteValue\":null}");
- fail("Expected failure for byte + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type byte");
- }
- try {
- reader.readValue("{\"charValue\":null}");
- fail("Expected failure for char + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type char");
- }
- try {
- reader.readValue("{\"shortValue\":null}");
- fail("Expected failure for short + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type short");
- }
- try {
- reader.readValue("{\"intValue\":null}");
- fail("Expected failure for int + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type int");
- }
- try {
- reader.readValue("{\"longValue\":null}");
- fail("Expected failure for long + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type long");
- }
-
- // float/double
- try {
- reader.readValue("{\"floatValue\":null}");
- fail("Expected failure for float + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type float");
- }
- try {
- reader.readValue("{\"doubleValue\":null}");
- fail("Expected failure for double + null");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not map JSON null into type double");
- }
- }
-
// [databind#407]
public void testListOfNulls() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/PropertyAliasTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/PropertyAliasTest.java
new file mode 100644
index 0000000..3cc17ff
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/PropertyAliasTest.java
@@ -0,0 +1,84 @@
+package com.fasterxml.jackson.databind.deser;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class PropertyAliasTest extends BaseMapTest
+{
+ static class AliasBean {
+ @JsonAlias({ "nm", "Name" })
+ public String name;
+
+ int _xyz;
+
+ int _a;
+
+ @JsonCreator
+ public AliasBean(@JsonProperty("a")
+ @JsonAlias("A") int a) {
+ _a = a;
+ }
+
+ @JsonAlias({ "Xyz" })
+ public void setXyz(int x) {
+ _xyz = x;
+ }
+ }
+
+ static class PolyWrapperForAlias {
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.WRAPPER_ARRAY)
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = AliasBean.class,name = "ab"),
+ })
+ public Object value;
+
+ protected PolyWrapperForAlias() { }
+ public PolyWrapperForAlias(Object v) { value = v; }
+ }
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ // [databind#1029]
+ public void testSimpleAliases() throws Exception
+ {
+ AliasBean bean;
+
+ // first, one indicated by field annotation, set via field
+ bean = MAPPER.readValue(aposToQuotes("{'Name':'Foobar','a':3,'xyz':37}"),
+ AliasBean.class);
+ assertEquals("Foobar", bean.name);
+ assertEquals(3, bean._a);
+ assertEquals(37, bean._xyz);
+
+ // then method-bound one
+ bean = MAPPER.readValue(aposToQuotes("{'name':'Foobar','a':3,'Xyz':37}"),
+ AliasBean.class);
+ assertEquals("Foobar", bean.name);
+ assertEquals(3, bean._a);
+ assertEquals(37, bean._xyz);
+
+ // and finally, constructor-backed one
+ bean = MAPPER.readValue(aposToQuotes("{'name':'Foobar','A':3,'xyz':37}"),
+ AliasBean.class);
+ assertEquals("Foobar", bean.name);
+ assertEquals(3, bean._a);
+ assertEquals(37, bean._xyz);
+ }
+
+ public void testAliasWithPolymorphic() throws Exception
+ {
+ PolyWrapperForAlias value = MAPPER.readValue(aposToQuotes(
+ "{'value': ['ab', {'nm' : 'Bob', 'A' : 17} ] }"
+ ), PolyWrapperForAlias.class);
+ assertNotNull(value.value);
+ AliasBean bean = (AliasBean) value.value;
+ assertEquals("Bob", bean.name);
+ assertEquals(17, bean._a);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeser1805Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeser1805Test.java
new file mode 100644
index 0000000..a8f68d0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/ReadOnlyDeser1805Test.java
@@ -0,0 +1,78 @@
+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 = newObjectMapper();
+
+ 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/TestAbstract.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestAbstract.java
deleted file mode 100644
index 28830fe..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestAbstract.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.fasterxml.jackson.databind.deser;
-
-
-import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.*;
-
-/**
- * Tests for checking handling of abstract types.
- */
-public class TestAbstract
- extends BaseMapTest
-{
- static abstract class Abstract {
- public int x;
- }
-
- /*
- /**********************************************************
- /* Unit tests
- /**********************************************************
- */
-
- /**
- * Test to verify details of how trying to deserialize into
- * abstract type should fail (if there is no way to determine
- * actual type information for the concrete type to use)
- */
- public void testAbstractFailure() throws Exception
- {
- ObjectMapper m = new ObjectMapper();
- try {
- m.readValue("{ \"x\" : 3 }", Abstract.class);
- fail("Should fail on trying to deserialize abstract type");
- } catch (JsonProcessingException e) {
- verifyException(e, "can not construct");
- }
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestBasicAnnotations.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestBasicAnnotations.java
index 1240bd3..4796e40 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestBasicAnnotations.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestBasicAnnotations.java
@@ -3,7 +3,7 @@
import java.io.*;
import com.fasterxml.jackson.annotation.*;
-
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -18,12 +18,6 @@
public class TestBasicAnnotations
extends BaseMapTest
{
- /*
- /**********************************************************
- /* Annotated helper classes
- /**********************************************************
- */
-
/// Class for testing {@link JsonProperty} annotations
final static class SizeClassSetter
{
@@ -88,6 +82,24 @@
@JsonDeserialize protected int a;
}
+ @JsonAutoDetect(setterVisibility=Visibility.NONE)
+ final static class Dummy { }
+
+ final static class EmptyDummy { }
+
+ static class AnnoBean {
+ int value = 3;
+
+ @JsonProperty("y")
+ public void setX(int v) { value = v; }
+ }
+
+ enum Alpha { A, B, C; }
+
+ public static class SimpleBean {
+ public int x, y;
+ }
+
/*
/**********************************************************
/* Other helper classes
@@ -104,10 +116,10 @@
return new int[] { jp.getIntValue() };
}
}
-
+
/*
/**********************************************************
- /* Test methods
+ /* Test methods, basic
/**********************************************************
*/
@@ -168,4 +180,45 @@
Issue442Bean bean = MAPPER.readValue("{\"i\":5}", Issue442Bean.class);
assertEquals(5, bean.w.i);
}
+
+ /*
+ /**********************************************************
+ /* Test methods, annotations disabled
+ /**********************************************************
+ */
+
+ public void testAnnotationsDisabled() throws Exception
+ {
+ // first: verify that annotation introspection is enabled by default
+ assertTrue(MAPPER.getDeserializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS));
+ // with annotations, property is renamed
+ AnnoBean bean = MAPPER.readValue("{ \"y\" : 0 }", AnnoBean.class);
+ assertEquals(0, bean.value);
+
+ ObjectMapper m = new ObjectMapper();
+ m.configure(MapperFeature.USE_ANNOTATIONS, false);
+ // without annotations, should default to default bean-based name...
+ bean = m.readValue("{ \"x\" : 0 }", AnnoBean.class);
+ assertEquals(0, bean.value);
+ }
+
+ public void testEnumsWhenDisabled() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ assertEquals(Alpha.B, m.readValue(quote("B"), Alpha.class));
+
+ m = new ObjectMapper();
+ m.configure(MapperFeature.USE_ANNOTATIONS, false);
+ // should still use the basic name handling here
+ assertEquals(Alpha.B, m.readValue(quote("B"), Alpha.class));
+ }
+
+ public void testNoAccessOverrides() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ m.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS);
+ SimpleBean bean = m.readValue("{\"x\":1,\"y\":2}", SimpleBean.class);
+ assertEquals(1, bean.x);
+ assertEquals(2, bean.y);
+ }
}
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 cd1a506..988ece3 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java
@@ -3,11 +3,10 @@
import java.io.IOException;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.deser.BeanDeserializer;
-import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
-import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
@@ -18,11 +17,9 @@
@SuppressWarnings("serial")
public class TestBeanDeserializer extends BaseMapTest
{
- /*
- /**********************************************************
- /* Helper types
- /**********************************************************
- */
+ static abstract class Abstract {
+ public int x;
+ }
static class Bean {
public String b = "b";
@@ -141,7 +138,92 @@
context.addBeanDeserializerModifier(new Issue476DeserializerModifier());
}
}
-
+
+ public static class Issue1912Bean {
+ public Issue1912SubBean subBean;
+
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) // This is need to populate _propertyBasedCreator on BeanDeserializerBase
+ public Issue1912Bean(@JsonProperty("subBean") Issue1912SubBean subBean) {
+ this.subBean = subBean;
+ }
+ }
+ public static class Issue1912SubBean {
+ public String a;
+
+ public Issue1912SubBean() { }
+
+ public Issue1912SubBean(String a) {
+ this.a = a;
+ }
+ }
+
+ public static class Issue1912CustomBeanDeserializer extends JsonDeserializer<Issue1912Bean> {
+ private BeanDeserializer defaultDeserializer;
+
+ public Issue1912CustomBeanDeserializer(BeanDeserializer defaultDeserializer) {
+ this.defaultDeserializer = defaultDeserializer;
+ }
+
+ @Override
+ public Issue1912Bean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ // this is need on some cases, this populate _propertyBasedCreator
+ defaultDeserializer.resolve(ctxt);
+
+ p.nextFieldName(); // read subBean
+ p.nextToken(); // read start object
+
+ Issue1912SubBean subBean = (Issue1912SubBean) defaultDeserializer.findProperty("subBean").deserialize(p, ctxt);
+
+ return new Issue1912Bean(subBean);
+ }
+ }
+
+ public static class Issue1912CustomPropertyDeserializer extends JsonDeserializer<Issue1912SubBean> {
+
+ @Override
+ public Issue1912SubBean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ p.nextFieldName(); // read "a"
+ Issue1912SubBean object = new Issue1912SubBean(p.nextTextValue() + "_custom");
+ p.nextToken();
+ return object;
+ }
+ }
+ public static class Issue1912UseAddOrReplacePropertyDeserializerModifier extends BeanDeserializerModifier {
+
+ @Override
+ public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ if (beanDesc.getBeanClass() == Issue1912Bean.class) {
+ return new Issue1912CustomBeanDeserializer((BeanDeserializer) deserializer);
+ }
+ return super.modifyDeserializer(config, beanDesc, deserializer);
+ }
+
+ @Override
+ public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
+ if (beanDesc.getBeanClass() == Issue1912Bean.class) {
+ Iterator<SettableBeanProperty> props = builder.getProperties();
+ while (props.hasNext()) {
+ SettableBeanProperty prop = props.next();
+ SettableBeanProperty propWithCustomDeserializer = prop.withValueDeserializer(new Issue1912CustomPropertyDeserializer());
+ builder.addOrReplaceProperty(propWithCustomDeserializer, true);
+ }
+ }
+
+ return builder;
+ }
+ }
+ public class Issue1912Module extends SimpleModule {
+
+ public Issue1912Module() {
+ super("Issue1912Module", Version.unknownVersion());
+ }
+
+ @Override
+ public void setupModule(SetupContext context) {
+ context.addBeanDeserializerModifier(new Issue1912UseAddOrReplacePropertyDeserializerModifier());
+ }
+ }
+
// [Issue#121], arrays, collections, maps
enum EnumABC { A, B, C; }
@@ -242,6 +324,20 @@
private final ObjectMapper MAPPER = new ObjectMapper();
+ /**
+ * Test to verify details of how trying to deserialize into
+ * abstract type should fail (if there is no way to determine
+ * actual type information for the concrete type to use)
+ */
+ public void testAbstractFailure() throws Exception
+ {
+ try {
+ MAPPER.readValue("{ \"x\" : 3 }", Abstract.class);
+ fail("Should fail on trying to deserialize abstract type");
+ } catch (JsonProcessingException e) {
+ verifyException(e, "cannot construct");
+ }
+ }
public void testPropertyRemoval() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
@@ -368,4 +464,11 @@
assertEquals("ABCDEF", result);
}
+ public void testAddOrReplacePropertyIsUsedOnDeserialization() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new Issue1912Module());
+
+ Issue1912Bean result = mapper.readValue("{\"subBean\": {\"a\":\"foo\"}}", Issue1912Bean.class);
+ assertEquals("foo_custom", result.subBean.a);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestConfig.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestConfig.java
deleted file mode 100644
index 6b2dbf4..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestConfig.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package com.fasterxml.jackson.databind.deser;
-
-import com.fasterxml.jackson.annotation.*;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
-
-/**
- * Unit tests for checking handling of DeserializationConfig.
- */
-public class TestConfig
- extends BaseMapTest
-{
- @JsonAutoDetect(setterVisibility=Visibility.NONE)
- final static class Dummy { }
-
- final static class EmptyDummy { }
-
- static class AnnoBean {
- int value = 3;
-
- @JsonProperty("y")
- public void setX(int v) { value = v; }
- }
-
- enum Alpha { A, B, C; }
-
- public static class SimpleBean {
- public int x, y;
- }
-
- /*
- /**********************************************************
- /* Main tests
- /**********************************************************
- */
-
- /* Test to verify that we don't overflow number of features; if we
- * hit the limit, need to change implementation -- this test just
- * gives low-water mark
- */
- public void testEnumIndexes()
- {
- int max = 0;
-
- for (DeserializationFeature f : DeserializationFeature.values()) {
- max = Math.max(max, f.ordinal());
- }
- if (max >= 31) { // 31 is actually ok; 32 not
- fail("Max number of DeserializationFeature enums reached: "+max);
- }
- }
-
- public void testDefaults()
- {
- ObjectMapper m = new ObjectMapper();
- DeserializationConfig cfg = m.getDeserializationConfig();
-
- // Expected defaults:
- assertTrue(cfg.isEnabled(MapperFeature.USE_ANNOTATIONS));
- assertTrue(cfg.isEnabled(MapperFeature.AUTO_DETECT_SETTERS));
- assertTrue(cfg.isEnabled(MapperFeature.AUTO_DETECT_CREATORS));
- assertTrue(cfg.isEnabled(MapperFeature.USE_GETTERS_AS_SETTERS));
- assertTrue(cfg.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS));
-
-
- assertFalse(cfg.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS));
- assertFalse(cfg.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS));
-
- assertTrue(cfg.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
- }
-
- public void testOverrideIntrospectors()
- {
- ObjectMapper m = new ObjectMapper();
- DeserializationConfig cfg = m.getDeserializationConfig();
- // and finally, ensure we could override introspectors
- cfg = cfg.with((ClassIntrospector) null); // no way to verify tho
- cfg = cfg.with((AnnotationIntrospector) null);
- assertNull(cfg.getAnnotationIntrospector());
- }
-
- public void testAnnotationsDisabled() throws Exception
- {
- // first: verify that annotation introspection is enabled by default
- ObjectMapper m = new ObjectMapper();
- assertTrue(m.getDeserializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS));
- // with annotations, property is renamed
- AnnoBean bean = m.readValue("{ \"y\" : 0 }", AnnoBean.class);
- assertEquals(0, bean.value);
-
- m = new ObjectMapper();
- m.configure(MapperFeature.USE_ANNOTATIONS, false);
- // without annotations, should default to default bean-based name...
- bean = m.readValue("{ \"x\" : 0 }", AnnoBean.class);
- assertEquals(0, bean.value);
- }
-
- // [JACKSON-875]
- public void testEnumsWhenDisabled() throws Exception
- {
- ObjectMapper m = new ObjectMapper();
- assertEquals(Alpha.B, m.readValue(quote("B"), Alpha.class));
-
- m = new ObjectMapper();
- m.configure(MapperFeature.USE_ANNOTATIONS, false);
- // should still use the basic name handling here
- assertEquals(Alpha.B, m.readValue(quote("B"), Alpha.class));
- }
-
- public void testNoAccessOverrides() throws Exception
- {
- ObjectMapper m = new ObjectMapper();
- m.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS);
- SimpleBean bean = m.readValue("{\"x\":1,\"y\":2}", SimpleBean.class);
- assertEquals(1, bean.x);
- assertEquals(2, bean.y);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java
index 58c5251..f85d995 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java
@@ -6,7 +6,9 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+
import com.fasterxml.jackson.core.*;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.*;
import com.fasterxml.jackson.databind.deser.std.*;
@@ -37,11 +39,11 @@
}
@Override
- public T deserialize(JsonParser jp, DeserializationContext ctxt)
- throws IOException, JsonProcessingException
+ public T deserialize(JsonParser p, DeserializationContext ctxt)
+ throws IOException
{
// need to skip, if structured...
- jp.skipChildren();
+ p.skipChildren();
return value;
}
}
@@ -65,29 +67,29 @@
static class CustomBeanDeserializer extends JsonDeserializer<CustomBean>
{
@Override
- public CustomBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
+ public CustomBean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
int a = 0, b = 0;
- JsonToken t = jp.getCurrentToken();
+ JsonToken t = p.getCurrentToken();
if (t == JsonToken.START_OBJECT) {
- t = jp.nextToken();
+ t = p.nextToken();
} else if (t != JsonToken.FIELD_NAME) {
throw new Error();
}
while(t == JsonToken.FIELD_NAME) {
- final String fieldName = jp.getCurrentName();
- t = jp.nextToken();
+ final String fieldName = p.getCurrentName();
+ t = p.nextToken();
if (t != JsonToken.VALUE_NUMBER_INT) {
- throw new JsonParseException(jp, "expecting number got "+ t);
+ throw new JsonParseException(p, "expecting number got "+ t);
}
if (fieldName.equals("a")) {
- a = jp.getIntValue();
+ a = p.getIntValue();
} else if (fieldName.equals("b")) {
- b = jp.getIntValue();
+ b = p.getIntValue();
} else {
throw new Error();
}
- t = jp.nextToken();
+ t = p.nextToken();
}
return new CustomBean(a, b);
}
@@ -102,7 +104,6 @@
}
}
- // [JACKSON-882]
public static class CustomKey {
private final int id;
@@ -130,8 +131,8 @@
static class CustomKeySerializer extends JsonSerializer<CustomKey> {
@Override
- public void serialize(CustomKey value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
- jgen.writeFieldName(String.valueOf(value.getId()));
+ public void serialize(CustomKey value, JsonGenerator g, SerializerProvider provider) throws IOException {
+ g.writeFieldName(String.valueOf(value.getId()));
}
}
@@ -142,7 +143,7 @@
}
}
- // [#375]
+ // [databind#375]
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@@ -201,9 +202,9 @@
}
@Override
- public Bean375Inner deserialize(JsonParser jp, DeserializationContext ctxt)
+ public Bean375Inner deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
- int x = jp.getIntValue();
+ int x = p.getIntValue();
if (negative) {
x = -x;
} else {
@@ -257,7 +258,55 @@
return p.getText().toUpperCase();
}
}
-
+
+ static class DelegatingModuleImpl extends SimpleModule
+ {
+ public DelegatingModuleImpl() {
+ super("test", Version.unknownVersion());
+ }
+
+ @Override
+ public void setupModule(SetupContext context)
+ {
+ super.setupModule(context);
+ context.addBeanDeserializerModifier(new BeanDeserializerModifier() {
+ @Override
+ public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
+ BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ if (deserializer.handledType() == String.class) {
+ JsonDeserializer<?> d = new MyStringDeserializer(deserializer);
+ // just for test coverage purposes...
+ if (d.getDelegatee() != deserializer) {
+ throw new Error("Cannot access delegatee!");
+ }
+ return d;
+ }
+ return deserializer;
+ }
+ });
+ }
+ }
+
+ static class MyStringDeserializer extends DelegatingDeserializer
+ {
+ public MyStringDeserializer(JsonDeserializer<?> newDel) {
+ super(newDel);
+ }
+
+ @Override
+ protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDel) {
+ return new MyStringDeserializer(newDel);
+ }
+
+ @Override
+ public Object deserialize(JsonParser p, DeserializationContext ctxt)
+ throws IOException
+ {
+ Object ob = _delegatee.deserialize(p, ctxt);
+ return "MY:"+ob;
+ }
+ }
+
/*
/**********************************************************
/* Unit tests
@@ -399,4 +448,12 @@
assertNotNull(sw);
assertEquals("FOO", sw.str);
}
+
+ public void testDelegatingDeserializer() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper().registerModule(
+ new DelegatingModuleImpl());
+ String str = mapper.readValue(quote("foo"), String.class);
+ assertEquals("MY:foo", str);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestGenericCollectionDeser.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestGenericCollectionDeser.java
index f99bbb2..a87670e 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestGenericCollectionDeser.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestGenericCollectionDeser.java
@@ -2,6 +2,8 @@
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -9,12 +11,6 @@
public class TestGenericCollectionDeser
extends BaseMapTest
{
- /*
- /**********************************************************
- /* Test classes, enums
- /**********************************************************
- */
-
static class ListSubClass extends ArrayList<StringWrapper> { }
/**
@@ -24,15 +20,18 @@
@JsonDeserialize(contentAs=StringWrapper.class)
static class AnnotatedStringList extends ArrayList<Object> { }
- @JsonDeserialize(contentAs=BooleanWrapper.class)
+ @JsonDeserialize(contentAs=BooleanElement.class)
static class AnnotatedBooleanList extends ArrayList<Object> { }
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
+ protected static class BooleanElement {
+ public Boolean b;
+ @JsonCreator
+ public BooleanElement(Boolean value) { b = value; }
+
+ @JsonValue public Boolean value() { return b; }
+ }
+
/*
/**********************************************************
/* Tests for sub-classing
@@ -76,7 +75,7 @@
AnnotatedBooleanList result = mapper.readValue("[ false ]", AnnotatedBooleanList.class);
assertEquals(1, result.size());
Object ob = result.get(0);
- assertEquals(BooleanWrapper.class, ob.getClass());
- assertFalse(((BooleanWrapper) ob).b);
+ assertEquals(BooleanElement.class, ob.getClass());
+ assertFalse(((BooleanElement) ob).b);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestInnerClass.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestInnerClass.java
index 4be0a63..556e996 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestInnerClass.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestInnerClass.java
@@ -1,10 +1,10 @@
package com.fasterxml.jackson.databind.deser;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.*;
public class TestInnerClass extends BaseMapTest
{
- // [JACKSON-594]
static class Dog
{
public String name;
@@ -16,9 +16,10 @@
brain = new Brain();
brain.isThinking = thinking;
}
-
+
// note: non-static
public class Brain {
+ @JsonProperty("brainiac")
public boolean isThinking;
public String parentName() { return name; }
@@ -45,5 +46,11 @@
assertEquals("Smurf", output.brain.parentName());
output.name = "Foo";
assertEquals("Foo", output.brain.parentName());
+
+ // also, null handling
+ input.brain = null;
+
+ output = mapper.readValue(mapper.writeValueAsString(input), Dog.class);
+ assertNull(output.brain);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderAdvancedTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderAdvancedTest.java
new file mode 100644
index 0000000..e3f2644
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderAdvancedTest.java
@@ -0,0 +1,63 @@
+package com.fasterxml.jackson.databind.deser.builder;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+public class BuilderAdvancedTest extends BaseMapTest
+{
+ @JsonDeserialize(builder=InjectableBuilderXY.class)
+ static class InjectableXY
+ {
+ final int _x, _y;
+ final String _stuff;
+
+ protected InjectableXY(int x, int y, String stuff) {
+ _x = x+1;
+ _y = y+1;
+ _stuff = stuff;
+ }
+ }
+
+ static class InjectableBuilderXY
+ {
+ public int x, y;
+
+ @JacksonInject
+ protected String stuff;
+
+ public InjectableBuilderXY withX(int x0) {
+ this.x = x0;
+ return this;
+ }
+
+ public InjectableBuilderXY withY(int y0) {
+ this.y = y0;
+ return this;
+ }
+
+ public InjectableXY build() {
+ return new InjectableXY(x, y, stuff);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ public void testWithInjectable() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setInjectableValues(new InjectableValues.Std()
+ .addValue(String.class, "stuffValue")
+ );
+ InjectableXY bean = mapper.readValue(aposToQuotes("{'y':3,'x':7}"),
+ InjectableXY.class);
+ assertEquals(8, bean._x);
+ assertEquals(4, bean._y);
+ assertEquals("stuffValue", bean._stuff);
+ }
+}
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
new file mode 100644
index 0000000..f7f67b0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderErrorHandling.java
@@ -0,0 +1,66 @@
+package com.fasterxml.jackson.databind.deser.builder;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+public class BuilderErrorHandling extends BaseMapTest
+{
+ @JsonDeserialize(builder=SimpleBuilderXY.class)
+ static class ValueClassXY
+ {
+ final int _x, _y;
+
+ protected ValueClassXY(int x, int y) {
+ _x = x+1;
+ _y = y+1;
+ }
+ }
+
+ static class SimpleBuilderXY
+ {
+ int x, y;
+
+ public SimpleBuilderXY withX(int x0) {
+ this.x = x0;
+ return this;
+ }
+
+ public SimpleBuilderXY withY(int y0) {
+ this.y = y0;
+ return this;
+ }
+
+ public ValueClassXY build() {
+ return new ValueClassXY(x, y);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testUnknownProperty() throws Exception
+ {
+ // first, default failure
+ String json = aposToQuotes("{'x':1,'z':2,'y':4}");
+ try {
+ MAPPER.readValue(json, ValueClassXY.class);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "unrecognized field");
+ }
+ // but pass if ok to ignore
+ ValueClassXY result = MAPPER.readerFor(ValueClassXY.class)
+ .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .readValue(json);
+ assertEquals(2, result._x);
+ assertEquals(5, result._y);
+ }
+}
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
new file mode 100644
index 0000000..b674a89
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderFailTest.java
@@ -0,0 +1,88 @@
+package com.fasterxml.jackson.databind.deser.builder;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
+
+public class BuilderFailTest extends BaseMapTest
+{
+ @JsonDeserialize(builder=SimpleBuilderXY.class)
+ static class ValueClassXY
+ {
+ final int _x, _y;
+
+ protected ValueClassXY(int x, int y) {
+ _x = x+1;
+ _y = y+1;
+ }
+ }
+
+ static class SimpleBuilderXY
+ {
+ public int x, y;
+
+ public SimpleBuilderXY withX(int x0) {
+ this.x = x0;
+ return this;
+ }
+
+ public SimpleBuilderXY withY(int y0) {
+ this.y = y0;
+ return this;
+ }
+
+ public ValueClassXY build() {
+ return new ValueClassXY(x, y);
+ }
+ }
+
+ // for [databind#761]
+ @JsonDeserialize(builder = ValueBuilderWrongBuildType.class)
+ static class ValueClassWrongBuildType {
+ }
+
+ static class ValueBuilderWrongBuildType
+ {
+ public int x;
+
+ public ValueBuilderWrongBuildType withX(int x0) {
+ this.x = x0;
+ return this;
+ }
+
+ public ValueClassXY build() {
+ return null;
+ }
+ }
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testBuilderMethodReturnInvalidType() throws Exception
+ {
+ final String json = "{\"x\":1}";
+ try {
+ MAPPER.readValue(json, ValueClassWrongBuildType.class);
+ fail("Missing expected JsonProcessingException exception");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Build method");
+ verifyException(e, "has wrong return type");
+ }
+ }
+
+ public void testExtraFields() throws Exception
+ {
+ final String json = aposToQuotes("{'x':1,'y':2,'z':3}");
+ try {
+ MAPPER.readValue(json, ValueClassXY.class);
+ fail("should not pass");
+ } catch (UnrecognizedPropertyException e) {
+ verifyException(e, "Unrecognized field \"z\"");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderInfiniteLoop1978Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderInfiniteLoop1978Test.java
new file mode 100644
index 0000000..0a6a067
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderInfiniteLoop1978Test.java
@@ -0,0 +1,95 @@
+package com.fasterxml.jackson.databind.deser.builder;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+
+import com.fasterxml.jackson.databind.*;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+public class BuilderInfiniteLoop1978Test extends BaseMapTest
+{
+ static class Builder
+ {
+ private SubBean temp;
+ private int id;
+
+ Builder(@JsonProperty("beanId") int beanId) {
+ this.id = beanId;
+ }
+
+ @JsonUnwrapped(prefix="sub.")
+ public Builder withThing(SubBean thing) {
+ this.temp = thing;
+ return this;
+ }
+
+ public Bean build()
+ {
+ Bean bean = new Bean(id);
+ bean.setThing( temp );
+ return bean;
+ }
+ }
+
+ @JsonDeserialize(builder = Builder.class)
+ static class Bean
+ {
+ int id;
+ SubBean thing;
+
+ public Bean(int id) {
+ this.id = id;
+ }
+
+ public SubBean getThing() {
+ return thing;
+ }
+
+ public void setThing( SubBean thing ) {
+ this.thing = thing;
+ }
+ }
+
+ static class SubBuilder
+ {
+ private int element1;
+ private String element2;
+
+ @JsonProperty("el1")
+ public SubBuilder withElement1(int e1) {
+ this.element1 = e1;
+ return this;
+ }
+
+ public SubBean build()
+ {
+ SubBean bean = new SubBean();
+ bean.element1 = element1;
+ bean.element2 = element2;
+ return bean;
+ }
+ }
+
+ @JsonDeserialize(builder = SubBuilder.class)
+ static class SubBean
+ {
+ public int element1;
+ public String element2;
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ // for [databind#1978]
+ public void testInfiniteLoop1978() throws Exception
+ {
+ String json = "{\"sub.el1\":34,\"sub.el2\":\"some text\"}";
+ ObjectMapper mapper = new ObjectMapper();
+ Bean bean = mapper.readValue( json, Bean.class );
+ assertNotNull(bean);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/BuilderSimpleTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderSimpleTest.java
similarity index 77%
rename from src/test/java/com/fasterxml/jackson/databind/creators/BuilderSimpleTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderSimpleTest.java
index 3ae76e7..7644dc4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/BuilderSimpleTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderSimpleTest.java
@@ -1,21 +1,21 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.builder;
import java.util.*;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonSetter;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.core.Version;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
+import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
public class BuilderSimpleTest extends BaseMapTest
{
// // Simple 2-property value class, builder with standard naming
-
+
@JsonDeserialize(builder=SimpleBuilderXY.class)
static class ValueClassXY
{
@@ -47,7 +47,7 @@
}
// // 3-property value, with more varied builder
-
+
@JsonDeserialize(builder=BuildABC.class)
static class ValueClassABC
{
@@ -60,6 +60,7 @@
}
}
+ @JsonIgnoreProperties({ "d" })
static class BuildABC
{
public int a; // to be used as is
@@ -105,7 +106,6 @@
return new ValueImmutable(value);
}
}
-
// And then with custom naming:
@JsonDeserialize(builder=BuildFoo.class)
@@ -128,40 +128,6 @@
}
}
- // And with creator(s)
-
- @JsonDeserialize(builder=CreatorBuilder.class)
- static class CreatorValue
- {
- final int a, b, c;
-
- protected CreatorValue(int a, int b, int c) {
- this.a = a;
- this.b = b;
- this.c = c;
- }
- }
-
- static class CreatorBuilder {
- private final int a, b;
- private int c;
-
- @JsonCreator
- public CreatorBuilder(@JsonProperty("a") int a,
- @JsonProperty("b") int b)
- {
- this.a = a;
- this.b = b;
- }
-
- public CreatorBuilder withC(int v) {
- c = v;
- return this;
- }
- public CreatorValue build() {
- return new CreatorValue(a, b, c);
- }
- }
// for [databind#761]
@@ -231,25 +197,6 @@
return new ValueInterface2Impl(x);
}
}
-
- // for [databind#761]
- @JsonDeserialize(builder = ValueBuilderWrongBuildType.class)
- static class ValueClassWrongBuildType {
- }
-
- static class ValueBuilderWrongBuildType
- {
- public int x;
-
- public ValueBuilderWrongBuildType withX(int x0) {
- this.x = x0;
- return this;
- }
-
- public ValueClassXY build() {
- return null;
- }
- }
// [databind#777]
@JsonDeserialize(builder = SelfBuilder777.class)
@@ -296,6 +243,31 @@
}
}
+ protected static class NopModule1557 extends com.fasterxml.jackson.databind.Module
+ {
+ @Override
+ public String getModuleName() {
+ return "NopModule";
+ }
+
+ @Override
+ public Version version() {
+ return Version.unknownVersion();
+ }
+
+ @Override
+ public void setupModule(SetupContext setupContext) {
+ // This annotation introspector has no opinion about builders, make sure it doesn't interfere
+ setupContext.insertAnnotationIntrospector(new NopAnnotationIntrospector() {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Version version() {
+ return Version.unknownVersion();
+ }
+ });
+ }
+ }
+
/*
/**********************************************************
/* Unit tests
@@ -306,7 +278,7 @@
public void testSimple() throws Exception
{
- String json = "{\"x\":1,\"y\":2}";
+ String json = aposToQuotes("{'x':1,'y':2}");
Object o = MAPPER.readValue(json, ValueClassXY.class);
assertNotNull(o);
assertSame(ValueClassXY.class, o.getClass());
@@ -320,13 +292,14 @@
public void testSimpleWithIgnores() throws Exception
{
// 'z' is unknown, and would fail by default:
- String json = "{\"x\":1,\"y\":2,\"z\":3}";
+ final String json = aposToQuotes("{'x':1,'y':2,'z':4}");
Object o = null;
try {
o = MAPPER.readValue(json, ValueClassXY.class);
fail("Should not pass");
- } catch (JsonMappingException e) {
+ } catch (UnrecognizedPropertyException e) {
+ assertEquals("z", e.getPropertyName());
verifyException(e, "Unrecognized field \"z\"");
}
@@ -345,13 +318,19 @@
public void testMultiAccess() throws Exception
{
- String json = "{\"c\":3,\"a\":2,\"b\":-9}";
+ String json = aposToQuotes("{'c':3,'a':2,'b':-9}");
ValueClassABC value = MAPPER.readValue(json, ValueClassABC.class);
assertNotNull(value);
- // note: ctor adds one to both values
- assertEquals(value.a, 2);
- assertEquals(value.b, -9);
- assertEquals(value.c, 3);
+ assertEquals(2, value.a);
+ assertEquals(-9, value.b);
+ assertEquals(3, value.c);
+
+ // also, since we can ignore some properties:
+ value = MAPPER.readValue(aposToQuotes("{'c':3,'d':5,'b':-9}"), ValueClassABC.class);
+ assertNotNull(value);
+ assertEquals(0, value.a);
+ assertEquals(-9, value.b);
+ assertEquals(3, value.c);
}
// test for Immutable builder, to ensure return value is used
@@ -370,16 +349,6 @@
assertEquals(1, value.value);
}
- // test to ensure @JsonCreator also work
- public void testWithCreator() throws Exception
- {
- final String json = "{\"a\":1,\"c\":3,\"b\":2}";
- CreatorValue value = MAPPER.readValue(json, CreatorValue.class);
- assertEquals(1, value.a);
- assertEquals(2, value.b);
- assertEquals(3, value.c);
- }
-
// for [databind#761]
public void testBuilderMethodReturnMoreGeneral() throws Exception
@@ -395,19 +364,6 @@
ValueInterface2 value = MAPPER.readValue(json, ValueInterface2.class);
assertEquals(2, value.getX());
}
-
- public void testBuilderMethodReturnInvalidType() throws Exception
- {
- final String json = "{\"x\":1}";
- try {
- MAPPER.readValue(json, ValueClassWrongBuildType.class);
- fail("Missing expected JsonProcessingException exception");
- } catch(JsonProcessingException e) {
- assertTrue(
- "Exception cause must be IllegalArgumentException",
- e.getCause() instanceof IllegalArgumentException);
- }
- }
public void testSelfBuilder777() throws Exception
{
@@ -432,5 +388,11 @@
assertTrue(((List<?>) ob).isEmpty());
}
-
+ public void testPOJOConfigResolution1557() throws Exception
+ {
+ final String json = "{\"value\":1}";
+ MAPPER.registerModule(new NopModule1557());
+ ValueFoo value = MAPPER.readValue(json, ValueFoo.class);
+ assertEquals(1, value.value);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderViaUpdateTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderViaUpdateTest.java
new file mode 100644
index 0000000..f5b63a5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderViaUpdateTest.java
@@ -0,0 +1,89 @@
+package com.fasterxml.jackson.databind.deser.builder;
+
+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;
+
+/**
+ * Tests to ensure that use of "updateValue()" will fail with builder-based deserializers.
+ *
+ * @since 2.9
+ */
+public class BuilderViaUpdateTest extends BaseMapTest
+{
+ @JsonDeserialize(builder=SimpleBuilderXY.class)
+ static class ValueClassXY
+ {
+ protected int x, y;
+
+ protected ValueClassXY(int x, int y) {
+ x = x+1;
+ y = y+1;
+ }
+ }
+
+ static class SimpleBuilderXY
+ {
+ public int x, y;
+
+ public SimpleBuilderXY withX(int x0) {
+ this.x = x0;
+ return this;
+ }
+
+ public SimpleBuilderXY withY(int y0) {
+ this.y = y0;
+ return this;
+ }
+
+ public ValueClassXY build() {
+ return new ValueClassXY(x, y);
+ }
+ }
+
+ /*
+ /*****************************************************
+ /* Basic tests, potential (but not current) success cases
+ /*****************************************************
+ */
+
+ private final static ObjectMapper MAPPER = new ObjectMapper();
+
+ // Tests where result value is passed as thing to update
+ public void testBuilderUpdateWithValue() throws Exception
+ {
+ try {
+ /*ValueClassXY value =*/ MAPPER.readerFor(ValueClassXY.class)
+ .withValueToUpdate(new ValueClassXY(6, 7))
+ .readValue(aposToQuotes("{'x':1,'y:'2'}"));
+ fail("Should not have passed");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Deserialization of");
+ verifyException(e, "by passing existing instance");
+ verifyException(e, "ValueClassXY");
+ }
+ }
+
+ /*
+ /*****************************************************
+ /* Failing test cases
+ /*****************************************************
+ */
+
+ // and then test to ensure error handling works as expected if attempts
+ // is made to pass builder (API requires value, not builder)
+ public void testBuilderWithWrongType() throws Exception
+ {
+ try {
+ /* Object result =*/ MAPPER.readerFor(ValueClassXY.class)
+ .withValueToUpdate(new SimpleBuilderXY())
+ .readValue(aposToQuotes("{'x':1,'y:'2'}"));
+ fail("Should not have passed");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Deserialization of");
+ verifyException(e, "by passing existing Builder");
+ verifyException(e, "SimpleBuilderXY");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithCreatorTest.java
new file mode 100644
index 0000000..fd2e44c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithCreatorTest.java
@@ -0,0 +1,176 @@
+package com.fasterxml.jackson.databind.deser.builder;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+public class BuilderWithCreatorTest extends BaseMapTest
+{
+ @JsonDeserialize(builder=PropertyCreatorBuilder.class)
+ static class PropertyCreatorValue
+ {
+ final int a, b, c;
+
+ protected PropertyCreatorValue(int a, int b, int c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+ }
+
+ static class PropertyCreatorBuilder {
+ private final int a, b;
+ private int c;
+
+ @JsonCreator
+ public PropertyCreatorBuilder(@JsonProperty("a") int a,
+ @JsonProperty("b") int b)
+ {
+ this.a = a;
+ this.b = b;
+ }
+
+ public PropertyCreatorBuilder withC(int v) {
+ c = v;
+ return this;
+ }
+ public PropertyCreatorValue build() {
+ return new PropertyCreatorValue(a, b, c);
+ }
+ }
+
+ // With String
+
+ @JsonDeserialize(builder=StringCreatorBuilder.class)
+ static class StringCreatorValue
+ {
+ final String str;
+
+ protected StringCreatorValue(String s) { str = s; }
+ }
+
+ static class StringCreatorBuilder {
+ private final String v;
+
+ @JsonCreator
+ public StringCreatorBuilder(String str) {
+ v = str;
+ }
+
+ public StringCreatorValue build() {
+ return new StringCreatorValue(v);
+ }
+ }
+
+ // With boolean
+
+ @JsonDeserialize(builder=BooleanCreatorBuilder.class)
+ static class BooleanCreatorValue
+ {
+ final boolean value;
+
+ protected BooleanCreatorValue(boolean v) { value = v; }
+ }
+
+ static class BooleanCreatorBuilder {
+ private final boolean value;
+
+ @JsonCreator
+ public BooleanCreatorBuilder(boolean v) {
+ value = v;
+ }
+
+ public BooleanCreatorValue build() {
+ return new BooleanCreatorValue(value);
+ }
+ }
+
+ // With Int
+
+ @JsonDeserialize(builder=IntCreatorBuilder.class)
+ static class IntCreatorValue
+ {
+ final int value;
+
+ protected IntCreatorValue(int v) { value = v; }
+ }
+
+ static class IntCreatorBuilder {
+ private final int value;
+
+ @JsonCreator
+ public IntCreatorBuilder(int v) {
+ value = v;
+ }
+
+ public IntCreatorValue build() {
+ return new IntCreatorValue(value);
+ }
+ }
+
+ // With Double
+
+ @JsonDeserialize(builder=DoubleCreatorBuilder.class)
+ static class DoubleCreatorValue
+ {
+ final double value;
+
+ protected DoubleCreatorValue(double v) { value = v; }
+ }
+
+ static class DoubleCreatorBuilder {
+ private final double value;
+
+ @JsonCreator
+ public DoubleCreatorBuilder(double v) {
+ value = v;
+ }
+
+ public DoubleCreatorValue build() {
+ return new DoubleCreatorValue(value);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testWithPropertiesCreator() throws Exception
+ {
+ final String json = aposToQuotes("{'a':1,'c':3,'b':2}");
+ PropertyCreatorValue value = MAPPER.readValue(json, PropertyCreatorValue.class);
+ assertEquals(1, value.a);
+ assertEquals(2, value.b);
+ assertEquals(3, value.c);
+ }
+
+ public void testWithDelegatingStringCreator() throws Exception
+ {
+ final int EXP = 139;
+ IntCreatorValue value = MAPPER.readValue(String.valueOf(EXP),
+ IntCreatorValue.class);
+ assertEquals(EXP, value.value);
+ }
+
+ public void testWithDelegatingIntCreator() throws Exception
+ {
+ final double EXP = -3.75;
+ DoubleCreatorValue value = MAPPER.readValue(String.valueOf(EXP),
+ DoubleCreatorValue.class);
+ assertEquals(EXP, value.value);
+ }
+
+ public void testWithDelegatingBooleanCreator() throws Exception
+ {
+ final boolean EXP = true;
+ BooleanCreatorValue value = MAPPER.readValue(String.valueOf(EXP),
+ BooleanCreatorValue.class);
+ assertEquals(EXP, value.value);
+ }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithUnwrappedTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithUnwrappedTest.java
index 89726cb..a787584 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithUnwrappedTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithUnwrappedTest.java
@@ -8,13 +8,8 @@
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-public class BuilderWithUnwrappedTest extends BaseMapTest {
- /*
- *************************************
- * Mock classes
- *************************************
- */
-
+public class BuilderWithUnwrappedTest extends BaseMapTest
+{
final static class Name {
private final String first;
private final String last;
@@ -161,9 +156,9 @@
}
/*
- *************************************
- * Unit tests
- *************************************
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
*/
public void testWithUnwrappedAndCreatorSingleParameterAtBeginning() throws Exception {
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithViewTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithViewTest.java
new file mode 100644
index 0000000..346112e
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithViewTest.java
@@ -0,0 +1,114 @@
+package com.fasterxml.jackson.databind.deser.builder;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+public class BuilderWithViewTest extends BaseMapTest
+{
+ static class ViewX { }
+ static class ViewY { }
+
+ @JsonDeserialize(builder=SimpleBuilderXY.class)
+ static class ValueClassXY
+ {
+ final int _x, _y;
+
+ protected ValueClassXY(int x, int y) {
+ _x = x+1;
+ _y = y+1;
+ }
+ }
+
+ static class SimpleBuilderXY
+ {
+ public int x, y;
+
+ @JsonView(ViewX.class)
+ public SimpleBuilderXY withX(int x0) {
+ this.x = x0;
+ return this;
+ }
+
+ @JsonView(ViewY.class)
+ public SimpleBuilderXY withY(int y0) {
+ this.y = y0;
+ return this;
+ }
+
+ public ValueClassXY build() {
+ return new ValueClassXY(x, y);
+ }
+ }
+
+ @JsonDeserialize(builder=CreatorBuilderXY.class)
+ static class CreatorValueXY
+ {
+ final int _x, _y;
+
+ protected CreatorValueXY(int x, int y) {
+ _x = x;
+ _y = y;
+ }
+ }
+
+ @JsonIgnoreProperties({ "bogus" })
+ static class CreatorBuilderXY
+ {
+ public int x, y;
+
+ @JsonCreator
+ public CreatorBuilderXY(@JsonProperty("x") @JsonView(ViewX.class) int x,
+ @JsonProperty("y") @JsonView(ViewY.class) int y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ public CreatorValueXY build() {
+ return new CreatorValueXY(x, y);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testSimpleViews() throws Exception
+ {
+ final String json = aposToQuotes("{'x':5,'y':10}");
+ ValueClassXY resultX = MAPPER.readerFor(ValueClassXY.class)
+ .withView(ViewX.class)
+ .readValue(json);
+ assertEquals(6, resultX._x);
+ assertEquals(1, resultX._y);
+
+ ValueClassXY resultY = MAPPER.readerFor(ValueClassXY.class)
+ .withView(ViewY.class)
+ .readValue(json);
+ assertEquals(1, resultY._x);
+ assertEquals(11, resultY._y);
+ }
+
+ public void testCreatorViews() throws Exception
+ {
+ final String json = aposToQuotes("{'x':5,'y':10,'bogus':false}");
+ CreatorValueXY resultX = MAPPER.readerFor(CreatorValueXY.class)
+ .withView(ViewX.class)
+ .readValue(json);
+ assertEquals(5, resultX._x);
+ assertEquals(0, resultX._y);
+
+ CreatorValueXY resultY = MAPPER.readerFor(CreatorValueXY.class)
+ .withView(ViewY.class)
+ .readValue(json);
+ assertEquals(0, resultY._x);
+ assertEquals(10, resultY._y);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/ArrayDelegatorCreatorForCollectionTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ArrayDelegatorCreatorForCollectionTest.java
similarity index 94%
rename from src/test/java/com/fasterxml/jackson/databind/creators/ArrayDelegatorCreatorForCollectionTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/ArrayDelegatorCreatorForCollectionTest.java
index 4110008..62e4d63 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/ArrayDelegatorCreatorForCollectionTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ArrayDelegatorCreatorForCollectionTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.util.Collections;
import java.util.Set;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/BigCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/BigCreatorTest.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/creators/BigCreatorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/BigCreatorTest.java
index 154d1a5..4d43c56 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/BigCreatorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/BigCreatorTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/CreatorPropertiesTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorPropertiesTest.java
similarity index 60%
rename from src/test/java/com/fasterxml/jackson/databind/creators/CreatorPropertiesTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorPropertiesTest.java
index 93ad973..0e375dd 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/CreatorPropertiesTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorPropertiesTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.beans.ConstructorProperties;
@@ -16,7 +16,7 @@
@ConstructorProperties({"x", "y"})
// Same as above; use differing local parameter names so that parameter name
- // introspection can not be used as the source of property names.
+ // introspection cannot be used as the source of property names.
public Issue905Bean(int a, int b) {
_x = a;
_y = b;
@@ -25,7 +25,6 @@
// for [databind#1122]
static class Ambiguity {
-
@JsonProperty("bar")
private int foo;
@@ -46,6 +45,19 @@
}
}
+ // for [databind#1371]
+ static class Lombok1371Bean {
+ public int x, y;
+
+ protected Lombok1371Bean() { }
+
+ @ConstructorProperties({ "x", "y" })
+ public Lombok1371Bean(int _x, int _y) {
+ x = _x + 1;
+ y = _y + 1;
+ }
+ }
+
/*
/**********************************************************
/* Test methods
@@ -71,4 +83,24 @@
assertNotNull(amb);
assertEquals(3, amb.getFoo());
}
+
+ // [databind#1371]: MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES
+ public void testConstructorPropertiesInference() throws Exception
+ {
+ final String JSON = aposToQuotes("{'x':3,'y':5}");
+
+ // by default, should detect and use arguments-taking constructor as creator
+ assertTrue(MAPPER.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES));
+ Lombok1371Bean result = MAPPER.readValue(JSON, Lombok1371Bean.class);
+ assertEquals(4, result.x);
+ assertEquals(6, result.y);
+
+ // but change if configuration changed
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES);
+ // in which case fields are set directly:
+ result = mapper.readValue(JSON, Lombok1371Bean.class);
+ assertEquals(3, result.x);
+ assertEquals(5, result.y);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorWithNamingStrategyTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorWithNamingStrategyTest.java
new file mode 100644
index 0000000..83f5970
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorWithNamingStrategyTest.java
@@ -0,0 +1,54 @@
+package com.fasterxml.jackson.databind.deser.creators;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
+import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
+
+public class CreatorWithNamingStrategyTest extends BaseMapTest
+{
+ @SuppressWarnings("serial")
+ static class MyParamIntrospector extends JacksonAnnotationIntrospector
+ {
+ @Override
+ public String findImplicitPropertyName(AnnotatedMember param) {
+ if (param instanceof AnnotatedParameter) {
+ AnnotatedParameter ap = (AnnotatedParameter) param;
+ return "paramName"+ap.getIndex();
+ }
+ return super.findImplicitPropertyName(param);
+ }
+ }
+
+ // [databind#2051]
+ static class OneProperty {
+ public String paramName0;
+
+ @JsonCreator
+ public OneProperty(String bogus) {
+ paramName0 = "CTOR:"+bogus;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper()
+ .setAnnotationIntrospector(new MyParamIntrospector())
+ .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
+ ;
+
+ // [databind#2051]
+ public void testSnakeCaseWithOneArg() throws Exception
+ {
+ final String MSG = "1st";
+ OneProperty actual = MAPPER.readValue(
+ "{\"param_name0\":\""+MSG+"\"}",
+ OneProperty.class);
+ assertEquals("CTOR:"+MSG, actual.paramName0);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/CreatorWithObjectIdTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorWithObjectIdTest.java
similarity index 95%
rename from src/test/java/com/fasterxml/jackson/databind/creators/CreatorWithObjectIdTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorWithObjectIdTest.java
index 01c13a0..a82c7e2 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/CreatorWithObjectIdTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/CreatorWithObjectIdTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.beans.ConstructorProperties;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingArrayCreator1804Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreator1804Test.java
similarity index 94%
rename from src/test/java/com/fasterxml/jackson/databind/creators/DelegatingArrayCreator1804Test.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreator1804Test.java
index 328f4bc..04e0423 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingArrayCreator1804Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreator1804Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.util.List;
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreator2324Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreator2324Test.java
new file mode 100644
index 0000000..18882cb
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingArrayCreator2324Test.java
@@ -0,0 +1,70 @@
+package com.fasterxml.jackson.databind.deser.creators;
+
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+// for [databind#2324]
+public class DelegatingArrayCreator2324Test extends BaseMapTest
+{
+ @JsonDeserialize(as=ImmutableBag.class)
+ public interface Bag<T> extends Collection<T> { }
+
+ public static class ImmutableBag<T> extends AbstractCollection<T> implements Bag<T> {
+ @Override
+ public Iterator<T> iterator() { return elements.iterator(); }
+
+ @Override
+ public int size() { return elements.size(); }
+
+ @JsonCreator(mode=JsonCreator.Mode.DELEGATING)
+ private ImmutableBag(Collection<T> elements ) {
+ this.elements = Collections.unmodifiableCollection(elements);
+ }
+
+ private final Collection<T> elements;
+ }
+
+ static class Value {
+ public String value;
+
+ public Value(String v) { value = v; }
+
+ @Override
+ public boolean equals(Object o) {
+ return value.equals(((Value) o).value);
+ }
+ }
+
+ static class WithBagOfStrings {
+ public Bag<String> getStrings() { return this.bagOfStrings; }
+ public void setStrings(Bag<String> bagOfStrings) { this.bagOfStrings = bagOfStrings; }
+ private Bag<String> bagOfStrings;
+ }
+
+ static class WithBagOfValues {
+ public Bag<Value> getValues() { return this.bagOfValues; }
+ public void setValues(Bag<Value> bagOfValues) { this.bagOfValues = bagOfValues; }
+ private Bag<Value> bagOfValues;
+ }
+
+ private final ObjectMapper MAPPER = objectMapper();
+
+ public void testDeserializeBagOfStrings() throws Exception {
+ WithBagOfStrings result = MAPPER.readerFor(WithBagOfStrings.class)
+ .readValue("{\"strings\": [ \"a\", \"b\", \"c\"]}");
+ assertEquals(3, result.getStrings().size());
+ }
+
+ public void testDeserializeBagOfPOJOs() throws Exception {
+ WithBagOfValues result = MAPPER.readerFor(WithBagOfValues.class)
+ .readValue("{\"values\": [ \"a\", \"b\", \"c\"]}");
+ assertEquals(3, result.getValues().size());
+ assertEquals(new Value("a"), result.getValues().iterator().next());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorAnnotations2016Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorAnnotations2016Test.java
new file mode 100644
index 0000000..e892918
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorAnnotations2016Test.java
@@ -0,0 +1,56 @@
+package com.fasterxml.jackson.databind.deser.creators;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+// Tests for problems uncovered with [databind#2016]; related to
+// `@JsonDeserialize` modifications to type, deserializer(s)
+public class DelegatingCreatorAnnotations2016Test extends BaseMapTest
+{
+ // [databind#2016]
+ static class Wrapper2016As {
+ Object value;
+
+ @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+ public Wrapper2016As(@JsonDeserialize(as = java.util.Date.class) Object v) {
+ value = v;
+ }
+ }
+
+ static class Wrapper2016ContentAs {
+ List<Object> value;
+
+ @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+ public Wrapper2016ContentAs(@JsonDeserialize(contentAs = java.util.Date.class) List<Object> v) {
+ value = v;
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ // [databind#2016]
+ public void testDelegatingWithAs() throws Exception
+ {
+ Wrapper2016As actual = MAPPER.readValue("123", Wrapper2016As.class);
+ assertEquals(Date.class, actual.value.getClass());
+ }
+
+ public void testDelegatingWithContentAs() throws Exception
+ {
+ Wrapper2016ContentAs actual = MAPPER.readValue("[123]", Wrapper2016ContentAs.class);
+ List<Object> l = actual.value;
+ assertEquals(1, l.size());
+ assertEquals(Date.class, l.get(0).getClass());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorAnnotations2021Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorAnnotations2021Test.java
new file mode 100644
index 0000000..aa6eaa1
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorAnnotations2021Test.java
@@ -0,0 +1,58 @@
+package com.fasterxml.jackson.databind.deser.creators;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+import com.fasterxml.jackson.core.JsonParser;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+// Tests for problems uncovered with [databind#2016]; related to
+// `@JsonDeserialize` modifications to type, deserializer(s)
+@SuppressWarnings("serial")
+public class DelegatingCreatorAnnotations2021Test extends BaseMapTest
+{
+ // [databind#2021]
+ static class DelegatingWithCustomDeser2021 {
+ public final static Double DEFAULT = 0.25;
+
+ Number value;
+
+ @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+ public DelegatingWithCustomDeser2021(@JsonDeserialize(using = ValueDeser2021.class) Number v) {
+ value = v;
+ }
+ }
+
+ static class ValueDeser2021 extends StdDeserializer<Number> {
+ public ValueDeser2021() { super(Number.class); }
+
+ @Override
+ public Number deserialize(JsonParser p, DeserializationContext ctxt)
+ throws IOException
+ {
+ p.skipChildren();
+ return DelegatingWithCustomDeser2021.DEFAULT;
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ // [databind#2021]
+ public void testCustomDeserForDelegating() throws Exception
+ {
+ DelegatingWithCustomDeser2021 actual = MAPPER.readValue(" true ", DelegatingWithCustomDeser2021.class);
+ assertEquals(DelegatingWithCustomDeser2021.DEFAULT, actual.value);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingCreatorImplicitNames1001Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames1001Test.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/creators/DelegatingCreatorImplicitNames1001Test.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames1001Test.java
index ee354cb..c0043c9 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingCreatorImplicitNames1001Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames1001Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingExternalProperty1003Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingExternalProperty1003Test.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/creators/DelegatingExternalProperty1003Test.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingExternalProperty1003Test.java
index 2060d81..03c4f4d 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingExternalProperty1003Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingExternalProperty1003Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/DisablingCreatorsTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DisablingCreatorsTest.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/creators/DisablingCreatorsTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/DisablingCreatorsTest.java
index 1ca6cf4..03acdf0 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/DisablingCreatorsTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DisablingCreatorsTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/EnumCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/EnumCreatorTest.java
similarity index 99%
rename from src/test/java/com/fasterxml/jackson/databind/creators/EnumCreatorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/EnumCreatorTest.java
index 1cf89c2..5024cea 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/EnumCreatorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/EnumCreatorTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.math.BigDecimal;
import java.util.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/FailOnNullCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/FailOnNullCreatorTest.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/creators/FailOnNullCreatorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/FailOnNullCreatorTest.java
index 52416f4..a767bda 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/FailOnNullCreatorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/FailOnNullCreatorTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/ImplicitNameMatch792Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitNameMatch792Test.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/creators/ImplicitNameMatch792Test.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitNameMatch792Test.java
index 887cdf4..8eafb11 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/ImplicitNameMatch792Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitNameMatch792Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/ImplicitParamsForCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitParamsForCreatorTest.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/creators/ImplicitParamsForCreatorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitParamsForCreatorTest.java
index a52c55a..13da73a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/ImplicitParamsForCreatorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitParamsForCreatorTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/InnerClassCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/InnerClassCreatorTest.java
similarity index 91%
rename from src/test/java/com/fasterxml/jackson/databind/creators/InnerClassCreatorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/InnerClassCreatorTest.java
index c235388..eeb733e 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/InnerClassCreatorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/InnerClassCreatorTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -6,7 +6,7 @@
// For [databind#1501], [databind#1502], [databind#1503]; mostly to
// test that for non-static inner classes constructors are ignored
-// and no Creators should be processed (since they can not be made
+// and no Creators should be processed (since they cannot be made
// to work in standard way anyway).
public class InnerClassCreatorTest extends BaseMapTest
{
@@ -64,7 +64,7 @@
MAPPER.readValue(ser, Something1501.class);
fail("Should not pass");
} catch (JsonMappingException e) {
- verifyException(e, "Can not construct instance");
+ verifyException(e, "Cannot construct instance");
verifyException(e, "InnerSomething1501");
verifyException(e, "can only instantiate non-static inner class by using default");
}
@@ -77,7 +77,7 @@
MAPPER.readValue(ser, Something1502.class);
fail("Should not pass");
} catch (JsonMappingException e) {
- verifyException(e, "Can not construct instance");
+ verifyException(e, "Cannot construct instance");
verifyException(e, "InnerSomething1502");
verifyException(e, "can only instantiate non-static inner class by using default");
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/MultiArgConstructorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/MultiArgConstructorTest.java
similarity index 88%
rename from src/test/java/com/fasterxml/jackson/databind/creators/MultiArgConstructorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/MultiArgConstructorTest.java
index 883457c..72e39b7 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/MultiArgConstructorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/MultiArgConstructorTest.java
@@ -1,9 +1,11 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
+
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
@@ -92,13 +94,15 @@
{
final ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new MyParamIntrospector());
- mapper.setVisibility(PropertyAccessor.CREATOR, Visibility.NONE);
+ mapper.setDefaultVisibility(
+ JsonAutoDetect.Value.noOverrides()
+ .withCreatorVisibility(Visibility.NONE));
try {
/*MultiArgCtorBean bean =*/ mapper.readValue(aposToQuotes("{'b':13, 'a':-99}"),
MultiArgCtorBean.class);
fail("Should not have passed");
- } catch (JsonMappingException e) {
- verifyException(e, "No suitable constructor");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "no Creators");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/RequiredCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/RequiredCreatorTest.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/creators/RequiredCreatorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/RequiredCreatorTest.java
index 67a7e3a..66bd967 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/RequiredCreatorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/RequiredCreatorTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/SingleArgCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/SingleArgCreatorTest.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/creators/SingleArgCreatorTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/SingleArgCreatorTest.java
index 04cb86f..90f9869 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/SingleArgCreatorTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/SingleArgCreatorTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
@@ -208,4 +208,3 @@
assertEquals(2, v.y);
}
}
-
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestConstructFromMap.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestConstructFromMap.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestConstructFromMap.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestConstructFromMap.java
index 84e8c7c..2f7f847 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestConstructFromMap.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestConstructFromMap.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.math.BigDecimal;
import java.util.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorNullValue.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorNullValue.java
similarity index 95%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorNullValue.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorNullValue.java
index d41723e..9147b04 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorNullValue.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorNullValue.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.io.IOException;
import java.util.UUID;
@@ -50,7 +50,7 @@
}
}
- protected static class TestModule extends Module
+ protected static class TestModule extends com.fasterxml.jackson.databind.Module
{
@Override
public String getModuleName() {
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithNamingStrategy556.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorWithNamingStrategy556.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithNamingStrategy556.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorWithNamingStrategy556.java
index 8c4993b..affafc8 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithNamingStrategy556.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorWithNamingStrategy556.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithPolymorphic113.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorWithPolymorphic113.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithPolymorphic113.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorWithPolymorphic113.java
index eb39a57..75fb8af 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithPolymorphic113.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorWithPolymorphic113.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCreators.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators.java
index 543c0d7..07854bc 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators.java
@@ -1,10 +1,11 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.util.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
/**
* Unit tests for verifying that it is possible to annotate
@@ -493,7 +494,7 @@
{
try {
/*BrokenBean bean =*/ MAPPER.readValue("{ \"x\" : 42 }", BrokenBean.class);
- } catch (JsonMappingException je) {
+ } catch (InvalidDefinitionException je) {
verifyException(je, "has no property name");
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators2.java
similarity index 77%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators2.java
index f075bcb..f7b2f86 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators2.java
@@ -1,5 +1,5 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.io.IOException;
import java.util.List;
@@ -79,16 +79,6 @@
}
}
- static class MapBean
- {
- protected Map<String,Long> map;
-
- @JsonCreator
- public MapBean(Map<String, Long> map) {
- this.map = map;
- }
- }
-
// For [JACKSON-470]: should be appropriately detected, reported error about
static class BrokenCreatorBean
{
@@ -159,6 +149,34 @@
public String getItem() { return null; }
}
+ static final class MultiPropCreator1476 {
+ private final int intField;
+ private final String stringField;
+
+ public MultiPropCreator1476(@JsonProperty("intField") int intField) {
+ this(intField, "empty");
+ }
+
+ public MultiPropCreator1476(@JsonProperty("stringField") String stringField) {
+ this(-1, stringField);
+ }
+
+ @JsonCreator
+ public MultiPropCreator1476(@JsonProperty("intField") int intField,
+ @JsonProperty("stringField") String stringField) {
+ this.intField = intField;
+ this.stringField = stringField;
+ }
+
+ public int getIntField() {
+ return intField;
+ }
+
+ public String getStringField() {
+ return stringField;
+ }
+ }
+
/*
/**********************************************************
/* Test methods
@@ -176,6 +194,9 @@
verifyException(e, ": foobar");
// also: should have nested exception
Throwable t = e.getCause();
+ if (t == null) {
+ fail("Should have assigned cause for: ("+e.getClass().getSimpleName()+") "+e);
+ }
assertNotNull(t);
assertEquals(IllegalArgumentException.class, t.getClass());
assertEquals("foobar", t.getMessage());
@@ -210,47 +231,27 @@
assertNotNull(foo);
}
- // Catch and rethrow exceptions that Creator methods throw
+ // Catch and re-throw exceptions that Creator methods throw
public void testJackson438() throws Exception
{
+ Exception e = null;
try {
MAPPER.readValue("{ \"name\":\"foobar\" }", BeanFor438.class);
fail("Should have failed");
- } catch (Exception e) {
- if (!(e instanceof JsonMappingException)) {
- fail("Should have received JsonMappingException, caught "+e.getClass().getName());
- }
- verifyException(e, "don't like that name");
- // Ok: also, let's ensure root cause is directly linked, without other extra wrapping:
- Throwable t = e.getCause();
- assertNotNull(t);
- assertEquals(IllegalArgumentException.class, t.getClass());
- verifyException(e, "don't like that name");
+ } catch (Exception e0) {
+ e = e0;
}
- }
-
- @SuppressWarnings("unchecked")
- public void testIssue465() throws Exception
- {
- final String JSON = "{\"A\":12}";
-
- // first, test with regular Map, non empty
- Map<String,Long> map = MAPPER.readValue(JSON, Map.class);
- assertEquals(1, map.size());
- assertEquals(Integer.valueOf(12), map.get("A"));
-
- MapBean bean = MAPPER.readValue(JSON, MapBean.class);
- assertEquals(1, bean.map.size());
- assertEquals(Long.valueOf(12L), bean.map.get("A"));
-
- // and then empty ones
- final String EMPTY_JSON = "{}";
-
- map = MAPPER.readValue(EMPTY_JSON, Map.class);
- assertEquals(0, map.size());
-
- bean = MAPPER.readValue(EMPTY_JSON, MapBean.class);
- assertEquals(0, bean.map.size());
+ if (!(e instanceof JsonMappingException)) {
+ fail("Should have received JsonMappingException, caught "+e.getClass().getName());
+ }
+ verifyException(e, "don't like that name");
+ // Ok: also, let's ensure root cause is directly linked, without other extra wrapping:
+ Throwable t = e.getCause();
+ if (t == null) {
+ fail("Should have assigned cause for: ("+e.getClass().getSimpleName()+") "+e);
+ }
+ assertEquals(IllegalArgumentException.class, t.getClass());
+ verifyException(e, "don't like that name");
}
public void testCreatorWithDupNames() throws Exception
@@ -260,6 +261,8 @@
fail("Should have caught duplicate creator parameters");
} catch (JsonMappingException e) {
verifyException(e, "duplicate creator property \"bar\"");
+ verifyException(e, "for type `com.fasterxml.jackson.databind.");
+ verifyException(e, "$BrokenCreatorBean`");
}
}
@@ -294,4 +297,13 @@
Issue700Bean value = MAPPER.readValue("{ \"item\" : \"foo\" }", Issue700Bean.class);
assertNotNull(value);
}
+
+ // [databind#1476]
+ public void testConstructorChoice() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ MultiPropCreator1476 pojo = mapper.readValue("{ \"intField\": 1, \"stringField\": \"foo\" }",
+ MultiPropCreator1476.class);
+ assertEquals(1, pojo.getIntField());
+ assertEquals("foo", pojo.getStringField());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators3.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators3.java
new file mode 100644
index 0000000..d00030c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreators3.java
@@ -0,0 +1,209 @@
+package com.fasterxml.jackson.databind.deser.creators;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
+import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
+
+// Misc Creator tests, part 3
+public class TestCreators3 extends BaseMapTest
+{
+ static final class Foo {
+
+ @JsonProperty("foo")
+ protected Map<Integer, Bar> foo;
+ @JsonProperty("anumber")
+ protected long anumber;
+
+ public Foo() {
+ anumber = 0;
+ }
+
+ public Map<Integer, Bar> getFoo() {
+ return foo;
+ }
+
+ public long getAnumber() {
+ return anumber;
+ }
+ }
+
+ static final class Bar {
+
+ private final long p;
+ private final List<String> stuff;
+
+ @JsonCreator
+ public Bar(@JsonProperty("p") long p, @JsonProperty("stuff") List<String> stuff) {
+ this.p = p;
+ this.stuff = stuff;
+ }
+
+ @JsonProperty("s")
+ public List<String> getStuff() {
+ return stuff;
+ }
+
+ @JsonProperty("stuff")
+ private List<String> getStuffDeprecated() {
+ return stuff;
+ }
+
+ public long getP() {
+ return p;
+ }
+ }
+
+ // [databind#421]
+
+ static class MultiCtor
+ {
+ protected String _a, _b;
+
+ private MultiCtor() { }
+ private MultiCtor(String a, String b, Boolean c) {
+ if (c == null) {
+ throw new RuntimeException("Wrong factory!");
+ }
+ _a = a;
+ _b = b;
+ }
+
+ @JsonCreator
+ static MultiCtor factory(@JsonProperty("a") String a, @JsonProperty("b") String b) {
+ return new MultiCtor(a, b, Boolean.TRUE);
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static class MyParamIntrospector extends JacksonAnnotationIntrospector
+ {
+ @Override
+ public String findImplicitPropertyName(AnnotatedMember param) {
+ if (param instanceof AnnotatedParameter) {
+ AnnotatedParameter ap = (AnnotatedParameter) param;
+ switch (ap.getIndex()) {
+ case 0: return "a";
+ case 1: return "b";
+ case 2: return "c";
+ default:
+ return "param"+ap.getIndex();
+ }
+ }
+ return super.findImplicitPropertyName(param);
+ }
+ }
+
+ // [databind#1853]
+ public static class Product1853 {
+ String name;
+
+ public Object other, errors;
+
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public Product1853(@JsonProperty("name") String name) {
+ this.name = "PROP:" + name;
+ }
+
+ @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+ public static Product1853 from(String name){
+ return new Product1853(false, "DELEG:"+name);
+ }
+
+ private Product1853(boolean bogus, String name) {
+ this.name = name;
+ }
+
+ @JsonValue
+ public String getName(){
+ return name;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testCreator541() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(
+ MapperFeature.AUTO_DETECT_CREATORS,
+ MapperFeature.AUTO_DETECT_FIELDS,
+ MapperFeature.AUTO_DETECT_GETTERS,
+ MapperFeature.AUTO_DETECT_IS_GETTERS,
+ MapperFeature.AUTO_DETECT_SETTERS,
+ MapperFeature.USE_GETTERS_AS_SETTERS
+ );
+ mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+ final String JSON = "{\n"
+ + " \"foo\": {\n"
+ + " \"0\": {\n"
+ + " \"p\": 0,\n"
+ + " \"stuff\": [\n"
+ + " \"a\", \"b\" \n"
+ + " ] \n"
+ + " },\n"
+ + " \"1\": {\n"
+ + " \"p\": 1000,\n"
+ + " \"stuff\": [\n"
+ + " \"c\", \"d\" \n"
+ + " ] \n"
+ + " },\n"
+ + " \"2\": {\n"
+ + " \"p\": 2000,\n"
+ + " \"stuff\": [\n"
+ + " ] \n"
+ + " }\n"
+ + " },\n"
+ + " \"anumber\": 25385874\n"
+ + "}";
+
+ Foo obj = mapper.readValue(JSON, Foo.class);
+ assertNotNull(obj);
+ assertNotNull(obj.foo);
+ assertEquals(3, obj.foo.size());
+ assertEquals(25385874L, obj.getAnumber());
+ }
+
+ // [databind#421]
+ public void testMultiCtor421() throws Exception
+ {
+ final ObjectMapper mapper = newObjectMapper();
+ mapper.setAnnotationIntrospector(new MyParamIntrospector());
+
+ MultiCtor bean = mapper.readValue(aposToQuotes("{'a':'123','b':'foo'}"), MultiCtor.class);
+ assertNotNull(bean);
+ assertEquals("123", bean._a);
+ assertEquals("foo", bean._b);
+ }
+
+ // [databind#1853]
+ public void testSerialization() throws Exception {
+ assertEquals(quote("testProduct"),
+ MAPPER.writeValueAsString(new Product1853(false, "testProduct")));
+ }
+
+ public void testDeserializationFromObject() throws Exception {
+ final String EXAMPLE_DATA = "{\"name\":\"dummy\",\"other\":{},\"errors\":{}}";
+ assertEquals("PROP:dummy", MAPPER.readValue(EXAMPLE_DATA, Product1853.class).getName());
+ }
+
+ public void testDeserializationFromString() throws Exception {
+ assertEquals("DELEG:testProduct",
+ MAPPER.readValue(quote("testProduct"), Product1853.class).getName());
+ }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorsDelegating.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java
similarity index 76%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorsDelegating.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java
index 0989f8b..85b028f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorsDelegating.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsDelegating.java
@@ -1,7 +1,9 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
-import com.fasterxml.jackson.annotation.JsonCreator;
+import java.util.*;
+
import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -70,21 +72,32 @@
}
}
+ static class MapBean
+ {
+ protected Map<String,Long> map;
+
+ @JsonCreator
+ public MapBean(Map<String, Long> map) {
+ this.map = map;
+ }
+ }
+
/*
/**********************************************************
- /* Unit tests
+ /* Test methods
/**********************************************************
*/
+ private final ObjectMapper MAPPER = newObjectMapper();
+
public void testBooleanDelegate() throws Exception
{
- ObjectMapper m = new ObjectMapper();
// should obviously work with booleans...
- BooleanBean bb = m.readValue("true", BooleanBean.class);
+ BooleanBean bb = MAPPER.readValue("true", BooleanBean.class);
assertEquals(Boolean.TRUE, bb.value);
// but also with value conversion from String
- bb = m.readValue(quote("true"), BooleanBean.class);
+ bb = MAPPER.readValue(quote("true"), BooleanBean.class);
assertEquals(Boolean.TRUE, bb.value);
}
@@ -125,8 +138,7 @@
// [databind#592]
public void testDelegateWithTokenBuffer() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
- Value592 value = mapper.readValue("{\"a\":1,\"b\":2}", Value592.class);
+ Value592 value = MAPPER.readValue("{\"a\":1,\"b\":2}", Value592.class);
assertNotNull(value);
Object ob = value.stuff;
assertEquals(TokenBuffer.class, ob.getClass());
@@ -144,4 +156,27 @@
jp.close();
}
+ @SuppressWarnings("unchecked")
+ public void testIssue465() throws Exception
+ {
+ final String JSON = "{\"A\":12}";
+
+ // first, test with regular Map, non empty
+ Map<String,Long> map = MAPPER.readValue(JSON, Map.class);
+ assertEquals(1, map.size());
+ assertEquals(Integer.valueOf(12), map.get("A"));
+
+ MapBean bean = MAPPER.readValue(JSON, MapBean.class);
+ assertEquals(1, bean.map.size());
+ assertEquals(Long.valueOf(12L), bean.map.get("A"));
+
+ // and then empty ones
+ final String EMPTY_JSON = "{}";
+
+ map = MAPPER.readValue(EMPTY_JSON, Map.class);
+ assertEquals(0, map.size());
+
+ bean = MAPPER.readValue(EMPTY_JSON, MapBean.class);
+ assertEquals(0, bean.map.size());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorsWithIdentity.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsWithIdentity.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorsWithIdentity.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsWithIdentity.java
index b064424..386802a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorsWithIdentity.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCreatorsWithIdentity.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.io.IOException;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCustomValueInstDefaults.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCustomValueInstDefaults.java
similarity index 99%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestCustomValueInstDefaults.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCustomValueInstDefaults.java
index e9793e3..79edd02 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCustomValueInstDefaults.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestCustomValueInstDefaults.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.io.IOException;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestPolymorphicCreators.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestPolymorphicCreators.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestPolymorphicCreators.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestPolymorphicCreators.java
index b407ba5..16c953f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestPolymorphicCreators.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestPolymorphicCreators.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestPolymorphicDelegating.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestPolymorphicDelegating.java
similarity index 95%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestPolymorphicDelegating.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestPolymorphicDelegating.java
index 0eba60b..eff86e3 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestPolymorphicDelegating.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestPolymorphicDelegating.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestValueInstantiator.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestValueInstantiator.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestValueInstantiator.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/TestValueInstantiator.java
index 744d3d0..a53ab8f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestValueInstantiator.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/TestValueInstantiator.java
@@ -1,12 +1,15 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.creators;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonValueInstantiator;
import com.fasterxml.jackson.databind.deser.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;
@@ -19,7 +22,7 @@
static class MyBean
{
String _secret;
-
+
public MyBean(String s, boolean bogus) {
_secret = s;
}
@@ -28,16 +31,16 @@
static class MysteryBean
{
Object value;
-
+
public MysteryBean(Object v) { value = v; }
}
-
+
static class CreatorBean
{
String _secret;
public String value;
-
+
protected CreatorBean(String s) {
_secret = s;
}
@@ -586,8 +589,10 @@
MAPPER.readValue("{ }", MyBean.class);
fail("Should not succeed");
} catch (JsonMappingException e) {
- verifyException(e, "Can not construct instance of");
- verifyException(e, "missing default constructor");
+ verifyException(e, "Cannot construct instance of");
+ verifyException(e, "no Creators");
+ // as per [databind#1414], is definition problem
+ assertEquals(InvalidDefinitionException.class, e.getClass());
}
}
@@ -599,8 +604,10 @@
MAPPER.readValue("\"foo\"", MyBean.class);
fail("Should not succeed");
} catch (JsonMappingException e) {
- verifyException(e, "Can not construct instance of");
+ verifyException(e, "Cannot construct instance of");
verifyException(e, "no String-argument constructor/factory");
+ // as per [databind#1414], is definition problem
+ assertEquals(InvalidDefinitionException.class, e.getClass());
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/dos/HugeIntegerCoerceTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/dos/HugeIntegerCoerceTest.java
new file mode 100644
index 0000000..18e580b
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/dos/HugeIntegerCoerceTest.java
@@ -0,0 +1,35 @@
+package com.fasterxml.jackson.databind.deser.dos;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+
+// for [databind#2157]
+public class HugeIntegerCoerceTest extends BaseMapTest
+{
+ private final static int BIG_NUM_LEN = 199999;
+ private final static String BIG_POS_INTEGER;
+ static {
+ StringBuilder sb = new StringBuilder(BIG_NUM_LEN);
+ for (int i = 0; i < BIG_NUM_LEN; ++i) {
+ sb.append('9');
+ }
+ BIG_POS_INTEGER = sb.toString();
+ }
+
+ private final ObjectMapper MAPPER = objectMapper(); // shared is fine
+
+ public void testMaliciousLongForEnum() throws Exception
+ {
+ // Note: due to [jackson-core#488], fix verified with streaming over multiple
+ // parser types. Here we focus on databind-level
+
+ try {
+ /*ABC value =*/ MAPPER.readValue(BIG_POS_INTEGER, ABC.class);
+ fail("Should not pass");
+ } catch (JsonParseException e) {
+ verifyException(e, "out of range of int");
+ verifyException(e, "Integer with "+BIG_NUM_LEN+" digits");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/IgnoreCreatorProp1317Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnoreCreatorProp1317Test.java
similarity index 89%
rename from src/test/java/com/fasterxml/jackson/databind/filter/IgnoreCreatorProp1317Test.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnoreCreatorProp1317Test.java
index 4df9611..7117007 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/IgnoreCreatorProp1317Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnoreCreatorProp1317Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.deser.filter;
import java.beans.ConstructorProperties;
@@ -41,10 +41,10 @@
}
public void testThatJsonIgnoreWorksWithConstructorProperties() throws Exception {
+ ObjectMapper om = objectMapper();
Testing testing = new Testing("shouldBeIgnored", "notIgnore");
- ObjectMapper om = new ObjectMapper();
String json = om.writeValueAsString(testing);
- System.out.println(json);
+// System.out.println(json);
assertFalse(json.contains("shouldBeIgnored"));
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropertyOnDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java
similarity index 95%
rename from src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropertyOnDeserTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java
index 08b882d..5255d4b 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropertyOnDeserTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/IgnorePropertyOnDeserTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.deser.filter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@@ -40,8 +40,8 @@
/* Unit tests
/****************************************************************
*/
-
- private final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final ObjectMapper MAPPER = newObjectMapper();
// [databind#1217]
public void testIgnoreOnProperty1217() throws Exception
@@ -54,7 +54,7 @@
assertEquals(1, result.obj.x);
assertEquals(2, result.obj2.y);
-
+
TestIgnoreObject result1 = MAPPER.readValue(
aposToQuotes("{'obj':{'x': 20, 'y': 30}, 'obj2':{'x': 20, 'y': 40}}"),
TestIgnoreObject.class);
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsForContentTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsForContentTest.java
new file mode 100644
index 0000000..dfb3d37
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsForContentTest.java
@@ -0,0 +1,434 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidNullException;
+
+// For [databind#1402]; configurable null handling, for contents of
+// Collections, Maps, arrays
+public class NullConversionsForContentTest extends BaseMapTest
+{
+ static class NullContentFail<T> {
+ public T nullsOk;
+
+ @JsonSetter(contentNulls=Nulls.FAIL)
+ public T noNulls;
+ }
+
+ static class NullContentAsEmpty<T> {
+ @JsonSetter(contentNulls=Nulls.AS_EMPTY)
+ public T values;
+ }
+
+ static class NullContentSkip<T> {
+ @JsonSetter(contentNulls=Nulls.SKIP)
+ public T values;
+ }
+
+ static class NullContentUndefined<T> {
+ @JsonSetter // leave with defaults
+ public T values;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, fail-on-null
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ // Tests to verify that we can set default settings for failure
+ public void testFailOnNullFromDefaults() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':[null]}");
+ TypeReference<?> listType = new TypeReference<NullContentUndefined<List<String>>>() { };
+
+ // by default fine to get nulls
+ NullContentUndefined<List<String>> result = MAPPER.readValue(JSON, listType);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.size());
+ assertNull(result.values.get(0));
+
+ // but not when overridden globally:
+ ObjectMapper mapper = newObjectMapper();
+ mapper.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL));
+ try {
+ mapper.readValue(JSON, listType);
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"values\"");
+ }
+
+ // or configured for type:
+ mapper = newObjectMapper();
+ mapper.configOverride(List.class)
+ .setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL));
+ try {
+ mapper.readValue(JSON, listType);
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"values\"");
+ }
+ }
+
+ public void testFailOnNullWithCollections() throws Exception
+ {
+ TypeReference<?> typeRef = new TypeReference<NullContentFail<List<Integer>>>() { };
+
+ // first, ok if assigning non-null to not-nullable, null for nullable
+ NullContentFail<List<Integer>> result = MAPPER.readValue(aposToQuotes("{'nullsOk':[null]}"),
+ typeRef);
+ assertNotNull(result.nullsOk);
+ assertEquals(1, result.nullsOk.size());
+ assertNull(result.nullsOk.get(0));
+
+ // and then see that nulls are not ok for non-nullable.
+
+ // List<Integer>
+ final String JSON = aposToQuotes("{'noNulls':[null]}");
+ try {
+ MAPPER.readValue(JSON, typeRef);
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+
+ // List<String>
+ try {
+ MAPPER.readValue(JSON, new TypeReference<NullContentFail<List<String>>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+ }
+
+ public void testFailOnNullWithArrays() throws Exception
+ {
+ final String JSON = aposToQuotes("{'noNulls':[null]}");
+ // Object[]
+ try {
+ MAPPER.readValue(JSON, new TypeReference<NullContentFail<Object[]>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+
+ // String[]
+ try {
+ MAPPER.readValue(JSON, new TypeReference<NullContentFail<String[]>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+ }
+
+ public void testFailOnNullWithPrimitiveArrays() throws Exception
+ {
+ final String JSON = aposToQuotes("{'noNulls':[null]}");
+
+ // boolean[]
+ try {
+ MAPPER.readValue(JSON, new TypeReference<NullContentFail<boolean[]>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+ // int[]
+ try {
+ MAPPER.readValue(JSON, new TypeReference<NullContentFail<int[]>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+ // double[]
+ try {
+ MAPPER.readValue(JSON, new TypeReference<NullContentFail<double[]>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+ }
+
+ public void testFailOnNullWithMaps() throws Exception
+ {
+ // Then: Map<String,String>
+ try {
+ final String MAP_JSON = aposToQuotes("{'noNulls':{'a':null}}");
+ MAPPER.readValue(MAP_JSON, new TypeReference<NullContentFail<Map<String,String>>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+
+ // Then: EnumMap<Enum,String>
+ try {
+ final String MAP_JSON = aposToQuotes("{'noNulls':{'A':null}}");
+ MAPPER.readValue(MAP_JSON, new TypeReference<NullContentFail<EnumMap<ABC,String>>>() { });
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, null-as-empty
+ /**********************************************************
+ */
+
+ public void testNullsAsEmptyWithCollections() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':[null]}");
+
+ // List<Integer>
+ {
+ NullContentAsEmpty<List<Integer>> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentAsEmpty<List<Integer>>>() { });
+ assertEquals(1, result.values.size());
+ assertEquals(Integer.valueOf(0), result.values.get(0));
+ }
+
+ // List<String>
+ {
+ NullContentAsEmpty<List<String>> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentAsEmpty<List<String>>>() { });
+ assertEquals(1, result.values.size());
+ assertEquals("", result.values.get(0));
+ }
+ }
+
+ public void testNullsAsEmptyUsingDefaults() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':[null]}");
+ TypeReference<?> listType = new TypeReference<NullContentUndefined<List<Integer>>>() { };
+
+ // Let's see defaulting in action
+ ObjectMapper mapper = newObjectMapper();
+ mapper.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY));
+ NullContentUndefined<List<Integer>> result = mapper.readValue(JSON, listType);
+ assertEquals(1, result.values.size());
+ assertEquals(Integer.valueOf(0), result.values.get(0));
+
+ // or configured for type:
+ mapper = newObjectMapper();
+ mapper.configOverride(List.class)
+ .setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY));
+ result = mapper.readValue(JSON, listType);
+ assertEquals(1, result.values.size());
+ assertEquals(Integer.valueOf(0), result.values.get(0));
+ }
+
+ public void testNullsAsEmptyWithArrays() throws Exception
+ {
+ // Note: skip `Object[]`, no default empty value at this point
+ final String JSON = aposToQuotes("{'values':[null]}");
+
+ // Then: String[]
+ {
+ NullContentAsEmpty<String[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentAsEmpty<String[]>>() { });
+ assertEquals(1, result.values.length);
+ assertEquals("", result.values[0]);
+ }
+ }
+
+ public void testNullsAsEmptyWithPrimitiveArrays() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':[null]}");
+
+ // int[]
+ {
+ NullContentAsEmpty<int[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentAsEmpty<int[]>>() { });
+ assertEquals(1, result.values.length);
+ assertEquals(0, result.values[0]);
+ }
+
+ // long[]
+ {
+ NullContentAsEmpty<long[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentAsEmpty<long[]>>() { });
+ assertEquals(1, result.values.length);
+ assertEquals(0L, result.values[0]);
+ }
+
+ // boolean[]
+ {
+ NullContentAsEmpty<boolean[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentAsEmpty<boolean[]>>() { });
+ assertEquals(1, result.values.length);
+ assertEquals(false, result.values[0]);
+ }
+}
+
+ public void testNullsAsEmptyWithMaps() throws Exception
+ {
+ // Then: Map<String,String>
+ final String MAP_JSON = aposToQuotes("{'values':{'A':null}}");
+ {
+ NullContentAsEmpty<Map<String,String>> result
+ = MAPPER.readValue(MAP_JSON, new TypeReference<NullContentAsEmpty<Map<String,String>>>() { });
+ assertEquals(1, result.values.size());
+ assertEquals("A", result.values.entrySet().iterator().next().getKey());
+ assertEquals("", result.values.entrySet().iterator().next().getValue());
+ }
+
+ // Then: EnumMap<Enum,String>
+ {
+ NullContentAsEmpty<EnumMap<ABC,String>> result
+ = MAPPER.readValue(MAP_JSON, new TypeReference<NullContentAsEmpty<EnumMap<ABC,String>>>() { });
+ assertEquals(1, result.values.size());
+ assertEquals(ABC.A, result.values.entrySet().iterator().next().getKey());
+ assertEquals("", result.values.entrySet().iterator().next().getValue());
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, skip-nulls
+ /**********************************************************
+ */
+
+ public void testNullsSkipUsingDefaults() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':[null]}");
+ TypeReference<?> listType = new TypeReference<NullContentUndefined<List<Long>>>() { };
+
+ // Let's see defaulting in action
+ ObjectMapper mapper = newObjectMapper();
+ mapper.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP));
+ NullContentUndefined<List<Long>> result = mapper.readValue(JSON, listType);
+ assertEquals(0, result.values.size());
+
+ // or configured for type:
+ mapper = newObjectMapper();
+ mapper.configOverride(List.class)
+ .setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP));
+ result = mapper.readValue(JSON, listType);
+ assertEquals(0, result.values.size());
+ }
+
+ // Test to verify that per-property setting overrides defaults:
+ public void testNullsSkipWithOverrides() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':[null]}");
+ TypeReference<?> listType = new TypeReference<NullContentSkip<List<Long>>>() { };
+
+ ObjectMapper mapper = newObjectMapper();
+ // defaults call for fail; but POJO specifies "skip"; latter should win
+ mapper.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL));
+ NullContentSkip<List<Long>> result = mapper.readValue(JSON, listType);
+ assertEquals(0, result.values.size());
+
+ // ditto for per-type defaults
+ mapper = newObjectMapper();
+ mapper.configOverride(List.class)
+ .setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL));
+ result = mapper.readValue(JSON, listType);
+ assertEquals(0, result.values.size());
+ }
+
+ public void testNullsSkipWithCollections() throws Exception
+ {
+ // List<Integer>
+ {
+ final String JSON = aposToQuotes("{'values':[1,null,2]}");
+ NullContentSkip<List<Integer>> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentSkip<List<Integer>>>() { });
+ assertEquals(2, result.values.size());
+ assertEquals(Integer.valueOf(1), result.values.get(0));
+ assertEquals(Integer.valueOf(2), result.values.get(1));
+ }
+
+ // List<String>
+ {
+ final String JSON = aposToQuotes("{'values':['ab',null,'xy']}");
+ NullContentSkip<List<String>> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentSkip<List<String>>>() { });
+ assertEquals(2, result.values.size());
+ assertEquals("ab", result.values.get(0));
+ assertEquals("xy", result.values.get(1));
+ }
+ }
+
+ public void testNullsSkipWithArrays() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':['a',null,'xy']}");
+ // Object[]
+ {
+ NullContentSkip<Object[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentSkip<Object[]>>() { });
+ assertEquals(2, result.values.length);
+ assertEquals("a", result.values[0]);
+ assertEquals("xy", result.values[1]);
+ }
+ // String[]
+ {
+ NullContentSkip<String[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentSkip<String[]>>() { });
+ assertEquals(2, result.values.length);
+ assertEquals("a", result.values[0]);
+ assertEquals("xy", result.values[1]);
+ }
+ }
+
+ public void testNullsSkipWithPrimitiveArrays() throws Exception
+ {
+ // int[]
+ {
+ final String JSON = aposToQuotes("{'values':[3,null,7]}");
+ NullContentSkip<int[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentSkip<int[]>>() { });
+ assertEquals(2, result.values.length);
+ assertEquals(3, result.values[0]);
+ assertEquals(7, result.values[1]);
+ }
+
+ // long[]
+ {
+ final String JSON = aposToQuotes("{'values':[-13,null,999]}");
+ NullContentSkip<long[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentSkip<long[]>>() { });
+ assertEquals(2, result.values.length);
+ assertEquals(-13L, result.values[0]);
+ assertEquals(999L, result.values[1]);
+ }
+
+ // boolean[]
+ {
+ final String JSON = aposToQuotes("{'values':[true,null,true]}");
+ NullContentSkip<boolean[]> result = MAPPER.readValue(JSON,
+ new TypeReference<NullContentSkip<boolean[]>>() { });
+ assertEquals(2, result.values.length);
+ assertEquals(true, result.values[0]);
+ assertEquals(true, result.values[1]);
+ }
+ }
+
+ public void testNullsSkipWithMaps() throws Exception
+ {
+ // Then: Map<String,String>
+ final String MAP_JSON = aposToQuotes("{'values':{'A':'foo','B':null,'C':'bar'}}");
+ {
+ NullContentSkip<Map<String,String>> result
+ = MAPPER.readValue(MAP_JSON, new TypeReference<NullContentSkip<Map<String,String>>>() { });
+ assertEquals(2, result.values.size());
+ assertEquals("foo", result.values.get("A"));
+ assertEquals("bar", result.values.get("C"));
+ }
+
+ // Then: EnumMap<Enum,String>
+ {
+ NullContentSkip<EnumMap<ABC,String>> result
+ = MAPPER.readValue(MAP_JSON, new TypeReference<NullContentSkip<EnumMap<ABC,String>>>() { });
+ assertEquals(2, result.values.size());
+ assertEquals("foo", result.values.get(ABC.A));
+ assertEquals("bar", result.values.get(ABC.C));
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsGenericTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsGenericTest.java
new file mode 100644
index 0000000..87df5f9
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsGenericTest.java
@@ -0,0 +1,126 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import com.fasterxml.jackson.databind.*;
+
+// for [databind#1402]; configurable null handling, for values themselves,
+// using generic types
+public class NullConversionsGenericTest extends BaseMapTest
+{
+ static class GeneralEmpty<T> {
+ // 09-Feb-2017, tatu: Should only need annotation either for field OR setter, not both:
+// @JsonSetter(nulls=JsonSetter.Nulls.AS_EMPTY)
+ T value;
+
+ @JsonSetter(nulls=Nulls.AS_EMPTY)
+ public void setValue(T v) {
+ value = v;
+ }
+ }
+
+ static class NoCtorWrapper {
+ @JsonSetter(nulls=Nulls.AS_EMPTY)
+ public NoCtorPOJO value;
+ }
+
+ static class NoCtorPOJO {
+ public NoCtorPOJO(boolean b) { }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testNullsToEmptyPojo() throws Exception
+ {
+ GeneralEmpty<Point> result = MAPPER.readValue(aposToQuotes("{'value':null}"),
+ new TypeReference<GeneralEmpty<Point>>() { });
+ assertNotNull(result.value);
+ Point p = result.value;
+ assertEquals(0, p.x);
+ assertEquals(0, p.y);
+
+ // and then also failing case with no suitable creator:
+ try {
+ /* NoCtorWrapper nogo =*/ MAPPER.readValue(aposToQuotes("{'value':null}"),
+ NoCtorWrapper.class);
+ fail("Should not pass");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Cannot create empty instance");
+ }
+ }
+
+ // [databind#2023] two-part coercion from "" to `null` to skip/empty/exception should work
+ public void testEmptyStringToNullToEmptyPojo() throws Exception
+ {
+ GeneralEmpty<Point> result = MAPPER.readerFor(new TypeReference<GeneralEmpty<Point>>() { })
+ .with(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
+ .readValue(aposToQuotes("{'value':''}"));
+ assertNotNull(result.value);
+ Point p = result.value;
+ assertEquals(0, p.x);
+ assertEquals(0, p.y);
+ }
+
+ public void testNullsToEmptyCollection() throws Exception
+ {
+ GeneralEmpty<List<String>> result = MAPPER.readValue(aposToQuotes("{'value':null}"),
+ new TypeReference<GeneralEmpty<List<String>>>() { });
+ assertNotNull(result.value);
+ assertEquals(0, result.value.size());
+
+ // but also non-String type, since impls vary
+ GeneralEmpty<List<Integer>> result2 = MAPPER.readValue(aposToQuotes("{'value':null}"),
+ new TypeReference<GeneralEmpty<List<Integer>>>() { });
+ assertNotNull(result2.value);
+ assertEquals(0, result2.value.size());
+ }
+
+ public void testNullsToEmptyMap() throws Exception
+ {
+ GeneralEmpty<Map<String,String>> result = MAPPER.readValue(aposToQuotes("{'value':null}"),
+ new TypeReference<GeneralEmpty<Map<String,String>>>() { });
+ assertNotNull(result.value);
+ assertEquals(0, result.value.size());
+ }
+
+ public void testNullsToEmptyArrays() throws Exception
+ {
+ final String json = aposToQuotes("{'value':null}");
+
+ GeneralEmpty<Object[]> result = MAPPER.readValue(json,
+ new TypeReference<GeneralEmpty<Object[]>>() { });
+ assertNotNull(result.value);
+ assertEquals(0, result.value.length);
+
+ GeneralEmpty<String[]> result2 = MAPPER.readValue(json,
+ new TypeReference<GeneralEmpty<String[]>>() { });
+ assertNotNull(result2.value);
+ assertEquals(0, result2.value.length);
+
+ GeneralEmpty<int[]> result3 = MAPPER.readValue(json,
+ new TypeReference<GeneralEmpty<int[]>>() { });
+ assertNotNull(result3.value);
+ assertEquals(0, result3.value.length);
+
+ GeneralEmpty<double[]> result4 = MAPPER.readValue(json,
+ new TypeReference<GeneralEmpty<double[]>>() { });
+ assertNotNull(result4.value);
+ assertEquals(0, result4.value.length);
+
+ GeneralEmpty<boolean[]> result5 = MAPPER.readValue(json,
+ new TypeReference<GeneralEmpty<boolean[]>>() { });
+ assertNotNull(result5.value);
+ assertEquals(0, result5.value.length);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsPojoTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsPojoTest.java
new file mode 100644
index 0000000..b022fc6
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsPojoTest.java
@@ -0,0 +1,107 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidNullException;
+
+// for [databind#1402]; configurable null handling, for values themselves
+public class NullConversionsPojoTest extends BaseMapTest
+{
+ static class NullFail {
+ public String nullsOk = "a";
+
+ @JsonSetter(nulls=Nulls.FAIL)
+ public String noNulls = "b";
+ }
+
+ static class NullAsEmpty {
+ public String nullsOk = "a";
+
+ @JsonSetter(nulls=Nulls.AS_EMPTY)
+ public String nullAsEmpty = "b";
+ }
+
+ static class NullsForString {
+ /*
+ String n = "foo";
+
+ public void setName(String name) {
+ n = name;
+ }
+ */
+
+ String n = "foo";
+
+ public void setName(String n0) { n = n0; }
+ public String getName() { return n; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testFailOnNull() throws Exception
+ {
+ // first, ok if assigning non-null to not-nullable, null for nullable
+ NullFail result = MAPPER.readValue(aposToQuotes("{'noNulls':'foo', 'nullsOk':null}"),
+ NullFail.class);
+ assertEquals("foo", result.noNulls);
+ assertNull(result.nullsOk);
+
+ // and then see that nulls are not ok for non-nullable
+ try {
+ result = MAPPER.readValue(aposToQuotes("{'noNulls':null}"),
+ NullFail.class);
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"noNulls\"");
+ }
+ }
+
+ public void testFailOnNullWithDefaults() throws Exception
+ {
+ // also: config overrides by type should work
+ String json = aposToQuotes("{'name':null}");
+ NullsForString def = MAPPER.readValue(json, NullsForString.class);
+ assertNull(def.getName());
+
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configOverride(String.class)
+ .setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.FAIL));
+ try {
+ mapper.readValue(json, NullsForString.class);
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"name\"");
+ }
+ }
+
+ public void testNullsToEmptyScalar() throws Exception
+ {
+ NullAsEmpty result = MAPPER.readValue(aposToQuotes("{'nullAsEmpty':'foo', 'nullsOk':null}"),
+ NullAsEmpty.class);
+ assertEquals("foo", result.nullAsEmpty);
+ assertNull(result.nullsOk);
+
+ // and then see that nulls are not ok for non-nullable
+ result = MAPPER.readValue(aposToQuotes("{'nullAsEmpty':null}"),
+ NullAsEmpty.class);
+ assertEquals("", result.nullAsEmpty);
+
+ // also: config overrides by type should work
+ String json = aposToQuotes("{'name':null}");
+ NullsForString def = MAPPER.readValue(json, NullsForString.class);
+ assertNull(def.getName());
+
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configOverride(String.class)
+ .setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
+ NullsForString named = mapper.readValue(json, NullsForString.class);
+ assertEquals("", named.getName());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsSkipTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsSkipTest.java
new file mode 100644
index 0000000..9fe93a3
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/NullConversionsSkipTest.java
@@ -0,0 +1,112 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.databind.*;
+
+// for [databind#1402]; configurable null handling, specifically with SKIP
+public class NullConversionsSkipTest extends BaseMapTest
+{
+ static class NullSkipField {
+ public String nullsOk = "a";
+
+ @JsonSetter(nulls=Nulls.SKIP)
+ public String noNulls = "b";
+ }
+
+ static class NullSkipMethod {
+ String _nullsOk = "a";
+ String _noNulls = "b";
+
+ public void setNullsOk(String v) {
+ _nullsOk = v;
+ }
+
+ @JsonSetter(nulls=Nulls.SKIP)
+ public void setNoNulls(String v) {
+ _noNulls = v;
+ }
+ }
+
+ static class StringValue {
+ String value = "default";
+
+ public void setValue(String v) {
+ value = v;
+ }
+ }
+
+ // for [databind#2015]
+ enum NUMS2015 {
+ ONE, TWO
+ }
+
+ public static class Pojo2015 {
+ @JsonSetter(value = "number", nulls = Nulls.SKIP)
+ NUMS2015 number = NUMS2015.TWO;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, straight annotation
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testSkipNullField() throws Exception
+ {
+ // first, ok if assigning non-null to not-nullable, null for nullable
+ NullSkipField result = MAPPER.readValue(aposToQuotes("{'noNulls':'foo', 'nullsOk':null}"),
+ NullSkipField.class);
+ assertEquals("foo", result.noNulls);
+ assertNull(result.nullsOk);
+
+ // and then see that nulls are not ok for non-nullable
+ result = MAPPER.readValue(aposToQuotes("{'noNulls':null}"),
+ NullSkipField.class);
+ assertEquals("b", result.noNulls);
+ assertEquals("a", result.nullsOk);
+ }
+
+ public void testSkipNullMethod() throws Exception
+ {
+ NullSkipMethod result = MAPPER.readValue(aposToQuotes("{'noNulls':'foo', 'nullsOk':null}"),
+ NullSkipMethod.class);
+ assertEquals("foo", result._noNulls);
+ assertNull(result._nullsOk);
+
+ result = MAPPER.readValue(aposToQuotes("{'noNulls':null}"),
+ NullSkipMethod.class);
+ assertEquals("b", result._noNulls);
+ assertEquals("a", result._nullsOk);
+ }
+
+ // for [databind#2015]
+ public void testEnumAsNullThenSkip() throws Exception
+ {
+ Pojo2015 p = MAPPER.readerFor(Pojo2015.class)
+ .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
+ .readValue("{\"number\":\"THREE\"}");
+ assertEquals(NUMS2015.TWO, p.number);
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, defaulting
+ /**********************************************************
+ */
+
+ public void testSkipNullWithDefaults() throws Exception
+ {
+ String json = aposToQuotes("{'value':null}");
+ StringValue result = MAPPER.readValue(json, StringValue.class);
+ assertNull(result.value);
+
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configOverride(String.class)
+ .setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SKIP));
+ result = mapper.readValue(json, StringValue.class);
+ assertEquals("default", result.value);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerLocation1440Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerLocation1440Test.java
new file mode 100644
index 0000000..14464e7
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerLocation1440Test.java
@@ -0,0 +1,141 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import java.io.IOException;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
+
+// Test(s) to verify [databind#1440]
+public class ProblemHandlerLocation1440Test extends BaseMapTest
+{
+ static class DeserializationProblem {
+ public List<String> unknownProperties = new ArrayList<>();
+
+ public DeserializationProblem() { }
+
+ public void addUnknownProperty(final String prop) {
+ unknownProperties.add(prop);
+ }
+ public boolean foundProblems() {
+ return !unknownProperties.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return "DeserializationProblem{" +"unknownProperties=" + unknownProperties +'}';
+ }
+ }
+
+ static class DeserializationProblemLogger extends DeserializationProblemHandler {
+
+ public DeserializationProblem probs = new DeserializationProblem();
+
+ public List<String> problems() {
+ return probs.unknownProperties;
+ }
+
+ @Override
+ public boolean handleUnknownProperty(final DeserializationContext ctxt, final JsonParser p,
+ JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName)
+ throws IOException
+ {
+ final JsonStreamContext parsingContext = p.getParsingContext();
+ final List<String> pathList = new ArrayList<>();
+ addParent(parsingContext, pathList);
+ Collections.reverse(pathList);
+ final String path = _join(".", pathList) + "#" + propertyName;
+
+ probs.addUnknownProperty(path);
+
+ p.skipChildren();
+ return true;
+ }
+
+ static String _join(String sep, Collection<String> parts) {
+ StringBuilder sb = new StringBuilder();
+ for (String part : parts) {
+ if (sb.length() > 0) {
+ sb.append(sep);
+ }
+ sb.append(part);
+ }
+ return sb.toString();
+ }
+
+ private void addParent(final JsonStreamContext streamContext, final List<String> pathList) {
+ if (streamContext != null && streamContext.getCurrentName() != null) {
+ pathList.add(streamContext.getCurrentName());
+ addParent(streamContext.getParent(), pathList);
+ }
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ static class Activity {
+ public ActivityEntity actor;
+ public String verb;
+ public ActivityEntity object;
+ public ActivityEntity target;
+
+ @JsonCreator
+ public Activity(@JsonProperty("actor") final ActivityEntity actor, @JsonProperty("object") final ActivityEntity object, @JsonProperty("target") final ActivityEntity target, @JsonProperty("verb") final String verb) {
+ this.actor = actor;
+ this.verb = verb;
+ this.object = object;
+ this.target = target;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ static class ActivityEntity {
+ public String id;
+ public String type;
+ public String status;
+ public String context;
+
+ @JsonCreator
+ public ActivityEntity(@JsonProperty("id") final String id, @JsonProperty("type") final String type, @JsonProperty("status") final String status, @JsonProperty("context") final String context) {
+ this.id = id;
+ this.type = type;
+ this.status = status;
+ this.context = context;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testIncorrectContext() throws Exception
+ {
+ // need invalid to trigger problem:
+ final String invalidInput = aposToQuotes(
+"{'actor': {'id': 'actor_id','type': 'actor_type',"
++"'status': 'actor_status','context':'actor_context','invalid_1': 'actor_invalid_1'},"
++"'verb': 'verb','object': {'id': 'object_id','type': 'object_type',"
++"'invalid_2': 'object_invalid_2','status': 'object_status','context': 'object_context'},"
++"'target': {'id': 'target_id','type': 'target_type','invalid_3': 'target_invalid_3',"
++"'invalid_4': 'target_invalid_4','status': 'target_status','context': 'target_context'}}"
+);
+
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ final DeserializationProblemLogger logger = new DeserializationProblemLogger();
+ mapper.addHandler(logger);
+ mapper.readValue(invalidInput, Activity.class);
+
+ List<String> probs = logger.problems();
+ assertEquals(4, probs.size());
+ assertEquals("actor.invalid_1#invalid_1", probs.get(0));
+ assertEquals("object.invalid_2#invalid_2", probs.get(1));
+ assertEquals("target.invalid_3#invalid_3", probs.get(2));
+ assertEquals("target.invalid_4#invalid_4", probs.get(3));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/ProblemHandlerTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java
similarity index 77%
rename from src/test/java/com/fasterxml/jackson/databind/filter/ProblemHandlerTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java
index 1a9f396..0bd73f5 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/ProblemHandlerTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.deser.filter;
import java.io.IOException;
import java.util.Map;
@@ -9,6 +9,8 @@
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
+import com.fasterxml.jackson.databind.deser.ValueInstantiator;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
@@ -96,6 +98,9 @@
Class<?> instClass, Object argument, Throwable t)
throws IOException
{
+ if (!(t instanceof InvalidDefinitionException)) {
+ throw new IllegalArgumentException("Should have gotten `InvalidDefinitionException`, instead got: "+t);
+ }
return value;
}
}
@@ -108,10 +113,10 @@
public MissingInstantiationHandler(Object v0) {
value = v0;
}
-
+
@Override
public Object handleMissingInstantiator(DeserializationContext ctxt,
- Class<?> instClass, JsonParser p, String msg)
+ Class<?> instClass, ValueInstantiator inst, JsonParser p, String msg)
throws IOException
{
p.skipChildren();
@@ -123,11 +128,11 @@
extends DeserializationProblemHandler
{
protected final Object value;
-
+
public WeirdTokenHandler(Object v) {
value = v;
}
-
+
@Override
public Object handleUnexpectedToken(DeserializationContext ctxt,
Class<?> targetType, JsonToken t, JsonParser p,
@@ -138,12 +143,12 @@
}
}
- static class TypeIdHandler
+ static class UnknownTypeIdHandler
extends DeserializationProblemHandler
{
protected final Class<?> raw;
- public TypeIdHandler(Class<?> r) { raw = r; }
+ public UnknownTypeIdHandler(Class<?> r) { raw = r; }
@Override
public JavaType handleUnknownTypeId(DeserializationContext ctxt,
@@ -155,6 +160,23 @@
}
}
+ static class MissingTypeIdHandler
+ extends DeserializationProblemHandler
+ {
+ protected final Class<?> raw;
+
+ public MissingTypeIdHandler(Class<?> r) { raw = r; }
+
+ @Override
+ public JavaType handleMissingTypeId(DeserializationContext ctxt,
+ JavaType baseType, TypeIdResolver idResolver,
+ String failureMsg)
+ throws IOException
+ {
+ return ctxt.constructType(raw);
+ }
+ }
+
/*
/**********************************************************
/* Other helper types
@@ -193,7 +215,7 @@
public final static BustedCtor INST = new BustedCtor(true);
public BustedCtor() {
- throw new RuntimeException("Fail!");
+ throw new RuntimeException("Fail! (to be caught by handler)");
}
private BustedCtor(boolean b) { }
}
@@ -210,11 +232,11 @@
/**********************************************************
*/
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newObjectMapper();
public void testWeirdKeyHandling() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
+ ObjectMapper mapper = newObjectMapper()
.addHandler(new WeirdKeyHandler(7));
IntKeyMapWrapper w = mapper.readValue("{\"stuff\":{\"foo\":\"abc\"}}",
IntKeyMapWrapper.class);
@@ -226,7 +248,7 @@
public void testWeirdNumberHandling() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
+ ObjectMapper mapper = newObjectMapper()
.addHandler(new WeirdNumberHandler(SingleValuedEnum.A))
;
SingleValuedEnum result = mapper.readValue("3", SingleValuedEnum.class);
@@ -235,7 +257,7 @@
public void testWeirdStringHandling() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
+ ObjectMapper mapper = newObjectMapper()
.addHandler(new WeirdStringHandler(SingleValuedEnum.A))
;
SingleValuedEnum result = mapper.readValue("\"B\"", SingleValuedEnum.class);
@@ -250,25 +272,46 @@
public void testInvalidTypeId() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
- .addHandler(new TypeIdHandler(BaseImpl.class));
+ ObjectMapper mapper = newObjectMapper()
+ .addHandler(new UnknownTypeIdHandler(BaseImpl.class));
BaseWrapper w = mapper.readValue("{\"value\":{\"type\":\"foo\",\"a\":4}}",
BaseWrapper.class);
assertNotNull(w);
assertEquals(BaseImpl.class, w.value.getClass());
}
-
public void testInvalidClassAsId() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
- .addHandler(new TypeIdHandler(Base2Impl.class));
+ ObjectMapper mapper = newObjectMapper()
+ .addHandler(new UnknownTypeIdHandler(Base2Impl.class));
Base2Wrapper w = mapper.readValue("{\"value\":{\"clazz\":\"com.fizz\",\"a\":4}}",
Base2Wrapper.class);
assertNotNull(w);
assertEquals(Base2Impl.class, w.value.getClass());
}
+ // 2.9: missing type id, distinct from unknown
+
+ public void testMissingTypeId() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper()
+ .addHandler(new MissingTypeIdHandler(BaseImpl.class));
+ BaseWrapper w = mapper.readValue("{\"value\":{\"a\":4}}",
+ BaseWrapper.class);
+ assertNotNull(w);
+ assertEquals(BaseImpl.class, w.value.getClass());
+ }
+
+ public void testMissingClassAsId() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper()
+ .addHandler(new MissingTypeIdHandler(Base2Impl.class));
+ Base2Wrapper w = mapper.readValue("{\"value\":{\"a\":4}}",
+ Base2Wrapper.class);
+ assertNotNull(w);
+ assertEquals(Base2Impl.class, w.value.getClass());
+ }
+
// verify that by default we get special exception type
public void testInvalidTypeIdFail() throws Exception
{
@@ -285,7 +328,7 @@
public void testInstantiationExceptionHandling() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
+ ObjectMapper mapper = newObjectMapper()
.addHandler(new InstantiationProblemHandler(BustedCtor.INST));
BustedCtor w = mapper.readValue("{ }",
BustedCtor.class);
@@ -294,7 +337,7 @@
public void testMissingInstantiatorHandling() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
+ ObjectMapper mapper = newObjectMapper()
.addHandler(new MissingInstantiationHandler(new NoDefaultCtor(13)))
;
NoDefaultCtor w = mapper.readValue("{ \"x\" : true }", NoDefaultCtor.class);
@@ -304,7 +347,7 @@
public void testUnexpectedTokenHandling() throws Exception
{
- ObjectMapper mapper = new ObjectMapper()
+ ObjectMapper mapper = newObjectMapper()
.addHandler(new WeirdTokenHandler(Integer.valueOf(13)))
;
Integer v = mapper.readValue("true", Integer.class);
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerUnknownTypeId2221Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerUnknownTypeId2221Test.java
new file mode 100644
index 0000000..7cd1b4a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ProblemHandlerUnknownTypeId2221Test.java
@@ -0,0 +1,109 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import java.io.*;
+import java.util.Collection;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
+import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
+
+// for [databind#2221]
+public class ProblemHandlerUnknownTypeId2221Test extends BaseMapTest
+{
+ @SuppressWarnings("rawtypes")
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "_class")
+ @JsonInclude(Include.NON_EMPTY)
+ static class GenericContent {
+
+ private Collection innerObjects;
+
+ public Collection getInnerObjects() {
+ return innerObjects;
+ }
+
+ public void setInnerObjects(Collection innerObjects) {
+ this.innerObjects = innerObjects;
+ }
+ }
+
+ static class DummyContent {
+ private String aField;
+
+ public DummyContent() {
+ super();
+ }
+
+ public DummyContent(String aField) {
+ super();
+ this.aField = aField;
+ }
+
+ public String getaField() {
+ return aField;
+ }
+
+ public void setaField(String aField) {
+ this.aField = aField;
+ }
+
+ @Override
+ public String toString() {
+ return "DummyContent [aField=" + aField + "]";
+ }
+ }
+
+ private final static String CLASS_GENERIC_CONTENT = GenericContent.class.getName();
+ private final static String CLASS_DUMMY_CONTENT = DummyContent.class.getName();
+ private final static String JSON = aposToQuotes(
+"{\n" +
+" \"_class\":\""+CLASS_GENERIC_CONTENT+"\",\n" +
+" \"innerObjects\":\n" +
+" [\n" +
+" \"java.util.ArrayList\",\n" +
+" [\n" +
+" [\n" +
+" \""+CLASS_DUMMY_CONTENT+"\",\n" +
+" {\n" +
+" \"aField\":\"some value\"\n" +
+" }\n" +
+" ],\n" +
+" [\n" +
+" \"com.fasterxml.jackson.databind.deser.NoSuchClass$AnInventedClassBeingNotOnTheClasspath\",\n" +
+" {\n" +
+" \"aField\":\"some value\"\n" +
+" }\n" +
+" ]\n" +
+" ]\n" +
+" ]\n" +
+" }"
+);
+
+ public void testWithDeserializationProblemHandler() throws Exception {
+ final ObjectMapper mapper = new ObjectMapper()
+ .enableDefaultTyping();
+ mapper.addHandler(new DeserializationProblemHandler() {
+ @Override
+ public JavaType handleUnknownTypeId(DeserializationContext ctxt, JavaType baseType, String subTypeId, TypeIdResolver idResolver, String failureMsg) throws IOException {
+// System.out.println("Print out a warning here");
+ return ctxt.constructType(Void.class);
+ }
+ });
+ GenericContent processableContent = mapper.readValue(JSON, GenericContent.class);
+ assertNotNull(processableContent.getInnerObjects());
+ assertEquals(2, processableContent.getInnerObjects().size());
+ }
+
+ public void testWithDisabledFAIL_ON_INVALID_SUBTYPE() throws Exception {
+ final ObjectMapper mapper = new ObjectMapper()
+ .disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE)
+ .enableDefaultTyping()
+ ;
+ GenericContent processableContent = mapper.readValue(JSON, GenericContent.class);
+ assertNotNull(processableContent.getInnerObjects());
+ assertEquals(2, processableContent.getInnerObjects().size());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/ReadOnlyDeser1890Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ReadOnlyDeser1890Test.java
new file mode 100644
index 0000000..0756a99
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ReadOnlyDeser1890Test.java
@@ -0,0 +1,95 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import java.beans.ConstructorProperties;
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ReadOnlyDeser1890Test
+ extends BaseMapTest
+{
+ public static class PersonAnnotations {
+ public String name;
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private TestEnum testEnum = TestEnum.DEFAULT;
+
+ PersonAnnotations() { }
+
+ @ConstructorProperties({"testEnum", "name"})
+ public PersonAnnotations(TestEnum testEnum, String name) {
+ this.testEnum = testEnum;
+ this.name = name;
+ }
+
+ public TestEnum getTestEnum() {
+ return testEnum;
+ }
+
+ public void setTestEnum(TestEnum testEnum) {
+ this.testEnum = testEnum;
+ }
+ }
+
+ public static class Person {
+ public String name;
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private TestEnum testEnum = TestEnum.DEFAULT;
+
+ Person() { }
+
+ public Person(TestEnum testEnum, String name) {
+ this.testEnum = testEnum;
+ this.name = name;
+ }
+
+ public TestEnum getTestEnum() {
+ return testEnum;
+ }
+
+ public void setTestEnum(TestEnum testEnum) {
+ this.testEnum = testEnum;
+ }
+ }
+
+ enum TestEnum{
+ DEFAULT, TEST;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = objectMapper();
+
+ public void testDeserializeAnnotationsOneField() throws IOException {
+ PersonAnnotations person = MAPPER.readValue("{\"testEnum\":\"\"}", PersonAnnotations.class);
+ // can not remain as is, so becomes `null`
+ assertEquals(null, person.getTestEnum());
+ assertNull(person.name);
+ }
+
+ public void testDeserializeAnnotationsTwoFields() throws IOException {
+ PersonAnnotations person = MAPPER.readValue("{\"testEnum\":\"\",\"name\":\"changyong\"}",
+ PersonAnnotations.class);
+ // can not remain as is, so becomes `null`
+ assertEquals(null, person.getTestEnum());
+ assertEquals("changyong", person.name);
+ }
+
+ public void testDeserializeOneField() throws IOException {
+ Person person = MAPPER.readValue("{\"testEnum\":\"\"}", Person.class);
+ assertEquals(TestEnum.DEFAULT, person.getTestEnum());
+ assertNull(person.name);
+ }
+
+ public void testDeserializeTwoFields() throws IOException {
+ Person person = MAPPER.readValue("{\"testEnum\":\"\",\"name\":\"changyong\"}",
+ Person.class);
+ assertEquals(TestEnum.DEFAULT, person.getTestEnum());
+ assertEquals("changyong", person.name);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/ReadOnlyProperties95Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ReadOnlyDeser95Test.java
similarity index 87%
rename from src/test/java/com/fasterxml/jackson/databind/filter/ReadOnlyProperties95Test.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/filter/ReadOnlyDeser95Test.java
index a00b5c6..2462141 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/ReadOnlyProperties95Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/ReadOnlyDeser95Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.deser.filter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.*;
@@ -6,7 +6,7 @@
/**
* Failing test related to [databind#95]
*/
-public class ReadOnlyProperties95Test extends BaseMapTest
+public class ReadOnlyDeser95Test extends BaseMapTest
{
@JsonIgnoreProperties(value={ "computed" }, allowGetters=true)
static class ReadOnlyBean
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/filter/RecursiveIgnorePropertiesTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/RecursiveIgnorePropertiesTest.java
new file mode 100644
index 0000000..e3a2c7f
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/RecursiveIgnorePropertiesTest.java
@@ -0,0 +1,70 @@
+package com.fasterxml.jackson.databind.deser.filter;
+
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import com.fasterxml.jackson.databind.*;
+
+public class RecursiveIgnorePropertiesTest extends BaseMapTest
+{
+ static class Person {
+ public String name;
+
+ @JsonProperty("person_z") // renaming this to person_p works
+ @JsonIgnoreProperties({"person_z"}) // renaming this to person_p works
+ public Person personZ;
+ }
+
+ static class Persons {
+ public String name;
+
+ @JsonProperty("person_z") // renaming this to person_p works
+ @JsonIgnoreProperties({"person_z"}) // renaming this to person_p works
+ public Set<Persons> personZ;
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testRecursiveForDeser() throws Exception
+ {
+ String st = aposToQuotes("{ 'name': 'admin',\n"
+ + " 'person_z': { 'name': 'wyatt' }"
+ + "}");
+ Person result = MAPPER.readValue(st, Person.class);
+ assertEquals("admin", result.name);
+ assertNotNull(result.personZ);
+ assertEquals("wyatt", result.personZ.name);
+ }
+
+ public void testRecursiveWithCollectionDeser() throws Exception
+ {
+ String st = aposToQuotes("{ 'name': 'admin',\n"
+ + " 'person_z': [ { 'name': 'Foor' }, { 'name' : 'Bar' } ]"
+ + "}");
+ Persons result = MAPPER.readValue(st, Persons.class);
+ assertEquals("admin", result.name);
+ assertNotNull(result.personZ);
+ assertEquals(2, result.personZ.size());
+ }
+
+ public void testRecursiveForSer() throws Exception
+ {
+ Person input = new Person();
+ input.name = "Bob";
+ Person p2 = new Person();
+ p2.name = "Bill";
+ input.personZ = p2;
+ p2.personZ = input;
+
+ String json = MAPPER.writeValueAsString(input);
+ assertNotNull(json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/TestUnknownPropertyDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/filter/TestUnknownPropertyDeserialization.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/filter/TestUnknownPropertyDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/filter/TestUnknownPropertyDeserialization.java
index b362822..dacf423 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/TestUnknownPropertyDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/filter/TestUnknownPropertyDeserialization.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.deser.filter;
import java.io.*;
import java.util.*;
@@ -125,7 +125,7 @@
/**********************************************************
*/
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newObjectMapper();
/**
* By default we should just get an exception if an unknown property
@@ -146,7 +146,7 @@
*/
public void testUnknownHandlingIgnoreWithHandler() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
+ ObjectMapper mapper = newObjectMapper();
mapper.clearProblemHandlers();
mapper.addHandler(new MyHandler());
TestBean result = mapper.readValue(new StringReader(JSON_UNKNOWN_FIELD), TestBean.class);
@@ -162,7 +162,7 @@
*/
public void testUnknownHandlingIgnoreWithHandlerAndObjectReader() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
+ ObjectMapper mapper = newObjectMapper();
mapper.clearProblemHandlers();
TestBean result = mapper.readerFor(TestBean.class).withHandler(new MyHandler()).readValue(new StringReader(JSON_UNKNOWN_FIELD));
assertNotNull(result);
@@ -177,7 +177,7 @@
*/
public void testUnknownHandlingIgnoreWithFeature() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
+ ObjectMapper mapper = newObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
TestBean result = null;
try {
@@ -271,7 +271,7 @@
public void testIssue987() throws Exception
{
- ObjectMapper jsonMapper = new ObjectMapper();
+ ObjectMapper jsonMapper = newObjectMapper();
jsonMapper.addHandler(new DeserializationProblemHandler() {
@Override
public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName) throws IOException, JsonProcessingException {
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/inject/InvalidInjectionTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/inject/InvalidInjectionTest.java
new file mode 100644
index 0000000..a6e2543
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/inject/InvalidInjectionTest.java
@@ -0,0 +1,45 @@
+package com.fasterxml.jackson.databind.deser.inject;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+
+public class InvalidInjectionTest extends BaseMapTest
+{
+ static class BadBean1 {
+ @JacksonInject protected String prop1;
+ @JacksonInject protected String prop2;
+ }
+
+ static class BadBean2 {
+ @JacksonInject("x") protected String prop1;
+ @JacksonInject("x") protected String prop2;
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testInvalidDup() throws Exception
+ {
+ try {
+ MAPPER.readValue("{}", BadBean1.class);
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Duplicate injectable value");
+ }
+ try {
+ MAPPER.readValue("{}", BadBean2.class);
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Duplicate injectable value");
+ }
+ }
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestInjectables.java b/src/test/java/com/fasterxml/jackson/databind/deser/inject/TestInjectables.java
similarity index 76%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestInjectables.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/inject/TestInjectables.java
index 2a1c3ef..49acd07 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestInjectables.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/inject/TestInjectables.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.inject;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
@@ -21,16 +21,6 @@
public void injectThird(long v) {
third = v;
}
- }
-
- static class BadBean1 {
- @JacksonInject protected String prop1;
- @JacksonInject protected String prop2;
- }
-
- static class BadBean2 {
- @JacksonInject("x") protected String prop1;
- @JacksonInject("x") protected String prop2;
}
static class CtorBean {
@@ -55,22 +45,30 @@
}
}
- static class IssueGH471Bean {
+ // [databind#77]
+ static class TransientBean {
+ @JacksonInject("transient")
+ transient Object injected;
+
+ public int value;
+ }
+
+ static class Bean471 {
protected final Object constructorInjected;
protected final String constructorValue;
@JacksonInject("field_injected") protected Object fieldInjected;
- @JsonProperty("field_value") protected String fieldValue;
+ @JsonProperty("field_value") protected String fieldValue;
protected Object methodInjected;
protected String methodValue;
public int x;
-
+
@JsonCreator
- private IssueGH471Bean(@JacksonInject("constructor_injected") Object constructorInjected,
- @JsonProperty("constructor_value") String constructorValue) {
+ private Bean471(@JacksonInject("constructor_injected") Object constructorInjected,
+ @JsonProperty("constructor_value") String constructorValue) {
this.constructorInjected = constructorInjected;
this.constructorValue = constructorValue;
}
@@ -86,25 +84,17 @@
}
}
- // [databind#77]
- static class TransientBean {
- @JacksonInject("transient")
- transient Object injected;
-
- public int value;
- }
-
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newObjectMapper();
public void testSimple() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
+ ObjectMapper mapper = newObjectMapper();
mapper.setInjectableValues(new InjectableValues.Std()
.addValue(String.class, "stuffValue")
.addValue("myId", "xyz")
@@ -127,7 +117,6 @@
assertEquals("Bubba", bean.name);
}
- // [Issue-13]
public void testTwoInjectablesViaCreator() throws Exception
{
CtorBean2 bean = MAPPER.readerFor(CtorBean2.class)
@@ -139,34 +128,22 @@
assertEquals("Bob", bean.name);
}
- public void testInvalidDup() throws Exception
- {
- try {
- MAPPER.readValue("{}", BadBean1.class);
- } catch (Exception e) {
- verifyException(e, "Duplicate injectable value");
- }
- try {
- MAPPER.readValue("{}", BadBean2.class);
- } catch (Exception e) {
- verifyException(e, "Duplicate injectable value");
- }
- }
-
- public void testIssueGH471() throws Exception
+ // [databind#471]
+ public void testIssue471() throws Exception
{
final Object constructorInjected = "constructorInjected";
final Object methodInjected = "methodInjected";
final Object fieldInjected = "fieldInjected";
- ObjectMapper mapper = new ObjectMapper()
+ ObjectMapper mapper = newObjectMapper()
.setInjectableValues(new InjectableValues.Std()
.addValue("constructor_injected", constructorInjected)
.addValue("method_injected", methodInjected)
.addValue("field_injected", fieldInjected));
- IssueGH471Bean bean = mapper.readValue("{\"x\":13,\"constructor_value\":\"constructor\",\"method_value\":\"method\",\"field_value\":\"field\"}",
- IssueGH471Bean.class);
+ Bean471 bean = mapper.readValue(aposToQuotes(
+"{'x':13,'constructor_value':'constructor','method_value':'method','field_value':'field'}"),
+ Bean471.class);
/* Assert *SAME* instance */
assertSame(constructorInjected, bean.constructorInjected);
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/Base64DecodingTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/Base64DecodingTest.java
new file mode 100644
index 0000000..9e57066
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/Base64DecodingTest.java
@@ -0,0 +1,50 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import java.nio.charset.StandardCharsets;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+// Mostly for [databind#1425]; not in optimal place (as it also has
+// tree-access tests), but has to do for now
+public class Base64DecodingTest extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = objectMapper();
+
+ private final byte[] HELLO_BYTES = "hello".getBytes(StandardCharsets.UTF_8);
+ private final String BASE64_HELLO = "aGVsbG8=";
+
+ // for [databind#1425]
+ public void testInvalidBase64() throws Exception
+ {
+ byte[] b = MAPPER.readValue(quote(BASE64_HELLO), byte[].class);
+ assertEquals(HELLO_BYTES, b);
+
+ _testInvalidBase64(MAPPER, BASE64_HELLO+"!");
+ _testInvalidBase64(MAPPER, BASE64_HELLO+"!!");
+ }
+
+ private void _testInvalidBase64(ObjectMapper mapper, String value) throws Exception
+ {
+ // First, use data-binding
+ try {
+ MAPPER.readValue(quote(value), byte[].class);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Failed to decode");
+ verifyException(e, "as base64");
+ verifyException(e, "Illegal character '!'");
+ }
+
+ // and then tree model
+ JsonNode tree = mapper.readTree(String.format("{\"foo\":\"%s\"}", value));
+ JsonNode nodeValue = tree.get("foo");
+ try {
+ /*byte[] b =*/ nodeValue.binaryValue();
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot access contents of TextNode as binary");
+ verifyException(e, "Illegal character '!'");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestCollectionDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CollectionDeserTest.java
similarity index 90%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestCollectionDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/CollectionDeserTest.java
index e67156d..e71fa5e 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestCollectionDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CollectionDeserTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.IOException;
import java.util.*;
@@ -11,7 +11,7 @@
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
@SuppressWarnings("serial")
-public class TestCollectionDeserialization
+public class CollectionDeserTest
extends BaseMapTest
{
enum Key {
@@ -42,11 +42,20 @@
public XBean(int x) { this.x = x; }
}
- // [Issue#199]
+ // [databind#199]
static class ListAsIterable {
public Iterable<String> values;
}
+ // [databind#2251]
+ static class ListAsAbstract {
+ public AbstractList<String> values;
+ }
+
+ static class SetAsAbstract {
+ public AbstractSet<String> values;
+ }
+
static class ListAsIterableX {
public Iterable<XBean> nums;
}
@@ -226,7 +235,7 @@
MAPPER.readValue(OBJECTS_JSON, Key[].class);
fail("Should not pass");
} catch (JsonMappingException e) {
- verifyException(e, "Can not deserialize");
+ verifyException(e, "Cannot deserialize");
List<JsonMappingException.Reference> refs = e.getPath();
assertEquals(1, refs.size());
assertEquals(1, refs.get(0).getIndex());
@@ -236,7 +245,7 @@
MAPPER.readValue("[ \"xyz\", { } ]", String[].class);
fail("Should not pass");
} catch (JsonMappingException e) {
- verifyException(e, "Can not deserialize");
+ verifyException(e, "Cannot deserialize");
List<JsonMappingException.Reference> refs = e.getPath();
assertEquals(1, refs.size());
assertEquals(1, refs.get(0).getIndex());
@@ -246,7 +255,7 @@
MAPPER.readValue("{\"keys\":"+OBJECTS_JSON+"}", KeyListBean.class);
fail("Should not pass");
} catch (JsonMappingException e) {
- verifyException(e, "Can not deserialize");
+ verifyException(e, "Cannot deserialize");
List<JsonMappingException.Reference> refs = e.getPath();
assertEquals(2, refs.size());
// Bean has no index, but has name:
@@ -285,21 +294,17 @@
}
}
- // And then a round-trip test for singleton collections
- public void testSingletonCollections() throws Exception
+ // [databind#2251]
+ public void testAbstractListAndSet() throws Exception
{
- final TypeReference<?> xbeanListType = new TypeReference<List<XBean>>() { };
+ final String JSON = "{\"values\":[\"foo\", \"bar\"]}";
- String json = MAPPER.writeValueAsString(Collections.singleton(new XBean(3)));
- Collection<XBean> result = MAPPER.readValue(json, xbeanListType);
- assertNotNull(result);
- assertEquals(1, result.size());
- assertEquals(3, result.iterator().next().x);
+ ListAsAbstract list = MAPPER.readValue(JSON, ListAsAbstract.class);
+ assertEquals(2, list.values.size());
+ assertEquals(ArrayList.class, list.values.getClass());
- json = MAPPER.writeValueAsString(Collections.singletonList(new XBean(28)));
- result = MAPPER.readValue(json, xbeanListType);
- assertNotNull(result);
- assertEquals(1, result.size());
- assertEquals(28, result.iterator().next().x);
+ SetAsAbstract set = MAPPER.readValue(JSON, SetAsAbstract.class);
+ assertEquals(2, set.values.size());
+ assertEquals(HashSet.class, set.values.getClass());
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/DateDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTZTest.java
similarity index 72%
rename from src/test/java/com/fasterxml/jackson/databind/deser/DateDeserializationTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTZTest.java
index 8a7a0b5..f4cd977 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/DateDeserializationTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTZTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.math.BigInteger;
import java.text.DateFormat;
@@ -9,20 +9,23 @@
import java.util.TimeZone;
import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.BaseMapTest;
-import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+/**
+ * Additional `java.util.Date` deserialization tests for cases where `ObjectMapper`
+ * is configured to use timezone different from UTC.
+ */
@SuppressWarnings("javadoc")
-public class DateDeserializationTest
+public class DateDeserializationTZTest
extends BaseMapTest
{
private static final String LOCAL_TZ = "GMT+2";
private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
-
+
static class Annot_TimeZone {
@JsonFormat(timezone="GMT+4")
private java.util.Date date;
@@ -94,23 +97,19 @@
// According to ISO8601, hours and minutes of the offset must be expressed with 2 digits
// (not more, not less), i.e. Z or +hh:mm or -hh:mm. See https://www.w3.org/TR/NOTE-datetime.
//
- // The forms below should be refused but some are accepted by the StdDateFormat. They are
- // included in the test to detect any change in behavior in futur releases...
+ // The forms below are therefore ILLEGAL and must be refused.
// ---------------------------------------------------------------------------------------------
- // Interpreted as if there was no timezone, therefore producing a date with the TZ set on the mapper
- // FIXME it is probably better to refuse these cases instead of silently creating dates in local tz...
- verify( MAPPER, "2000-01-02T03:04:05.678+", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:04:05.678+1", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
- // FIXME this should probably give GMT+1
- verify( MAPPER, "2000-01-02T03:04:05.678+001", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:04:05.678+00:", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:04:05.678+00:001", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:04:05.678+001:001", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
+ failure( MAPPER, "2000-01-02T03:04:05.678+");
+ failure( MAPPER, "2000-01-02T03:04:05.678+1");
- // Considering the above forms have been accepted, it is strange the following are refused...
- failure( MAPPER, "2000-01-02T03:04:05.678+1:"); // FIXME
- failure( MAPPER, "2000-01-02T03:04:05.678+00:1"); // FIXME
+ failure( MAPPER, "2000-01-02T03:04:05.678+001");
+ failure( MAPPER, "2000-01-02T03:04:05.678+00:");
+ failure( MAPPER, "2000-01-02T03:04:05.678+00:001");
+ failure( MAPPER, "2000-01-02T03:04:05.678+001:001");
+
+ failure( MAPPER, "2000-01-02T03:04:05.678+1:");
+ failure( MAPPER, "2000-01-02T03:04:05.678+00:1");
}
/**
@@ -119,34 +118,30 @@
public void testDateUtilISO8601_DateTimeMillis() throws Exception
{
// WITH timezone (from 4 to 0 digits)
- failure(MAPPER, "2000-01-02T03:04:05.6789+01:00");
- verify( MAPPER, "2000-01-02T03:04:05.678+01:00", judate(2000, 1, 2, 3, 4, 5, 678, "GMT+1"));
- verify( MAPPER, "2000-01-02T03:04:05.67+01:00", judate(2000, 1, 2, 3, 4, 5, 670, "GMT+1"));
- verify( MAPPER, "2000-01-02T03:04:05.6+01:00", judate(2000, 1, 2, 3, 4, 5, 600, "GMT+1"));
- verify( MAPPER, "2000-01-02T03:04:05+01:00", judate(2000, 1, 2, 3, 4, 5, 000, "GMT+1"));
+ failure(MAPPER, "2000-01-02T03:04:05.0123456789+01:00"); // at most 9 digits for the millis
+ verify( MAPPER, "2000-01-02T03:04:05.6789+01:00", judate(2000, 1, 2, 3, 4, 5, 678, "GMT+1"));
+ verify( MAPPER, "2000-01-02T03:04:05.678+01:00", judate(2000, 1, 2, 3, 4, 5, 678, "GMT+1"));
+ verify( MAPPER, "2000-01-02T03:04:05.67+01:00", judate(2000, 1, 2, 3, 4, 5, 670, "GMT+1"));
+ verify( MAPPER, "2000-01-02T03:04:05.6+01:00", judate(2000, 1, 2, 3, 4, 5, 600, "GMT+1"));
+ verify( MAPPER, "2000-01-02T03:04:05+01:00", judate(2000, 1, 2, 3, 4, 5, 000, "GMT+1"));
-
+
// WITH timezone Z (from 4 to 0 digits)
- verify( MAPPER, "2000-01-02T03:04:05.6789Z", judate(2000, 1, 2, 3, 4, 11, 789, "UTC"));
- // FIXME the .6789 millis are interpreted as 6789 millisecondes or 6.789 seconds!
- verify( MAPPER, "2000-01-02T03:04:05.678Z", judate(2000, 1, 2, 3, 4, 5, 678, "UTC"));
- verify( MAPPER, "2000-01-02T03:04:05.67Z", judate(2000, 1, 2, 3, 4, 5, 67, "UTC"));
- // FIXME should be 670 millis instead of 67
- verify( MAPPER, "2000-01-02T03:04:05.6Z", judate(2000, 1, 2, 3, 4, 5, 6, "UTC"));
- // FIXME should be 600 millis instead of 6
- verify( MAPPER, "2000-01-02T03:04:05Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
+ failure(MAPPER, "2000-01-02T03:04:05.0123456789Z"); // at most 9 digits for the millis
+ verify( MAPPER, "2000-01-02T03:04:05.6789Z", judate(2000, 1, 2, 3, 4, 5, 678, "UTC"));
+ verify( MAPPER, "2000-01-02T03:04:05.678Z", judate(2000, 1, 2, 3, 4, 5, 678, "UTC"));
+ verify( MAPPER, "2000-01-02T03:04:05.67Z", judate(2000, 1, 2, 3, 4, 5, 670, "UTC"));
+ verify( MAPPER, "2000-01-02T03:04:05.6Z", judate(2000, 1, 2, 3, 4, 5, 600, "UTC"));
+ verify( MAPPER, "2000-01-02T03:04:05Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
// WITHOUT timezone (from 4 to 0 digits)
- verify(MAPPER, "2000-01-02T03:04:05.6789", judate(2000, 1, 2, 3, 4, 11, 789, LOCAL_TZ));
- // FIXME: the .6789 millis are interpreted as 6789 millisecondes or 6.789 seconds!
-
- verify( MAPPER, "2000-01-02T03:04:05.678", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:04:05.67", judate(2000, 1, 2, 3, 5, 12, 000, LOCAL_TZ));
- // FIXME: the .67 millis are interpreted as 67 seconds.
-
- verify( MAPPER, "2000-01-02T03:04:05.6", judate(2000, 1, 2, 3, 4, 5, 600, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:04:05", judate(2000, 1, 2, 3, 4, 5, 000, LOCAL_TZ));
+ failure(MAPPER, "2000-01-02T03:04:05.0123456789"); // at most 9 digits for the millis
+ verify( MAPPER, "2000-01-02T03:04:05.6789", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
+ verify( MAPPER, "2000-01-02T03:04:05.678", judate(2000, 1, 2, 3, 4, 5, 678, LOCAL_TZ));
+ verify( MAPPER, "2000-01-02T03:04:05.67", judate(2000, 1, 2, 3, 4, 5, 670, LOCAL_TZ));
+ verify( MAPPER, "2000-01-02T03:04:05.6", judate(2000, 1, 2, 3, 4, 5, 600, LOCAL_TZ));
+ verify( MAPPER, "2000-01-02T03:04:05", judate(2000, 1, 2, 3, 4, 5, 000, LOCAL_TZ));
// ---------------------------------------------------------------------------------------------
@@ -161,17 +156,16 @@
// time-secfrac = "." 1*DIGIT
// partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
//
- // The second fraction (ie the millis) is optional and can be ommitted. However, a fraction
+ // The second fraction (ie the millis) is optional and can be omitted. However, a fraction
// with only a dot (.) and no digit is not allowed.
//
- // The forms below should be refused but some are accepted by the StdDateFormat. They are
- // included in the test to detect any change in behavior in futur releases...
+ // The forms below are therefore ILLEGAL and must be refused.
// ---------------------------------------------------------------------------------------------
// millis part with only a dot (.) and no digits
- verify( MAPPER, "2000-01-02T03:04:05.+01:00", judate(2000, 1, 2, 3, 4, 5, 000, "GMT+1"));
- verify( MAPPER, "2000-01-02T03:04:05.", judate(2000, 1, 2, 3, 4, 5, 000, LOCAL_TZ));
- failure(MAPPER, "2000-01-02T03:04:05.Z"); // FIXME this one fails, but not the others...
+ failure( MAPPER, "2000-01-02T03:04:05.+01:00");
+ failure( MAPPER, "2000-01-02T03:04:05.");
+ failure( MAPPER, "2000-01-02T03:04:05.Z");
}
@@ -188,25 +182,24 @@
// No timezone --> the one configured on the ObjectMapper must be used
verify(MAPPER, "2000-01-02T03:04:05", judate(2000, 1, 2, 3, 4, 5, 0, LOCAL_TZ));
- // Hours, minutes and seconds are mandatory when time is specified
+ // Hours, minutes are mandatory but seconds are optional
failure(MAPPER, "2000-01-02T");
failure(MAPPER, "2000-01-02T03");
failure(MAPPER, "2000-01-02T03:");
- failure(MAPPER, "2000-01-02T03:04");
+ verify(MAPPER, "2000-01-02T03:04", judate(2000, 1, 2, 3, 4, 0, 0, LOCAL_TZ));
failure(MAPPER, "2000-01-02T03:04:");
-
- // Although hours, minutes and seconds are mandatory, they can sometimes be omitted
- // if a TZ is specified... !!??
+
+ // Hours, minutes are mandatory but seconds are optional - test with a TZ
failure(MAPPER, "2000-01-02T+01:00");
failure(MAPPER, "2000-01-02T03+01:00");
failure(MAPPER, "2000-01-02T03:+01:00");
- verify( MAPPER, "2000-01-02T03:04+01:00", judate(2000, 1, 2, 3, 4, 0, 0, "GMT+1")); // FIXME should be refused
+ verify( MAPPER, "2000-01-02T03:04+01:00", judate(2000, 1, 2, 3, 4, 0, 0, "GMT+1"));
failure(MAPPER, "2000-01-02T03:04:+01:00");
failure(MAPPER, "2000-01-02TZ");
failure(MAPPER, "2000-01-02T03Z");
failure(MAPPER, "2000-01-02T03:Z");
- failure(MAPPER, "2000-01-02T03:04Z");
+ verify(MAPPER, "2000-01-02T03:04Z", judate(2000, 1, 2, 3, 4, 0, 0, "UTC"));
failure(MAPPER, "2000-01-02T03:04:Z");
@@ -228,8 +221,8 @@
// Behavior should be the SAME whatever the timezone and/or the millis.
// seconds (no TZ)
- verify( MAPPER, "2000-01-02T03:04:5", judate(2000, 1, 2, 3, 4, 5, 0, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:04:5.000", judate(2000, 1, 2, 3, 4, 5, 0, LOCAL_TZ));
+ failure( MAPPER, "2000-01-02T03:04:5");
+ failure( MAPPER, "2000-01-02T03:04:5.000");
failure(MAPPER, "2000-01-02T03:04:005");
// seconds (+01:00)
@@ -239,13 +232,13 @@
// seconds (Z)
failure(MAPPER, "2000-01-02T03:04:5Z");
- verify( MAPPER, "2000-01-02T03:04:5.000Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
+ failure( MAPPER, "2000-01-02T03:04:5.000Z");
failure(MAPPER, "2000-01-02T03:04:005Z");
// minutes (no TZ)
- verify( MAPPER, "2000-01-02T03:4:05", judate(2000, 1, 2, 3, 4, 5, 0, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T03:4:05.000", judate(2000, 1, 2, 3, 4, 5, 0, LOCAL_TZ));
+ failure( MAPPER, "2000-01-02T03:4:05");
+ failure( MAPPER, "2000-01-02T03:4:05.000");
failure(MAPPER, "2000-01-02T03:004:05");
// minutes (+01:00)
@@ -254,14 +247,13 @@
failure(MAPPER, "2000-01-02T03:004:05+01:00");
// minutes (Z)
- verify( MAPPER, "2000-01-02T03:4:05Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
- verify( MAPPER, "2000-01-02T03:4:05.000Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
- verify( MAPPER, "2000-01-02T03:004:05Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
-
+ failure( MAPPER, "2000-01-02T03:4:05Z");
+ failure( MAPPER, "2000-01-02T03:4:05.000Z");
+ failure( MAPPER, "2000-01-02T03:004:05Z");
// hour (no TZ)
- verify( MAPPER, "2000-01-02T3:04:05", judate(2000, 1, 2, 3, 4, 5, 0, LOCAL_TZ));
- verify( MAPPER, "2000-01-02T3:04:05.000", judate(2000, 1, 2, 3, 4, 5, 0, LOCAL_TZ));
+ failure( MAPPER, "2000-01-02T3:04:05");
+ failure( MAPPER, "2000-01-02T3:04:05.000");
failure(MAPPER, "2000-01-02T003:04:05");
// hour (+01:00)
@@ -270,12 +262,11 @@
failure(MAPPER, "2000-01-02T003:04:05+01:00");
// hour (Z)
- verify( MAPPER, "2000-01-02T3:04:05Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
- verify( MAPPER, "2000-01-02T3:04:05.000Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
- verify( MAPPER, "2000-01-02T003:04:05Z", judate(2000, 1, 2, 3, 4, 5, 0, "UTC"));
+ failure( MAPPER, "2000-01-02T3:04:05Z");
+ failure( MAPPER, "2000-01-02T3:04:05.000Z");
+ failure( MAPPER, "2000-01-02T003:04:05Z");
}
-
/**
* Date-only representations (no Time part)
*
@@ -299,21 +290,20 @@
// ---------------------------------------------------------------------------------------------
// day
- verify( MAPPER, "2000-01-2", judate(2000, 1, 2, 0, 0, 0, 0, LOCAL_TZ));
+ failure( MAPPER, "2000-01-2");
failure( MAPPER, "2000-01-002");
// month
- verify( MAPPER, "2000-1-02", judate(2000, 1, 2, 0, 0, 0, 0, LOCAL_TZ));
+ failure( MAPPER, "2000-1-02");
failure( MAPPER, "2000-001-02");
// year
failure( MAPPER, "20000-01-02");
failure( MAPPER, "200-01-02" );
failure( MAPPER, "20-01-02" );
- verify( MAPPER, "2-01-02", judate(2, 1, 2, 0, 0, 0, 0, LOCAL_TZ)); // FIXME Why accept 1 digit and refuse they other cases??
+ failure( MAPPER, "2-01-02");
}
-
/**
* DateTime as numeric representation
*/
@@ -325,31 +315,29 @@
verify( MAPPER, Long.toString(now), new java.util.Date(now) ); // as a string
}
{
- /* As of 1.5.0, should be ok to pass as JSON String, as long
- * as it is plain timestamp (all numbers, 64-bit)
- */
+ // should be ok to pass as JSON String, as long
+ // as it is plain timestamp (all numbers, 64-bit)
long now = 1321992375446L;
- verify( MAPPER, now, new java.util.Date(now) ); // as a long
+ verify( MAPPER, now, new java.util.Date(now) ); // as a long
verify( MAPPER, Long.toString(now), new java.util.Date(now) ); // as a string
}
{
// #267: should handle negative timestamps too; like 12 hours before 1.1.1970
long now = - (24 * 3600 * 1000L);
- verify( MAPPER, now, new java.util.Date(now) ); // as a long
+ verify( MAPPER, now, new java.util.Date(now) ); // as a long
verify( MAPPER, Long.toString(now), new java.util.Date(now) ); // as a string
}
-
+
// value larger than a long (Long.MAX_VALUE+1)
BigInteger tooLarge = BigInteger.valueOf(Long.MAX_VALUE).add( BigInteger.valueOf(1) );
- failure(MAPPER, tooLarge, JsonParseException.class); // FIXME: InvalidFormatException is thrown everywhere else...
- failure(MAPPER, tooLarge.toString());
-
+ failure(MAPPER, tooLarge, InvalidFormatException.class);
+ failure(MAPPER, tooLarge.toString(), InvalidFormatException.class);
+
// decimal value
- failure(MAPPER, 0.0, JsonMappingException.class); // FIXME: InvalidFormatException is thrown everywhere else...
- failure(MAPPER, "0.0");
+ failure(MAPPER, 0.0, MismatchedInputException.class);
+ failure(MAPPER, "0.0", InvalidFormatException.class);
}
-
/**
* Note: may be these cases are already covered by {@link #testDateUtil_Annotation_PatternAndLocale()}
*/
@@ -525,31 +513,30 @@
cal.set(year, month-1, day, hour, minutes, seconds);
cal.set(Calendar.MILLISECOND, millis);
cal.setTimeZone(TimeZone.getTimeZone(tz));
-
return cal.getTime();
}
private static void verify(ObjectMapper mapper, Object input, Date expected) throws Exception {
- // Deserialize using the supplied ObjectMapper
- Date actual = read(mapper, input, java.util.Date.class);
-
- // Test against the expected
- if( expected==null && actual==null) {
- return;
- }
- if( expected==null && actual != null) {
- fail("Failed to deserialize "+input+", actual: '"+FORMAT.format(actual)+"', expected: <null>'");
- }
- if( expected != null && actual == null ) {
- fail("Failed to deserialize "+input+", actual: <null>, expected: '"+FORMAT.format(expected)+"'");
- }
- if( actual.getTime() != expected.getTime() ) {
- fail("Failed to deserialize "+input+", actual: '"+FORMAT.format(actual)+"', expected: '"+FORMAT.format(expected)+"'");
- }
+ // Deserialize using the supplied ObjectMapper
+ Date actual = read(mapper, input, java.util.Date.class);
+
+ // Test against the expected
+ if( expected==null && actual==null) {
+ return;
+ }
+ if( expected==null && actual != null) {
+ fail("Failed to deserialize "+input+", actual: '"+FORMAT.format(actual)+"', expected: <null>'");
+ }
+ if( expected != null && actual == null ) {
+ fail("Failed to deserialize "+input+", actual: <null>, expected: '"+FORMAT.format(expected)+"'");
+ }
+ if( actual.getTime() != expected.getTime() ) {
+ fail("Failed to deserialize "+input+", actual: '"+FORMAT.format(actual)+"', expected: '"+FORMAT.format(expected)+"'");
+ }
}
private static void failure(ObjectMapper mapper, Object input) throws Exception {
- failure(mapper, input, InvalidFormatException.class);
+ failure(mapper, input, MismatchedInputException.class);
}
private static void failure(ObjectMapper mapper, Object input, Class<? extends Exception> exceptionType) throws Exception {
@@ -559,7 +546,7 @@
}
catch(Exception e) {
// Is it the expected exception ?
- if( ! exceptionType.isAssignableFrom(e.getClass()) ) {
+ if (!exceptionType.isAssignableFrom(e.getClass()) ) {
fail("Wrong exception thrown when reading "+input+", actual: "+e.getClass().getName() + "("+e.getMessage()+"), expected: "+exceptionType.getName());
}
}
@@ -571,7 +558,6 @@
if( !(input instanceof Number) ) {
json = "\""+json+"\"";
}
-
// Deserialize using the supplied ObjectMapper
return (T) mapper.readValue(json, type);
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestDateDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java
similarity index 68%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestDateDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java
index 3224a61..5ec830e 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestDateDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/DateDeserializationTest.java
@@ -1,18 +1,20 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
+import java.beans.ConstructorProperties;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.OptBoolean;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
-import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
-public class TestDateDeserialization
+public class DateDeserializationTest
extends BaseMapTest
{
- // Test for [JACKSON-435]
static class DateAsStringBean
{
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="/yyyy/MM/dd/")
@@ -35,14 +37,60 @@
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH", timezone="CET")
public Date date;
}
+
+ static class CalendarBean {
+ Calendar _v;
+ void setV(Calendar v) { _v = v; }
+ }
+
+ static class LenientCalendarBean {
+ @JsonFormat(lenient=OptBoolean.TRUE)
+ public Calendar value;
+ }
+ static class StrictCalendarBean {
+ @JsonFormat(lenient=OptBoolean.FALSE)
+ public Calendar value;
+ }
+
+ // [databind#1722]
+ public static class Date1722 {
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
+ private Date date;
+
+ @JsonIgnore
+ private String foo;
+
+ @ConstructorProperties({"date", "foo"})
+ public Date1722(Date date, String foo) {
+ this.date = date;
+ this.foo = foo;
+ }
+
+ public Date getDate() {
+ return this.date;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public String getFoo() {
+ return this.foo;
+ }
+
+ public void setFoo(String foo) {
+ this.foo = foo;
+ }
+ }
+
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newObjectMapper();
public void testDateUtil() throws Exception
{
@@ -63,7 +111,7 @@
public void testDateUtilWithStringTimestamp() throws Exception
{
long now = 1321992375446L;
- /* As of 1.5.0, should be ok to pass as JSON String, as long
+ /* Should be ok to pass as JSON String, as long
* as it is plain timestamp (all numbers, 64-bit)
*/
String json = quote(String.valueOf(now));
@@ -208,10 +256,53 @@
assertEquals(450, c.get(Calendar.MILLISECOND));
}
+ // Also: minutes-part of offset need not be all zeroes: [databind#1788]
+ public void testISO8601FractionalTimezoneOffset() throws Exception
+ {
+ String inputStr = "1997-07-16T19:20:30.45+01:30";
+ java.util.Date inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class);
+ Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ c.setTime(inputDate);
+ assertEquals(1997, c.get(Calendar.YEAR));
+ assertEquals(Calendar.JULY, c.get(Calendar.MONTH));
+ assertEquals(16, c.get(Calendar.DAY_OF_MONTH));
+ assertEquals(19 - 2, c.get(Calendar.HOUR_OF_DAY));
+ assertEquals(50, c.get(Calendar.MINUTE));
+ assertEquals(30, c.get(Calendar.SECOND));
+ assertEquals(450, c.get(Calendar.MILLISECOND));
+ }
+
+ // [databind#1745]
+ public void testISO8601FractSecondsLong() throws Exception
+ {
+ String inputStr;
+ Date inputDate;
+ Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+
+ inputStr = "2014-10-03T18:00:00.3456-05:00";
+ inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class);
+ c.setTime(inputDate);
+ assertEquals(2014, c.get(Calendar.YEAR));
+ assertEquals(Calendar.OCTOBER, c.get(Calendar.MONTH));
+ assertEquals(3, c.get(Calendar.DAY_OF_MONTH));
+ // should truncate; not error or round
+ assertEquals(345, c.get(Calendar.MILLISECOND));
+
+ // But! Still limit to 9 digits (nanoseconds)
+ try {
+ MAPPER.readValue(quote("2014-10-03T18:00:00.1234567890-05:00"), java.util.Date.class);
+ } catch (InvalidFormatException e) {
+ verifyException(e, "invalid fractional seconds");
+ verifyException(e, "can use at most 9 digits");
+ }
+ }
+
public void testISO8601MissingSeconds() throws Exception
{
String inputStr;
Date inputDate;
+
+ // 23-Jun-2017, tatu: Shouldn't this be UTC?
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
inputStr = "1997-07-16T19:20+01:00";
@@ -285,7 +376,17 @@
assertEquals(2, c.get(Calendar.HOUR_OF_DAY));
}
- // [Issue#338]
+ // [databind#1722]: combination of `@ConstructorProperties` and `@JsonIgnore`
+ // should work fine.
+ public void testFormatAndCtors1722() throws Exception
+ {
+ Date1722 input = new Date1722(new Date(0L), "bogus");
+ String json = MAPPER.writeValueAsString(input);
+ Date1722 result = MAPPER.readValue(json, Date1722.class);
+ assertNotNull(result);
+ }
+
+ // [databind#338]
public void testDateUtilISO8601NoMilliseconds() throws Exception
{
final String INPUT_STR = "2013-10-31T17:27:00";
@@ -306,7 +407,7 @@
// 03-Nov-2013, tatu: This wouldn't work, and is the nominal reason
// for #338 I think
/*
- inputDate = ISO8601Utils.parse(INPUT_STR);
+ inputDate = ISO8601Utils.parse(INPUT_STR, new ParsePosition(0));
c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
c.setTime(inputDate);
assertEquals(2013, c.get(Calendar.YEAR));
@@ -385,7 +486,10 @@
result = MAPPER.readValue(quote(dateStr), Calendar.class);
// note: representation may differ (wrt timezone etc), but underlying value must remain the same:
- assertEquals(l, result.getTimeInMillis());
+ if (l != result.getTimeInMillis()) {
+ fail(String.format("Expected timestamp %d, got %d, for '%s'",
+ l, result.getTimeInMillis(), dateStr));
+ }
}
public void testCustom() throws Exception
@@ -412,7 +516,6 @@
assertNull(MAPPER.readValue(quote(""), java.sql.Date.class));
}
- // for [JACKSON-334]
public void test8601DateTimeNoMilliSecs() throws Exception
{
// ok, Zebra, no milliseconds
@@ -513,17 +616,146 @@
assertEquals(9, c.get(Calendar.HOUR_OF_DAY));
}
- // Based on an external report; was throwing an exception for second case here
- public void testISO8601Directly() throws Exception
+ // [databind#1651]
+ public void testDateEndingWithZNonDefTZ1651() throws Exception
{
- final String TIME_STR = "2015-01-21T08:56:13.533+0000";
- Date d = MAPPER.readValue(quote(TIME_STR), Date.class);
- assertNotNull(d);
+ String json = quote("1970-01-01T00:00:00.000Z");
- ISO8601DateFormat f = new ISO8601DateFormat();
- Date d2 = f.parse(TIME_STR);
- assertNotNull(d2);
- assertEquals(d.getTime(), d2.getTime());
+ // Standard mapper with timezone UTC: shared instance should be ok.
+ // ... but, Travis manages to have fails, so insist on newly created
+ ObjectMapper mapper = newObjectMapper();
+ Date dateUTC = mapper.readValue(json, Date.class); // 1970-01-01T00:00:00.000+00:00
+
+ // Mapper with timezone GMT-2
+ // note: must construct new one, not share
+ mapper = new ObjectMapper();
+ mapper.setTimeZone(TimeZone.getTimeZone("GMT-2"));
+ Date dateGMT1 = mapper.readValue(json, Date.class); // 1970-01-01T00:00:00.000-02:00
+
+ // Underlying timestamps should be the same
+ assertEquals(dateUTC.getTime(), dateGMT1.getTime());
+ }
+
+ /*
+ /**********************************************************
+ /* Context timezone use (or not)
+ /**********************************************************
+ */
+
+ // for [databind#204]
+ public void testContextTimezone() throws Exception
+ {
+ String inputStr = "1997-07-16T19:20:30.45+0100";
+ final String tzId = "PST";
+
+ // this is enabled by default:
+ assertTrue(MAPPER.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE));
+ final ObjectReader r = MAPPER
+ .readerFor(Calendar.class)
+ .with(TimeZone.getTimeZone(tzId));
+
+ // by default use contextual timezone:
+ Calendar cal = r.readValue(quote(inputStr));
+ TimeZone tz = cal.getTimeZone();
+ assertEquals(tzId, tz.getID());
+
+ assertEquals(1997, cal.get(Calendar.YEAR));
+ assertEquals(Calendar.JULY, cal.get(Calendar.MONTH));
+ assertEquals(16, cal.get(Calendar.DAY_OF_MONTH));
+
+ // Translated from original into PST differs:
+ assertEquals(20, cal.get(Calendar.MINUTE));
+ assertEquals(30, cal.get(Calendar.SECOND));
+ assertEquals(11, cal.get(Calendar.HOUR_OF_DAY));
+
+ // but if disabled, should use what's been sent in:
+ cal = r.without(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
+ .readValue(quote(inputStr));
+
+ // 23-Jun-2017, tatu: Actually turns out to be hard if not impossible to do ...
+ // problem being SimpleDateFormat does not really retain timezone offset.
+ // But if we match fields... we perhaps could use it?
+
+ // !!! TODO: would not yet pass
+/*
+ System.err.println("CAL/2 == "+cal);
+
+ System.err.println("tz == "+cal.getTimeZone());
+ */
+ }
+
+ /*
+ /**********************************************************
+ /* Test(s) for array unwrapping
+ /**********************************************************
+ */
+
+ public void testCalendarArrayUnwrap() throws Exception
+ {
+ ObjectReader reader = new ObjectMapper()
+ .readerFor(CalendarBean.class)
+ .without(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ final String inputDate = "1972-12-28T00:00:00.000+0000";
+ final String input = aposToQuotes("{'v':['"+inputDate+"']}");
+ try {
+ reader.readValue(input);
+ 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");
+ }
+
+ reader = reader.with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ CalendarBean bean = reader.readValue(input);
+ assertNotNull(bean._v);
+ assertEquals(1972, bean._v.get(Calendar.YEAR));
+
+ // and finally, a fail due to multiple values:
+ try {
+ reader.readValue(aposToQuotes("{'v':['"+inputDate+"','"+inputDate+"']}"));
+ fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled");
+ } catch (JsonMappingException exp) {
+ verifyException(exp, "Attempted to unwrap");
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Tests for leniency
+ /**********************************************************
+ */
+
+ public void testLenientCalendar() throws Exception
+ {
+ final String JSON = aposToQuotes("{'value':'2015-11-32'}");
+
+ // with lenient, can parse fine
+ LenientCalendarBean lenBean = MAPPER.readValue(JSON, LenientCalendarBean.class);
+ assertEquals(Calendar.DECEMBER, lenBean.value.get(Calendar.MONTH));
+ assertEquals(2, lenBean.value.get(Calendar.DAY_OF_MONTH));
+
+ // with strict, ought to produce exception
+ try {
+ MAPPER.readValue(JSON, StrictCalendarBean.class);
+ fail("Should not pass with invalid (with strict) date value");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot deserialize value of type `java.util.Calendar`");
+ verifyException(e, "from String \"2015-11-32\"");
+ verifyException(e, "expected format");
+ }
+
+ // similarly with Date...
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(java.util.Date.class)
+ .setFormat(JsonFormat.Value.forLeniency(Boolean.FALSE));
+ try {
+ mapper.readValue(quote("2015-11-32"), java.util.Date.class);
+ fail("Should not pass with invalid (with strict) date value");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot deserialize value of type `java.util.Date`");
+ verifyException(e, "from String \"2015-11-32\"");
+ verifyException(e, "expected format");
+ }
}
/*
@@ -538,7 +770,7 @@
MAPPER.readValue(quote("foobar"), Date.class);
fail("Should have failed with an exception");
} catch (InvalidFormatException e) {
- verifyException(e, "Can not deserialize value of type java.util.Date from String");
+ verifyException(e, "Cannot deserialize value of type `java.util.Date` from String");
assertEquals("foobar", e.getValue());
assertEquals(Date.class, e.getTargetType());
} catch (Exception e) {
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumAltIdTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumAltIdTest.java
new file mode 100644
index 0000000..76f1882
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumAltIdTest.java
@@ -0,0 +1,132 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+
+public class EnumAltIdTest extends BaseMapTest
+{
+ // [databind#1313]
+
+ enum TestEnum { JACKSON, RULES, OK; }
+ protected enum LowerCaseEnum {
+ A, B, C;
+ private LowerCaseEnum() { }
+ @Override
+ public String toString() { return name().toLowerCase(); }
+ }
+
+ protected static class EnumBean {
+ @JsonFormat(with={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
+ public TestEnum value;
+ }
+
+ protected static class StrictCaseBean {
+ @JsonFormat(without={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
+ public TestEnum value;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, basic
+ /**********************************************************
+ */
+
+ protected final ObjectMapper MAPPER = new ObjectMapper();
+ protected final ObjectMapper MAPPER_IGNORE_CASE;
+ {
+ MAPPER_IGNORE_CASE = new ObjectMapper();
+ MAPPER_IGNORE_CASE.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
+ }
+
+ protected final ObjectReader READER_DEFAULT = MAPPER.reader();
+ protected final ObjectReader READER_IGNORE_CASE = MAPPER_IGNORE_CASE.reader();
+
+ // Tests for [databind#1313], case-insensitive
+
+ public void testFailWhenCaseSensitiveAndNameIsNotUpperCase() throws IOException {
+ try {
+ READER_DEFAULT.forType(TestEnum.class).readValue("\"Jackson\"");
+ fail("InvalidFormatException expected");
+ } catch (InvalidFormatException e) {
+ verifyException(e, "value not one of declared Enum instance names: [JACKSON, OK, RULES]");
+ }
+ }
+
+ public void testFailWhenCaseSensitiveAndToStringIsUpperCase() throws IOException {
+ ObjectReader r = READER_DEFAULT.forType(LowerCaseEnum.class)
+ .with(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+ try {
+ r.readValue("\"A\"");
+ fail("InvalidFormatException expected");
+ } catch (InvalidFormatException e) {
+ verifyException(e, "value not one of declared Enum instance names: [a, b, c]");
+ }
+ }
+
+ public void testEnumDesIgnoringCaseWithLowerCaseContent() throws IOException {
+ assertEquals(TestEnum.JACKSON,
+ READER_IGNORE_CASE.forType(TestEnum.class).readValue(quote("jackson")));
+ }
+
+ public void testEnumDesIgnoringCaseWithUpperCaseToString() throws IOException {
+ ObjectReader r = MAPPER_IGNORE_CASE.readerFor(LowerCaseEnum.class)
+ .with(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+ assertEquals(LowerCaseEnum.A, r.readValue("\"A\""));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, containers
+ /**********************************************************
+ */
+
+ public void testIgnoreCaseInEnumList() throws Exception {
+ TestEnum[] enums = READER_IGNORE_CASE.forType(TestEnum[].class)
+ .readValue("[\"jacksON\", \"ruLes\"]");
+
+ assertEquals(2, enums.length);
+ assertEquals(TestEnum.JACKSON, enums[0]);
+ assertEquals(TestEnum.RULES, enums[1]);
+ }
+
+ public void testIgnoreCaseInEnumSet() throws IOException {
+ ObjectReader r = READER_IGNORE_CASE.forType(new TypeReference<EnumSet<TestEnum>>() { });
+ EnumSet<TestEnum> set = r.readValue("[\"jackson\"]");
+ assertEquals(1, set.size());
+ assertTrue(set.contains(TestEnum.JACKSON));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, property overrides
+ /**********************************************************
+ */
+
+ public void testIgnoreCaseViaFormat() throws Exception
+ {
+ final String JSON = aposToQuotes("{'value':'ok'}");
+
+ // should be able to allow on per-case basis:
+ EnumBean pojo = READER_DEFAULT.forType(EnumBean.class)
+ .readValue(JSON);
+ assertEquals(TestEnum.OK, pojo.value);
+
+ // including disabling acceptance
+ try {
+ READER_DEFAULT.forType(StrictCaseBean.class)
+ .readValue(JSON);
+ fail("Should not pass");
+ } catch (InvalidFormatException e) {
+ verifyException(e, "value not one of declared Enum instance names: [JACKSON, OK, RULES]");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/EnumDefaultReadTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDefaultReadTest.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/deser/EnumDefaultReadTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDefaultReadTest.java
index f1ef94f..58392e8 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/EnumDefaultReadTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDefaultReadTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.IOException;
@@ -13,7 +13,7 @@
ZERO,
ONE;
}
-
+
enum SimpleEnumWithDefault {
@JsonEnumDefaultValue
ZERO,
@@ -165,8 +165,8 @@
throws Exception
{
ObjectReader r = MAPPER.reader()
- .with(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
- .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
+ .with(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
+ .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
_verifyOkDeserialization(r, "ZERO", SimpleEnum.class, SimpleEnum.ZERO);
_verifyOkDeserialization(r, "ONE", SimpleEnum.class, SimpleEnum.ONE);
@@ -222,7 +222,7 @@
reader.forType(toValueType).readValue(quote(fromValue));
fail("Deserialization should have failed");
} catch (InvalidFormatException e) {
- verifyException(e, "Can not deserialize value of type");
+ verifyException(e, "Cannot deserialize value of type");
/* Expected. */
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/EnumDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDeserializationTest.java
similarity index 86%
rename from src/test/java/com/fasterxml/jackson/databind/deser/EnumDeserializationTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDeserializationTest.java
index a1b6782..ff0a29c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/EnumDeserializationTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDeserializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.IOException;
import java.util.*;
@@ -11,6 +11,8 @@
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.module.SimpleModule;
@SuppressWarnings("serial")
@@ -46,7 +48,7 @@
return TestEnum.valueOf(jp.getText().toUpperCase());
}
}
-
+
protected enum LowerCaseEnum {
A, B, C;
private LowerCaseEnum() { }
@@ -116,6 +118,17 @@
}
}
+ static enum StrictEnumCreator {
+ A, B;
+
+ @JsonCreator public static StrictEnumCreator fromId(String value) {
+ for (StrictEnumCreator e: values()) {
+ if (e.name().toLowerCase().equals(value)) return e;
+ }
+ throw new IllegalArgumentException(value);
+ }
+ }
+
// //
public enum AnEnum {
@@ -170,17 +183,6 @@
}
}
- // [databind#1626]
- enum NumberEnum {
- @JsonProperty("2")
- EN2,
- @JsonProperty("0")
- EN0,
- @JsonProperty("1")
- EN1
- ;
- }
-
/*
/**********************************************************
/* Test methods
@@ -199,9 +201,7 @@
assertEquals(TestEnum.OK, MAPPER.readValue(jp, TestEnum.class));
assertEquals(TestEnum.RULES, MAPPER.readValue(jp, TestEnum.class));
- /* should be ok; nulls are typeless; handled by mapper, not by
- * deserializer
- */
+ // should be ok; nulls are typeless; handled by mapper, not by deserializer
assertNull(MAPPER.readValue(jp, TestEnum.class));
// and no more content beyond that...
@@ -214,7 +214,7 @@
try {
/*Object result =*/ MAPPER.readValue("\"NO-SUCH-VALUE\"", TestEnum.class);
fail("Expected an exception for bogus enum value...");
- } catch (JsonMappingException jex) {
+ } catch (MismatchedInputException jex) {
verifyException(jex, "value not one of declared");
}
jp.close();
@@ -244,13 +244,6 @@
assertEquals(AnnotatedTestEnum.OK, e);
}
- public void testEnumMaps() throws Exception
- {
- EnumMap<TestEnum,String> value = MAPPER.readValue("{\"OK\":\"value\"}",
- new TypeReference<EnumMap<TestEnum,String>>() { });
- assertEquals("value", value.get(TestEnum.OK));
- }
-
public void testSubclassedEnums() throws Exception
{
EnumWithSubClass value = MAPPER.readValue("\"A\"", EnumWithSubClass.class);
@@ -266,16 +259,6 @@
assertEquals(LowerCaseEnum.C, value);
}
- public void testToStringEnumMaps() throws Exception
- {
- // can't reuse global one due to reconfig
- ObjectMapper m = new ObjectMapper();
- m.configure(DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);
- EnumMap<LowerCaseEnum,String> value = m.readValue("{\"a\":\"value\"}",
- new TypeReference<EnumMap<LowerCaseEnum,String>>() { });
- assertEquals("value", value.get(LowerCaseEnum.A));
- }
-
public void testNumbersToEnums() throws Exception
{
// by default numbers are fine:
@@ -289,8 +272,8 @@
try {
value = r.readValue("1");
fail("Expected an error");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not deserialize");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot deserialize");
verifyException(e, "not allowed to deserialize Enum value out of number: disable");
}
@@ -298,8 +281,8 @@
try {
value = r.readValue(quote("1"));
fail("Expected an error");
- } catch (JsonMappingException e) {
- verifyException(e, "Can not deserialize");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot deserialize");
// 26-Jan-2017, tatu: as per [databind#1505], should fail bit differently
verifyException(e, "value not one of declared Enum");
}
@@ -343,12 +326,23 @@
public void testAllowUnknownEnumValuesReadAsNull() throws Exception
{
- // can not use shared mapper when changing configs...
+ // cannot use shared mapper when changing configs...
ObjectReader reader = MAPPER.reader(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
assertNull(reader.forType(TestEnum.class).readValue("\"NO-SUCH-VALUE\""));
assertNull(reader.forType(TestEnum.class).readValue(" 4343 "));
}
+ // Ability to ignore unknown Enum values:
+
+ // [databind#1642]
+ public void testAllowUnknownEnumValuesReadAsNullWithCreatorMethod() throws Exception
+ {
+ // cannot use shared mapper when changing configs...
+ ObjectReader reader = MAPPER.reader(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
+ assertNull(reader.forType(StrictEnumCreator.class).readValue("\"NO-SUCH-VALUE\""));
+ assertNull(reader.forType(StrictEnumCreator.class).readValue(" 4343 "));
+ }
+
public void testAllowUnknownEnumValuesForEnumSets() throws Exception
{
ObjectReader reader = MAPPER.reader(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
@@ -371,8 +365,8 @@
try {
MAPPER.readValue("{\"map\":{\"NO-SUCH-VALUE\":\"val\"}}", ClassWithEnumMapKey.class);
fail("Expected an exception for bogus enum value...");
- } catch (JsonMappingException jex) {
- verifyException(jex, "Can not deserialize Map key of type com.fasterxml.jackson.databind.deser");
+ } catch (InvalidFormatException jex) {
+ verifyException(jex, "Cannot deserialize Map key of type `com.fasterxml.jackson.databind.deser.jdk.EnumDeserializationTest$TestEnum`");
}
}
@@ -397,22 +391,22 @@
// [databind#381]
public void testUnwrappedEnum() throws Exception {
- final ObjectMapper mapper = new ObjectMapper();
+ final ObjectMapper mapper = newObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
assertEquals(TestEnum.JACKSON, mapper.readValue("[" + quote("JACKSON") + "]", TestEnum.class));
}
public void testUnwrappedEnumException() throws Exception {
- final ObjectMapper mapper = new ObjectMapper();
+ final ObjectMapper mapper = newObjectMapper();
mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
try {
Object v = mapper.readValue("[" + quote("JACKSON") + "]",
TestEnum.class);
fail("Exception was not thrown on deserializing a single array element of type enum; instead got: "+v);
- } catch (JsonMappingException exp) {
+ } catch (MismatchedInputException exp) {
//exception as thrown correctly
- verifyException(exp, "Can not deserialize");
+ verifyException(exp, "Cannot deserialize");
}
}
@@ -426,6 +420,18 @@
// but also with quoted Strings
en = MAPPER.readValue(quote("1"), TestEnum.class);
assertSame(TestEnum.values()[1], en);
+
+ // [databind#1690]: unless prevented
+ final ObjectMapper mapper = newObjectMapper();
+ mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
+ try {
+ en = mapper.readValue(quote("1"), TestEnum.class);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot deserialize value of type");
+ verifyException(e, "EnumDeserializationTest$TestEnum");
+ verifyException(e, "value looks like quoted Enum index");
+ }
}
public void testEnumWithJsonPropertyRename() throws Exception
@@ -510,23 +516,15 @@
assertNull("When using a constructor, the default value annotation shouldn't be used.", myEnum);
}
- public void testExceptionFromCustomEnumKeyDeserializer() {
- ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.registerModule(new EnumModule());
+ public void testExceptionFromCustomEnumKeyDeserializer() throws Exception {
+ ObjectMapper mapper = newObjectMapper()
+ .registerModule(new EnumModule());
try {
- objectMapper.readValue("{\"TWO\": \"dumpling\"}",
+ mapper.readValue("{\"TWO\": \"dumpling\"}",
new TypeReference<Map<AnEnum, String>>() {});
fail("No exception");
- } catch (IOException e) {
+ } catch (MismatchedInputException e) {
assertTrue(e.getMessage().contains("Undefined AnEnum"));
}
}
-
- // [databind#1626]
- public void testNumericEnumName() throws Exception
- {
- String json = MAPPER.writeValueAsString(NumberEnum.EN2);
- assertEquals(quote("2"), json);
- assertEquals(NumberEnum.EN2, MAPPER.readValue(json, NumberEnum.class));
- }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumMapDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumMapDeserializationTest.java
new file mode 100644
index 0000000..bf30d14
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumMapDeserializationTest.java
@@ -0,0 +1,223 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+
+@SuppressWarnings("serial")
+public class EnumMapDeserializationTest extends BaseMapTest
+{
+ enum TestEnum { JACKSON, RULES, OK; }
+
+ enum TestEnumWithDefault {
+ JACKSON, RULES,
+ @JsonEnumDefaultValue
+ OK;
+ }
+
+ protected enum LowerCaseEnum {
+ A, B, C;
+ private LowerCaseEnum() { }
+ @Override
+ public String toString() { return name().toLowerCase(); }
+ }
+
+ static class MySimpleEnumMap extends EnumMap<TestEnum,String> {
+ public MySimpleEnumMap() {
+ super(TestEnum.class);
+ }
+ }
+
+ static class FromStringEnumMap extends EnumMap<TestEnum,String> {
+ @JsonCreator
+ public FromStringEnumMap(String value) {
+ super(TestEnum.class);
+ put(TestEnum.JACKSON, value);
+ }
+ }
+
+ static class FromDelegateEnumMap extends EnumMap<TestEnum,String> {
+ @JsonCreator
+ public FromDelegateEnumMap(Map<Object,Object> stuff) {
+ super(TestEnum.class);
+ put(TestEnum.OK, String.valueOf(stuff));
+ }
+ }
+
+ static class FromPropertiesEnumMap extends EnumMap<TestEnum,String> {
+ int a0, b0;
+
+ @JsonCreator
+ public FromPropertiesEnumMap(@JsonProperty("a") int a,
+ @JsonProperty("b") int b) {
+ super(TestEnum.class);
+ a0 = a;
+ b0 = b;
+ }
+ }
+
+ // [databind#1859]
+ public enum Enum1859 {
+ A, B, C;
+ }
+
+ static class Pojo1859
+ {
+ public EnumMap<Enum1859, String> values;
+
+ public Pojo1859() { }
+ public Pojo1859(EnumMap<Enum1859, String> v) {
+ values = v;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, basic
+ /**********************************************************
+ */
+
+ protected final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testEnumMaps() throws Exception
+ {
+ EnumMap<TestEnum,String> value = MAPPER.readValue("{\"OK\":\"value\"}",
+ new TypeReference<EnumMap<TestEnum,String>>() { });
+ assertEquals("value", value.get(TestEnum.OK));
+ }
+
+ public void testToStringEnumMaps() throws Exception
+ {
+ // can't reuse global one due to reconfig
+ ObjectReader r = MAPPER.reader()
+ .with(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+ EnumMap<LowerCaseEnum,String> value = r.forType(
+ new TypeReference<EnumMap<LowerCaseEnum,String>>() { })
+ .readValue("{\"a\":\"value\"}");
+ assertEquals("value", value.get(LowerCaseEnum.A));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods: custom enum maps
+ /**********************************************************
+ */
+
+ public void testCustomEnumMapWithDefaultCtor() throws Exception
+ {
+ MySimpleEnumMap map = MAPPER.readValue(aposToQuotes("{'RULES':'waves'}"),
+ MySimpleEnumMap.class);
+ assertEquals(1, map.size());
+ assertEquals("waves", map.get(TestEnum.RULES));
+ }
+
+ public void testCustomEnumMapFromString() throws Exception
+ {
+ FromStringEnumMap map = MAPPER.readValue(quote("kewl"), FromStringEnumMap.class);
+ assertEquals(1, map.size());
+ assertEquals("kewl", map.get(TestEnum.JACKSON));
+ }
+
+ public void testCustomEnumMapWithDelegate() throws Exception
+ {
+ FromDelegateEnumMap map = MAPPER.readValue(aposToQuotes("{'foo':'bar'}"), FromDelegateEnumMap.class);
+ assertEquals(1, map.size());
+ assertEquals("{foo=bar}", map.get(TestEnum.OK));
+ }
+
+ public void testCustomEnumMapFromProps() throws Exception
+ {
+ FromPropertiesEnumMap map = MAPPER.readValue(aposToQuotes(
+ "{'a':13,'RULES':'jackson','b':-731,'OK':'yes'}"),
+ FromPropertiesEnumMap.class);
+
+ assertEquals(13, map.a0);
+ assertEquals(-731, map.b0);
+
+ assertEquals("jackson", map.get(TestEnum.RULES));
+ assertEquals("yes", map.get(TestEnum.OK));
+ assertEquals(2, map.size());
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods: polymorphic
+ /**********************************************************
+ */
+
+ // [databind#1859]
+ public void testEnumMapAsPolymorphic() throws Exception
+ {
+ EnumMap<Enum1859, String> enumMap = new EnumMap<>(Enum1859.class);
+ enumMap.put(Enum1859.A, "Test");
+ enumMap.put(Enum1859.B, "stuff");
+ Pojo1859 input = new Pojo1859(enumMap);
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.NON_FINAL, "@type");
+
+ // 05-Mar-2018, tatu: Original issue had this; should not make difference:
+ /*
+ TypeResolverBuilder<?> mapTyperAsPropertyType = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
+ mapTyperAsPropertyType.init(JsonTypeInfo.Id.CLASS, null);
+ mapTyperAsPropertyType.inclusion(JsonTypeInfo.As.PROPERTY);
+ mapper.setDefaultTyping(mapTyperAsPropertyType);
+ */
+
+ String json = mapper.writeValueAsString(input);
+ Pojo1859 result = mapper.readValue(json, Pojo1859.class);
+ assertNotNull(result);
+ assertNotNull(result.values);
+ assertEquals(2, result.values.size());
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods: handling of invalid values
+ /**********************************************************
+ */
+
+ // [databind#1859]
+ public void testUnknownKeyAsDefault() throws Exception
+ {
+ // first, via EnumMap
+ EnumMap<TestEnumWithDefault,String> value = MAPPER
+ .readerFor(new TypeReference<EnumMap<TestEnumWithDefault,String>>() { })
+ .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
+ .readValue("{\"unknown\":\"value\"}");
+ assertEquals(1, value.size());
+ assertEquals("value", value.get(TestEnumWithDefault.OK));
+
+ Map<TestEnumWithDefault,String> value2 = MAPPER
+ .readerFor(new TypeReference<Map<TestEnumWithDefault,String>>() { })
+ .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
+ .readValue("{\"unknown\":\"value\"}");
+ assertEquals(1, value2.size());
+ assertEquals("value", value2.get(TestEnumWithDefault.OK));
+ }
+
+ // [databind#1859]
+ public void testUnknownKeyAsNull() throws Exception
+ {
+ // first, via EnumMap
+ EnumMap<TestEnumWithDefault,String> value = MAPPER
+ .readerFor(new TypeReference<EnumMap<TestEnumWithDefault,String>>() { })
+ .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
+ .readValue("{\"unknown\":\"value\"}");
+ assertEquals(0, value.size());
+
+ // then regular Map
+ Map<TestEnumWithDefault,String> value2 = MAPPER
+ .readerFor(new TypeReference<Map<TestEnumWithDefault,String>>() { })
+ .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
+ .readValue("{\"unknown\":\"value\"}");
+ // 04-Jan-2017, tatu: Not sure if this is weird or not, but since `null`s are typically
+ // ok for "regular" JDK Maps...
+ assertEquals(1, value2.size());
+ assertEquals("value", value2.get(null));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/JDKAtomicTypesTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKAtomicTypesDeserTest.java
similarity index 86%
rename from src/test/java/com/fasterxml/jackson/databind/deser/JDKAtomicTypesTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKAtomicTypesDeserTest.java
index a3236cf..40b5a45 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/JDKAtomicTypesTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKAtomicTypesDeserTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -6,10 +6,11 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-public class JDKAtomicTypesTest
+public class JDKAtomicTypesDeserTest
extends com.fasterxml.jackson.databind.BaseMapTest
{
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@@ -40,7 +41,6 @@
static class SimpleWrapper {
public AtomicReference<Object> value;
- public SimpleWrapper() { }
public SimpleWrapper(Object o) { value = new AtomicReference<Object>(o); }
}
@@ -94,6 +94,11 @@
public AtomicReference<Object> b = new AtomicReference<Object>();
}
+ // [databind#2303]
+ static class MyBean2303 {
+ public AtomicReference<AtomicReference<Integer>> refRef;
+ }
+
/*
/**********************************************************
/* Test methods
@@ -150,7 +155,7 @@
JsonInclude.Value incl =
JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.ALWAYS);
ObjectMapper mapper = new ObjectMapper();
- mapper.setPropertyInclusion(incl);
+ mapper.setDefaultPropertyInclusion(incl);
assertEquals(aposToQuotes("{'value':true}"),
mapper.writeValueAsString(new SimpleWrapper(Boolean.TRUE)));
}
@@ -160,7 +165,7 @@
JsonInclude.Value incl =
JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.NON_NULL);
ObjectMapper mapper = new ObjectMapper();
- mapper.setPropertyInclusion(incl);
+ mapper.setDefaultPropertyInclusion(incl);
assertEquals(aposToQuotes("{'value':true}"),
mapper.writeValueAsString(new SimpleWrapper(Boolean.TRUE)));
}
@@ -170,7 +175,7 @@
JsonInclude.Value incl =
JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.NON_ABSENT);
ObjectMapper mapper = new ObjectMapper();
- mapper.setPropertyInclusion(incl);
+ mapper.setDefaultPropertyInclusion(incl);
assertEquals(aposToQuotes("{'value':true}"),
mapper.writeValueAsString(new SimpleWrapper(Boolean.TRUE)));
}
@@ -180,7 +185,7 @@
JsonInclude.Value incl =
JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.NON_EMPTY);
ObjectMapper mapper = new ObjectMapper();
- mapper.setPropertyInclusion(incl);
+ mapper.setDefaultPropertyInclusion(incl);
assertEquals(aposToQuotes("{'value':true}"),
mapper.writeValueAsString(new SimpleWrapper(Boolean.TRUE)));
}
@@ -205,12 +210,12 @@
ObjectMapper mapper = MAPPER;
// by default, include as null
- assertEquals("{\"value\":null}", mapper.writeValueAsString(input));
+ assertEquals(aposToQuotes("{'value':null}"), mapper.writeValueAsString(input));
// ditto with "no nulls"
mapper = new ObjectMapper().setSerializationInclusion(JsonInclude
.Include.NON_NULL);
- assertEquals("{\"value\":null}", mapper.writeValueAsString(input));
+ assertEquals(aposToQuotes("{'value':null}"), mapper.writeValueAsString(input));
// but not with "non empty"
mapper = new ObjectMapper().setSerializationInclusion(JsonInclude
@@ -272,11 +277,25 @@
@SuppressWarnings("unchecked")
public void testNullValueHandling() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
AtomicReference<Double> inputData = new AtomicReference<Double>();
- String json = mapper.writeValueAsString(inputData);
- AtomicReference<Double> readData = (AtomicReference<Double>) mapper.readValue(json, AtomicReference.class);
+ String json = MAPPER.writeValueAsString(inputData);
+ AtomicReference<Double> readData = (AtomicReference<Double>) MAPPER.readValue(json, AtomicReference.class);
assertNotNull(readData);
assertNull(readData.get());
}
+
+ // [databind#2303]
+ public void testNullWithinNested() throws Exception
+ {
+ final ObjectReader r = MAPPER.readerFor(MyBean2303.class);
+ MyBean2303 intRef = r.readValue(" {\"refRef\": 2 } ");
+ assertNotNull(intRef.refRef);
+ assertNotNull(intRef.refRef.get());
+ assertEquals(intRef.refRef.get().get(), new Integer(2));
+
+ MyBean2303 nullRef = r.readValue(" {\"refRef\": null } ");
+ assertNotNull(nullRef.refRef);
+ assertNotNull(nullRef.refRef.get());
+ assertNull(nullRef.refRef.get().get());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKCollectionsDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKCollectionsDeserTest.java
new file mode 100644
index 0000000..875c61c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKCollectionsDeserTest.java
@@ -0,0 +1,71 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * Tests for special collection/map types via `java.util.Collections`
+ */
+public class JDKCollectionsDeserTest extends BaseMapTest
+{
+ static class XBean {
+ public int x;
+
+ public XBean() { }
+ public XBean(int x) { this.x = x; }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ private final static ObjectMapper MAPPER = new ObjectMapper();
+
+ // And then a round-trip test for singleton collections
+ public void testSingletonCollections() throws Exception
+ {
+ final TypeReference<?> xbeanListType = new TypeReference<List<XBean>>() { };
+
+ String json = MAPPER.writeValueAsString(Collections.singleton(new XBean(3)));
+ Collection<XBean> result = MAPPER.readValue(json, xbeanListType);
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals(3, result.iterator().next().x);
+
+ json = MAPPER.writeValueAsString(Collections.singletonList(new XBean(28)));
+ result = MAPPER.readValue(json, xbeanListType);
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals(28, result.iterator().next().x);
+ }
+
+ // [databind#1868]: Verify class name serialized as is
+ public void testUnmodifiableSet() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
+
+ Set<String> theSet = Collections.unmodifiableSet(Collections.singleton("a"));
+ String json = mapper.writeValueAsString(theSet);
+
+ assertEquals("[\"java.util.Collections$UnmodifiableSet\",[\"a\"]]", json);
+
+ // 04-Jan-2018, tatu: Alas, no way to make this actually work well, at this point.
+ // In theory could jiggle things back on deser, using one of two ways:
+ //
+ // 1) Do mapping to regular Set/List types (abstract type mapping): would work, but get rid of immutability
+ // 2) Have actually separate deserializer OR ValueInstantiator
+ /*
+ Set<String> result = mapper.readValue(json, Set.class);
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ */
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/JDKNumberDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java
similarity index 77%
rename from src/test/java/com/fasterxml/jackson/databind/deser/JDKNumberDeserTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java
index c224f88..2fc0b1c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/JDKNumberDeserTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.IOException;
import java.io.StringReader;
@@ -10,6 +10,7 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
public class JDKNumberDeserTest extends BaseMapTest
{
@@ -81,16 +82,65 @@
assertEquals(Double.valueOf(Double.NEGATIVE_INFINITY), result);
}
+ // 01-Mar-2017, tatu: This is bit tricky... in some ways, mapping to "empty value"
+ // would be best; but due to legacy reasons becomes `null` at this point
public void testEmptyAsNumber() throws Exception
{
+ assertNull(MAPPER.readValue(quote(""), Byte.class));
+ assertNull(MAPPER.readValue(quote(""), Short.class));
+ assertNull(MAPPER.readValue(quote(""), Character.class));
assertNull(MAPPER.readValue(quote(""), Integer.class));
assertNull(MAPPER.readValue(quote(""), Long.class));
assertNull(MAPPER.readValue(quote(""), Float.class));
assertNull(MAPPER.readValue(quote(""), Double.class));
+
assertNull(MAPPER.readValue(quote(""), BigInteger.class));
assertNull(MAPPER.readValue(quote(""), BigDecimal.class));
}
+ public void testTextualNullAsNumber() throws Exception
+ {
+ final String NULL_JSON = quote("null");
+ assertNull(MAPPER.readValue(NULL_JSON, Byte.class));
+ assertNull(MAPPER.readValue(NULL_JSON, Short.class));
+ // Character is bit special, can't do:
+// assertNull(MAPPER.readValue(JSON, Character.class));
+ assertNull(MAPPER.readValue(NULL_JSON, Integer.class));
+ assertNull(MAPPER.readValue(NULL_JSON, Long.class));
+ assertNull(MAPPER.readValue(NULL_JSON, Float.class));
+ assertNull(MAPPER.readValue(NULL_JSON, Double.class));
+
+ assertEquals(Byte.valueOf((byte) 0), MAPPER.readValue(NULL_JSON, Byte.TYPE));
+ assertEquals(Short.valueOf((short) 0), MAPPER.readValue(NULL_JSON, Short.TYPE));
+ // Character is bit special, can't do:
+// assertEquals(Character.valueOf((char) 0), MAPPER.readValue(JSON, Character.TYPE));
+ assertEquals(Integer.valueOf(0), MAPPER.readValue(NULL_JSON, Integer.TYPE));
+ assertEquals(Long.valueOf(0L), MAPPER.readValue(NULL_JSON, Long.TYPE));
+ assertEquals(Float.valueOf(0f), MAPPER.readValue(NULL_JSON, Float.TYPE));
+ assertEquals(Double.valueOf(0d), MAPPER.readValue(NULL_JSON, Double.TYPE));
+
+ assertNull(MAPPER.readValue(NULL_JSON, BigInteger.class));
+ assertNull(MAPPER.readValue(NULL_JSON, BigDecimal.class));
+
+ // Also: verify failure for at least some
+ try {
+ MAPPER.readerFor(Integer.TYPE).with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
+ .readValue(NULL_JSON);
+ fail("Should not have passed");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce String \"null\"");
+ }
+
+ ObjectMapper noCoerceMapper = new ObjectMapper();
+ noCoerceMapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
+ try {
+ noCoerceMapper.readValue(NULL_JSON, Integer.TYPE);
+ fail("Should not have passed");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce String \"null\"");
+ }
+ }
+
public void testDeserializeDecimalHappyPath() throws Exception {
String json = "{\"defaultValue\": { \"value\": 123 } }";
MyBeanHolder result = MAPPER.readValue(json, MyBeanHolder.class);
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
new file mode 100644
index 0000000..a64e5b8
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKScalarsTest.java
@@ -0,0 +1,900 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import java.io.*;
+import java.lang.reflect.Array;
+import java.util.List;
+
+import org.junit.Assert;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.JsonMappingException.Reference;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+/**
+ * Unit tests for verifying handling of simple basic non-structured
+ * types; primitives (and/or their wrappers), Strings.
+ */
+public class JDKScalarsTest
+ extends BaseMapTest
+{
+ final static String NAN_STRING = "NaN";
+
+ final static class BooleanBean {
+ boolean _v;
+ void setV(boolean v) { _v = v; }
+ }
+
+ static class BooleanWrapper {
+ public Boolean wrapper;
+ public boolean primitive;
+
+ protected Boolean ctor;
+
+ @JsonCreator
+ public BooleanWrapper(@JsonProperty("ctor") Boolean foo) {
+ ctor = foo;
+ }
+ }
+
+ static class IntBean {
+ int _v;
+ void setV(int v) { _v = v; }
+ }
+
+ static class LongBean {
+ long _v;
+ void setV(long v) { _v = v; }
+ }
+
+ final static class DoubleBean {
+ double _v;
+ void setV(double v) { _v = v; }
+ }
+
+ final static class FloatBean {
+ float _v;
+ void setV(float v) { _v = v; }
+ }
+
+ final static class CharacterBean {
+ char _v;
+ void setV(char v) { _v = v; }
+ char getV() { return _v; }
+ }
+
+ final static class CharacterWrapperBean {
+ Character _v;
+ void setV(Character v) { _v = v; }
+ Character getV() { return _v; }
+ }
+
+ /**
+ * Also, let's ensure that it's ok to override methods.
+ */
+ static class IntBean2
+ extends IntBean
+ {
+ @Override
+ void setV(int v2) { super.setV(v2+1); }
+ }
+
+ static class PrimitivesBean
+ {
+ public boolean booleanValue = true;
+ public byte byteValue = 3;
+ public char charValue = 'a';
+ public short shortValue = 37;
+ public int intValue = 1;
+ public long longValue = 100L;
+ public float floatValue = 0.25f;
+ public double doubleValue = -1.0;
+ }
+
+ static class WrappersBean
+ {
+ public Boolean booleanValue;
+ public Byte byteValue;
+ public Character charValue;
+ public Short shortValue;
+ public Integer intValue;
+ public Long longValue;
+ public Float floatValue;
+ public Double doubleValue;
+ }
+
+ // [databind#2101]
+ static class PrimitiveCreatorBean
+ {
+ @JsonCreator
+ public PrimitiveCreatorBean(@JsonProperty(value="a",required=true) int a,
+ @JsonProperty(value="b",required=true) int b) { }
+ }
+
+ // [databind#2197]
+ static class VoidBean {
+ public Void value;
+ }
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ /*
+ /**********************************************************
+ /* Scalar tests for boolean
+ /**********************************************************
+ */
+
+ public void testBooleanPrimitive() throws Exception
+ {
+ // first, simple case:
+ BooleanBean result = MAPPER.readValue("{\"v\":true}", BooleanBean.class);
+ assertTrue(result._v);
+ result = MAPPER.readValue("{\"v\":null}", BooleanBean.class);
+ assertNotNull(result);
+ assertFalse(result._v);
+ result = MAPPER.readValue("{\"v\":1}", BooleanBean.class);
+ assertNotNull(result);
+ assertTrue(result._v);
+
+ // should work with arrays too..
+ boolean[] array = MAPPER.readValue("[ null, false ]", boolean[].class);
+ assertNotNull(array);
+ assertEquals(2, array.length);
+ assertFalse(array[0]);
+ assertFalse(array[1]);
+ }
+
+ /**
+ * Simple unit test to verify that we can map boolean values to
+ * java.lang.Boolean.
+ */
+ public void testBooleanWrapper() throws Exception
+ {
+ Boolean result = MAPPER.readValue("true", Boolean.class);
+ assertEquals(Boolean.TRUE, result);
+ result = MAPPER.readValue("false", Boolean.class);
+ assertEquals(Boolean.FALSE, result);
+
+ // should accept ints too, (0 == false, otherwise true)
+ result = MAPPER.readValue("0", Boolean.class);
+ assertEquals(Boolean.FALSE, result);
+ result = MAPPER.readValue("1", Boolean.class);
+ assertEquals(Boolean.TRUE, result);
+ }
+
+ // Test for verifying that Long values are coerced to boolean correctly as well
+ public void testLongToBoolean() throws Exception
+ {
+ long value = 1L + Integer.MAX_VALUE;
+ BooleanWrapper b = MAPPER.readValue("{\"primitive\" : "+value+", \"wrapper\":"+value+", \"ctor\":"+value+"}",
+ BooleanWrapper.class);
+ assertEquals(Boolean.TRUE, b.wrapper);
+ assertTrue(b.primitive);
+ assertEquals(Boolean.TRUE, b.ctor);
+
+ // but ensure we can also get `false`
+ b = MAPPER.readValue("{\"primitive\" : 0 , \"wrapper\":0, \"ctor\":0}",
+ BooleanWrapper.class);
+ assertEquals(Boolean.FALSE, b.wrapper);
+ assertFalse(b.primitive);
+ assertEquals(Boolean.FALSE, b.ctor);
+
+ boolean[] boo = MAPPER.readValue("[ 0, 15, \"\", \"false\", \"True\" ]",
+ boolean[].class);
+ assertEquals(5, boo.length);
+ assertFalse(boo[0]);
+ assertTrue(boo[1]);
+ assertFalse(boo[2]);
+ assertFalse(boo[3]);
+ assertTrue(boo[4]);
+ }
+
+ /*
+ /**********************************************************
+ /* Scalar tests for integral types
+ /**********************************************************
+ */
+
+ public void testByteWrapper() throws Exception
+ {
+ Byte result = MAPPER.readValue(" -42\t", Byte.class);
+ assertEquals(Byte.valueOf((byte)-42), result);
+
+ // Also: should be able to coerce floats, strings:
+ result = MAPPER.readValue(" \"-12\"", Byte.class);
+ assertEquals(Byte.valueOf((byte)-12), result);
+
+ result = MAPPER.readValue(" 39.07", Byte.class);
+ assertEquals(Byte.valueOf((byte)39), result);
+ }
+
+ public void testShortWrapper() throws Exception
+ {
+ Short result = MAPPER.readValue("37", Short.class);
+ assertEquals(Short.valueOf((short)37), result);
+
+ // Also: should be able to coerce floats, strings:
+ result = MAPPER.readValue(" \"-1009\"", Short.class);
+ assertEquals(Short.valueOf((short)-1009), result);
+
+ result = MAPPER.readValue("-12.9", Short.class);
+ assertEquals(Short.valueOf((short)-12), result);
+ }
+
+ public void testCharacterWrapper() throws Exception
+ {
+ // First: canonical value is 1-char string
+ Character result = MAPPER.readValue("\"a\"", Character.class);
+ assertEquals(Character.valueOf('a'), result);
+
+ // But can also pass in ascii code
+ result = MAPPER.readValue(" "+((int) 'X'), Character.class);
+ assertEquals(Character.valueOf('X'), result);
+
+ final CharacterWrapperBean wrapper = MAPPER.readValue("{\"v\":null}", CharacterWrapperBean.class);
+ assertNotNull(wrapper);
+ assertNull(wrapper.getV());
+
+ try {
+ MAPPER.readerFor(CharacterBean.class)
+ .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
+ .readValue("{\"v\":null}");
+ fail("Attempting to deserialize a 'null' JSON reference into a 'char' property did not throw an exception");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "cannot map `null`");
+ }
+ final CharacterBean charBean = MAPPER.readerFor(CharacterBean.class)
+ .without(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
+ .readValue("{\"v\":null}");
+ assertNotNull(wrapper);
+ assertEquals('\u0000', charBean.getV());
+ }
+
+ public void testIntWrapper() throws Exception
+ {
+ Integer result = MAPPER.readValue(" -42\t", Integer.class);
+ assertEquals(Integer.valueOf(-42), result);
+
+ // Also: should be able to coerce floats, strings:
+ result = MAPPER.readValue(" \"-1200\"", Integer.class);
+ assertEquals(Integer.valueOf(-1200), result);
+
+ result = MAPPER.readValue(" 39.07", Integer.class);
+ assertEquals(Integer.valueOf(39), result);
+ }
+
+ public void testIntPrimitive() throws Exception
+ {
+ // first, simple case:
+ IntBean result = MAPPER.readValue("{\"v\":3}", IntBean.class);
+ assertEquals(3, result._v);
+
+ result = MAPPER.readValue("{\"v\":null}", IntBean.class);
+ assertNotNull(result);
+ assertEquals(0, result._v);
+
+ // should work with arrays too..
+ int[] array = MAPPER.readValue("[ null ]", int[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(0, array[0]);
+
+ // [databind#381]
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ try {
+ mapper.readValue("{\"v\":[3]}", IntBean.class);
+ 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) {
+ //Correctly threw exception
+ }
+
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ result = mapper.readValue("{\"v\":[3]}", IntBean.class);
+ assertEquals(3, result._v);
+
+ result = mapper.readValue("[{\"v\":[3]}]", IntBean.class);
+ assertEquals(3, result._v);
+
+ try {
+ mapper.readValue("[{\"v\":[3,3]}]", IntBean.class);
+ fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled");
+ } catch (MismatchedInputException exp) {
+ //threw exception as required
+ }
+
+ result = mapper.readValue("{\"v\":[null]}", IntBean.class);
+ assertNotNull(result);
+ assertEquals(0, result._v);
+
+ array = mapper.readValue("[ [ null ] ]", int[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(0, array[0]);
+ }
+
+ public void testLongWrapper() throws Exception
+ {
+ Long result = MAPPER.readValue("12345678901", Long.class);
+ assertEquals(Long.valueOf(12345678901L), result);
+
+ // Also: should be able to coerce floats, strings:
+ result = MAPPER.readValue(" \"-9876\"", Long.class);
+ assertEquals(Long.valueOf(-9876), result);
+
+ result = MAPPER.readValue("1918.3", Long.class);
+ assertEquals(Long.valueOf(1918), result);
+ }
+
+ public void testLongPrimitive() throws Exception
+ {
+ // first, simple case:
+ LongBean result = MAPPER.readValue("{\"v\":3}", LongBean.class);
+ assertEquals(3, result._v);
+ result = MAPPER.readValue("{\"v\":null}", LongBean.class);
+ assertNotNull(result);
+ assertEquals(0, result._v);
+
+ // should work with arrays too..
+ long[] array = MAPPER.readValue("[ null ]", long[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(0, array[0]);
+
+ // [databind#381]
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ try {
+ mapper.readValue("{\"v\":[3]}", LongBean.class);
+ 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) {
+ //Correctly threw exception
+ }
+
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ result = mapper.readValue("{\"v\":[3]}", LongBean.class);
+ assertEquals(3, result._v);
+
+ result = mapper.readValue("[{\"v\":[3]}]", LongBean.class);
+ assertEquals(3, result._v);
+
+ try {
+ mapper.readValue("[{\"v\":[3,3]}]", LongBean.class);
+ fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled");
+ } catch (MismatchedInputException exp) {
+ //threw exception as required
+ }
+
+ result = mapper.readValue("{\"v\":[null]}", LongBean.class);
+ assertNotNull(result);
+ assertEquals(0, result._v);
+
+ array = mapper.readValue("[ [ null ] ]", long[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(0, array[0]);
+ }
+
+ /**
+ * Beyond simple case, let's also ensure that method overriding works as
+ * expected.
+ */
+ public void testIntWithOverride() throws Exception
+ {
+ IntBean2 result = MAPPER.readValue("{\"v\":8}", IntBean2.class);
+ assertEquals(9, result._v);
+ }
+
+ /*
+ /**********************************************************
+ /* Scalar tests for floating point types
+ /**********************************************************
+ */
+
+ public void testDoublePrimitive() throws Exception
+ {
+ // first, simple case:
+ // bit tricky with binary fps but...
+ final double value = 0.016;
+ DoubleBean result = MAPPER.readValue("{\"v\":"+value+"}", DoubleBean.class);
+ assertEquals(value, result._v);
+ // then [JACKSON-79]:
+ result = MAPPER.readValue("{\"v\":null}", DoubleBean.class);
+ assertNotNull(result);
+ assertEquals(0.0, result._v);
+
+ // should work with arrays too..
+ double[] array = MAPPER.readValue("[ null ]", double[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(0.0, array[0]);
+ }
+
+ /* Note: dealing with floating-point values is tricky; not sure if
+ * we can really use equality tests here... JDK does have decent
+ * conversions though, to retain accuracy and round-trippability.
+ * But still...
+ */
+ public void testFloatWrapper() throws Exception
+ {
+ // Also: should be able to coerce floats, strings:
+ String[] STRS = new String[] {
+ "1.0", "0.0", "-0.3", "0.7", "42.012", "-999.0", NAN_STRING
+ };
+
+ for (String str : STRS) {
+ Float exp = Float.valueOf(str);
+ Float result;
+
+ if (NAN_STRING != str) {
+ // First, as regular floating point value
+ result = MAPPER.readValue(str, Float.class);
+ assertEquals(exp, result);
+ }
+
+ // and then as coerced String:
+ result = MAPPER.readValue(" \""+str+"\"", Float.class);
+ assertEquals(exp, result);
+ }
+ }
+
+ public void testDoubleWrapper() throws Exception
+ {
+ // Also: should be able to coerce doubles, strings:
+ String[] STRS = new String[] {
+ "1.0", "0.0", "-0.3", "0.7", "42.012", "-999.0", NAN_STRING
+ };
+
+ for (String str : STRS) {
+ Double exp = Double.valueOf(str);
+ Double result;
+
+ // First, as regular double value
+ if (NAN_STRING != str) {
+ result = MAPPER.readValue(str, Double.class);
+ assertEquals(exp, result);
+ }
+ // and then as coerced String:
+ result = MAPPER.readValue(" \""+str+"\"", Double.class);
+ assertEquals(exp, result);
+ }
+ }
+
+ public void testDoubleAsArray() throws Exception
+ {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ final double value = 0.016;
+ try {
+ mapper.readValue("{\"v\":[" + value + "]}", DoubleBean.class);
+ fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled");
+ } catch (JsonMappingException exp) {
+ //Correctly threw exception
+ }
+
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ DoubleBean result = mapper.readValue("{\"v\":[" + value + "]}",
+ DoubleBean.class);
+ assertEquals(value, result._v);
+
+ result = mapper.readValue("[{\"v\":[" + value + "]}]", DoubleBean.class);
+ assertEquals(value, result._v);
+
+ try {
+ mapper.readValue("[{\"v\":[" + value + "," + value + "]}]", DoubleBean.class);
+ fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled");
+ } catch (MismatchedInputException exp) {
+ //threw exception as required
+ }
+
+ result = mapper.readValue("{\"v\":[null]}", DoubleBean.class);
+ assertNotNull(result);
+ assertEquals(0d, result._v);
+
+ double[] array = mapper.readValue("[ [ null ] ]", double[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(0d, array[0]);
+ }
+
+ public void testDoublePrimitiveNonNumeric() throws Exception
+ {
+ // first, simple case:
+ // bit tricky with binary fps but...
+ double value = Double.POSITIVE_INFINITY;
+ DoubleBean result = MAPPER.readValue("{\"v\":\""+value+"\"}", DoubleBean.class);
+ assertEquals(value, result._v);
+
+ // should work with arrays too..
+ double[] array = MAPPER.readValue("[ \"Infinity\" ]", double[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(Double.POSITIVE_INFINITY, array[0]);
+ }
+
+ public void testFloatPrimitiveNonNumeric() throws Exception
+ {
+ // bit tricky with binary fps but...
+ float value = Float.POSITIVE_INFINITY;
+ FloatBean result = MAPPER.readValue("{\"v\":\""+value+"\"}", FloatBean.class);
+ assertEquals(value, result._v);
+
+ // should work with arrays too..
+ float[] array = MAPPER.readValue("[ \"Infinity\" ]", float[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertEquals(Float.POSITIVE_INFINITY, array[0]);
+ }
+
+ /*
+ /**********************************************************
+ /* Scalar tests, other
+ /**********************************************************
+ */
+
+ 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");
+
+ // default encoding is "MIME, no linefeeds", so:
+ Assert.assertArrayEquals(INPUT, MAPPER.readValue(
+ quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA=="),
+ byte[].class));
+ ObjectReader reader = MAPPER.readerFor(byte[].class);
+ Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.MIME_NO_LINEFEEDS).readValue(
+ quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA=="
+ )));
+
+ // but others should be slightly different
+ Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.MIME).readValue(
+ quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1\\ndnd4eXoxMjM0NTY3ODkwWA=="
+ )));
+ Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.MODIFIED_FOR_URL).readValue(
+ quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA"
+ )));
+ // PEM mandates 64 char lines:
+ Assert.assertArrayEquals(INPUT, (byte[]) reader.with(Base64Variants.PEM).readValue(
+ quote("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwYWJjZGVmZ2hpamts\\nbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwWA=="
+ )));
+ }
+
+ /*
+ /**********************************************************
+ /* Sequence tests
+ /**********************************************************
+ */
+
+ /**
+ * Then a unit test to verify that we can conveniently bind sequence of
+ * space-separate simple values
+ */
+ public void testSequenceOfInts() throws Exception
+ {
+ final int NR_OF_INTS = 100;
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < NR_OF_INTS; ++i) {
+ sb.append(" ");
+ sb.append(i);
+ }
+ JsonParser jp = MAPPER.getFactory().createParser(sb.toString());
+ for (int i = 0; i < NR_OF_INTS; ++i) {
+ Integer result = MAPPER.readValue(jp, Integer.class);
+ assertEquals(Integer.valueOf(i), result);
+ }
+ jp.close();
+ }
+
+ /*
+ /**********************************************************
+ /* Empty String coercion, handling
+ /**********************************************************
+ */
+
+ // by default, should return nulls, n'est pas?
+ public void testEmptyStringForWrappers() throws IOException
+ {
+ WrappersBean bean;
+
+ bean = MAPPER.readValue("{\"booleanValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.booleanValue);
+ bean = MAPPER.readValue("{\"byteValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.byteValue);
+
+ // char/Character is different... not sure if this should work or not:
+ bean = MAPPER.readValue("{\"charValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.charValue);
+
+ bean = MAPPER.readValue("{\"shortValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.shortValue);
+ bean = MAPPER.readValue("{\"intValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.intValue);
+ bean = MAPPER.readValue("{\"longValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.longValue);
+ bean = MAPPER.readValue("{\"floatValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.floatValue);
+ bean = MAPPER.readValue("{\"doubleValue\":\"\"}", WrappersBean.class);
+ assertNull(bean.doubleValue);
+ }
+
+ public void testEmptyStringForPrimitives() throws IOException
+ {
+ PrimitivesBean bean;
+ bean = MAPPER.readValue("{\"booleanValue\":\"\"}", PrimitivesBean.class);
+ assertFalse(bean.booleanValue);
+ bean = MAPPER.readValue("{\"byteValue\":\"\"}", PrimitivesBean.class);
+ assertEquals((byte) 0, bean.byteValue);
+ bean = MAPPER.readValue("{\"charValue\":\"\"}", PrimitivesBean.class);
+ assertEquals((char) 0, bean.charValue);
+ bean = MAPPER.readValue("{\"shortValue\":\"\"}", PrimitivesBean.class);
+ assertEquals((short) 0, bean.shortValue);
+ bean = MAPPER.readValue("{\"intValue\":\"\"}", PrimitivesBean.class);
+ assertEquals(0, bean.intValue);
+ bean = MAPPER.readValue("{\"longValue\":\"\"}", PrimitivesBean.class);
+ assertEquals(0L, bean.longValue);
+ 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
+ {
+ final ObjectReader reader = MAPPER
+ .readerFor(PrimitivesBean.class)
+ .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
+ try {
+ reader.readValue(aposToQuotes("{'"+propName+"':''}"));
+ fail("Expected failure for boolean + empty String");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Cannot coerce empty String (\"\")");
+ verifyException(e, "to Null value");
+ }
+ }
+
+ // for [databind#403]
+ public void testEmptyStringFailForPrimitives() throws IOException
+ {
+ _verifyEmptyStringFailForPrimitives("booleanValue");
+ _verifyEmptyStringFailForPrimitives("byteValue");
+ _verifyEmptyStringFailForPrimitives("charValue");
+ _verifyEmptyStringFailForPrimitives("shortValue");
+ _verifyEmptyStringFailForPrimitives("intValue");
+ _verifyEmptyStringFailForPrimitives("longValue");
+ _verifyEmptyStringFailForPrimitives("floatValue");
+ _verifyEmptyStringFailForPrimitives("doubleValue");
+ }
+
+ /*
+ /**********************************************************
+ /* Null handling for scalars in POJO
+ /**********************************************************
+ */
+
+ public void testNullForPrimitives() throws IOException
+ {
+ // by default, ok to rely on defaults
+ PrimitivesBean bean = MAPPER.readValue(
+ "{\"intValue\":null, \"booleanValue\":null, \"doubleValue\":null}",
+ PrimitivesBean.class);
+ assertNotNull(bean);
+ assertEquals(0, bean.intValue);
+ assertEquals(false, bean.booleanValue);
+ assertEquals(0.0, bean.doubleValue);
+
+ bean = MAPPER.readValue("{\"byteValue\":null, \"longValue\":null, \"floatValue\":null}",
+ PrimitivesBean.class);
+ assertNotNull(bean);
+ assertEquals((byte) 0, bean.byteValue);
+ assertEquals(0L, bean.longValue);
+ assertEquals(0.0f, bean.floatValue);
+
+ // but not when enabled
+ final ObjectReader reader = MAPPER
+ .readerFor(PrimitivesBean.class)
+ .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
+ // boolean
+ try {
+ reader.readValue("{\"booleanValue\":null}");
+ fail("Expected failure for boolean + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type boolean");
+ verifyPath(e, "booleanValue");
+ }
+ // byte/char/short/int/long
+ try {
+ reader.readValue("{\"byteValue\":null}");
+ fail("Expected failure for byte + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type byte");
+ verifyPath(e, "byteValue");
+ }
+ try {
+ reader.readValue("{\"charValue\":null}");
+ fail("Expected failure for char + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type char");
+ verifyPath(e, "charValue");
+ }
+ try {
+ reader.readValue("{\"shortValue\":null}");
+ fail("Expected failure for short + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type short");
+ verifyPath(e, "shortValue");
+ }
+ try {
+ reader.readValue("{\"intValue\":null}");
+ fail("Expected failure for int + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type int");
+ verifyPath(e, "intValue");
+ }
+ try {
+ reader.readValue("{\"longValue\":null}");
+ fail("Expected failure for long + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type long");
+ verifyPath(e, "longValue");
+ }
+
+ // float/double
+ try {
+ reader.readValue("{\"floatValue\":null}");
+ fail("Expected failure for float + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type float");
+ verifyPath(e, "floatValue");
+ }
+ try {
+ reader.readValue("{\"doubleValue\":null}");
+ fail("Expected failure for double + null");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type double");
+ verifyPath(e, "doubleValue");
+ }
+ }
+
+ // [databind#2101]
+ public void testNullForPrimitivesViaCreator() throws IOException
+ {
+ try {
+ /*PrimitiveCreatorBean bean =*/ MAPPER
+ .readerFor(PrimitiveCreatorBean.class)
+ .with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
+ .readValue(aposToQuotes("{'a': null}"));
+ fail("Expected failure for `int` and `null`");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot map `null` into type int");
+ verifyPath(e, "a");
+ }
+ }
+
+ private void verifyPath(MismatchedInputException e, String propName) {
+ final List<Reference> path = e.getPath();
+ assertEquals(1, path.size());
+ assertEquals(propName, path.get(0).getFieldName());
+ }
+
+ public void testNullForPrimitiveArrays() throws IOException
+ {
+ _testNullForPrimitiveArrays(boolean[].class, Boolean.FALSE);
+ _testNullForPrimitiveArrays(byte[].class, Byte.valueOf((byte) 0));
+ _testNullForPrimitiveArrays(char[].class, Character.valueOf((char) 0), false);
+ _testNullForPrimitiveArrays(short[].class, Short.valueOf((short)0));
+ _testNullForPrimitiveArrays(int[].class, Integer.valueOf(0));
+ _testNullForPrimitiveArrays(long[].class, Long.valueOf(0L));
+ _testNullForPrimitiveArrays(float[].class, Float.valueOf(0f));
+ _testNullForPrimitiveArrays(double[].class, Double.valueOf(0d));
+ }
+
+ private void _testNullForPrimitiveArrays(Class<?> cls, Object defValue) throws IOException {
+ _testNullForPrimitiveArrays(cls, defValue, true);
+ }
+
+ private void _testNullForPrimitiveArrays(Class<?> cls, Object defValue,
+ boolean testEmptyString) throws IOException
+ {
+ final String EMPTY_STRING_JSON = "[ \"\" ]";
+ final String JSON_WITH_NULL = "[ null ]";
+ final String SIMPLE_NAME = "`"+cls.getSimpleName()+"`";
+ final ObjectReader readerCoerceOk = MAPPER.readerFor(cls);
+ final ObjectReader readerNoCoerce = 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);
+ fail("Should not pass");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Cannot coerce `null`");
+ verifyException(e, "as content of type "+SIMPLE_NAME);
+ }
+
+ if (testEmptyString) {
+ ob = readerCoerceOk.forType(cls).readValue(EMPTY_STRING_JSON);
+ assertEquals(1, Array.getLength(ob));
+ assertEquals(defValue, Array.get(ob, 0));
+
+ try {
+ readerNoCoerce.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);
+ }
+ }
+ }
+
+ // [databind#2197]
+ public void testVoidDeser() throws Exception
+ {
+ VoidBean bean = MAPPER.readValue(aposToQuotes("{'value' : 123 }"),
+ VoidBean.class);
+ assertNull(bean.value);
+ }
+
+ /*
+ /**********************************************************
+ /* Test for invalid String values
+ /**********************************************************
+ */
+
+ public void testInvalidStringCoercionFail() throws IOException
+ {
+ _testInvalidStringCoercionFail(boolean[].class);
+ _testInvalidStringCoercionFail(byte[].class);
+
+ // 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);
+ }
+
+ private void _testInvalidStringCoercionFail(Class<?> cls) throws IOException
+ {
+ final String JSON = "[ \"foobar\" ]";
+ final String SIMPLE_NAME = cls.getSimpleName();
+
+ try {
+ MAPPER.readerFor(cls).readValue(JSON);
+ fail("Should not pass");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Cannot deserialize value of type `"+SIMPLE_NAME+"` from String \"foobar\"");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/JDKStringLikeTypesTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java
similarity index 78%
rename from src/test/java/com/fasterxml/jackson/databind/deser/JDKStringLikeTypesTest.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java
index cef00c1..7f65dd3 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/JDKStringLikeTypesTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKStringLikeTypesTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.*;
import java.net.*;
@@ -10,10 +10,11 @@
import java.util.regex.Pattern;
import com.fasterxml.jackson.annotation.*;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+
import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
+
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
@@ -55,7 +56,6 @@
jp.skipChildren();
return new StackTraceElement("a", "b", "b", StackTraceBean.NUM);
}
-
}
/*
@@ -112,41 +112,6 @@
assertSame(String.class, result.clazz);
}
- public void testClassAsArray() throws Exception
- {
- final ObjectMapper mapper = new ObjectMapper();
- Class<?> result = mapper
- .readerFor(Class.class)
- .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
- .readValue(quote(String.class.getName()));
- assertEquals(String.class, result);
-
- try {
- mapper
- .readerFor(Class.class)
- .without(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
- .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 (JsonMappingException e) {
- verifyException(e, "out of START_ARRAY token");
- }
-
- try {
- mapper
- .readerFor(Class.class)
- .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
- .readValue("[" + quote(Object.class.getName()) + "," + quote(Object.class.getName()) +"]");
- fail("Did not throw exception when UNWRAP_SINGLE_VALUE_ARRAYS feature was enabled and attempted to read a Class array containing two elements");
- } catch (JsonMappingException e) {
- verifyException(e, "more than a single value in");
- }
- result = mapper
- .readerFor(Class.class)
- .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
- .readValue("[" + quote(String.class.getName()) + "]");
- assertEquals(String.class, result);
- }
-
public void testCurrency() throws IOException
{
Currency usd = Currency.getInstance("USD");
@@ -163,13 +128,6 @@
String json = MAPPER.writeValueAsString(abs);
File result = MAPPER.readValue(json, File.class);
assertEquals(abs, result.getAbsolutePath());
-
- // Then #170
- final ObjectMapper mapper2 = new ObjectMapper();
- mapper2.setVisibility(PropertyAccessor.CREATOR, Visibility.NONE);
-
- result = mapper2.readValue(json, File.class);
- assertEquals(abs, result.getAbsolutePath());
}
public void testLocale() throws IOException
@@ -317,30 +275,6 @@
}
}
- public void testURIAsArray() throws Exception
- {
- final ObjectReader reader = MAPPER.readerFor(URI.class);
- final URI value = new URI("http://foo.com");
- try {
- reader.without(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
- .readValue("[\""+value.toString()+"\"]");
- fail("Did not throw exception for single value array when UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
- } catch (JsonMappingException e) {
- verifyException(e, "out of START_ARRAY token");
- }
-
- try {
- reader.with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
- .readValue("[\""+value.toString()+"\",\""+value.toString()+"\"]");
- fail("Did not throw exception for single value array when there were multiple values");
- } catch (JsonMappingException e) {
- verifyException(e, "more than a single value in the array");
- }
- assertEquals(value,
- reader.with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
- .readValue("[\""+value.toString()+"\"]"));
- }
-
public void testURL() throws Exception
{
URL exp = new URL("http://foo.com");
@@ -387,25 +321,6 @@
UUID uuid = UUID.fromString(value);
assertEquals(uuid,
mapper.readValue(quote(value), UUID.class));
-
- try {
- mapper.readValue("[" + quote(value) + "]", UUID.class);
- fail("Exception was not thrown when UNWRAP_SINGLE_VALUE_ARRAYS is disabled and attempted to read a single value array as a single element");
- } catch (JsonMappingException exp) {
- //Exception thrown successfully
- }
-
- mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
-
- assertEquals(uuid,
- mapper.readValue("[" + quote(value) + "]", UUID.class));
-
- try {
- mapper.readValue("[" + quote(value) + "," + quote(value) + "]", UUID.class);
- fail("Exception was not thrown when UNWRAP_SINGLE_VALUE_ARRAYS is enabled and attempted to read a multi value array as a single element");
- } catch (JsonMappingException exp) {
- //Exception thrown successfully
- }
}
// then use templating; note that these are not exactly valid UUIDs
// wrt spec (type bits etc), but JDK UUID should deal ok
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeserializationTest.java
similarity index 80%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeserializationTest.java
index 76d9ee6..4b56d28 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeserializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.IOException;
import java.text.DateFormat;
@@ -14,7 +14,7 @@
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
@SuppressWarnings("serial")
-public class TestMapDeserialization
+public class MapDeserializationTest
extends BaseMapTest
{
static enum Key {
@@ -28,18 +28,18 @@
public BrokenMap(boolean dummy) { super(); }
}
- @JsonDeserialize(using=MapDeserializer.class)
+ @JsonDeserialize(using=CustomMapDeserializer.class)
static class CustomMap extends LinkedHashMap<String,String> { }
- static class MapDeserializer extends StdDeserializer<CustomMap>
+ static class CustomMapDeserializer extends StdDeserializer<CustomMap>
{
- public MapDeserializer() { super(CustomMap.class); }
+ public CustomMapDeserializer() { super(CustomMap.class); }
@Override
- public CustomMap deserialize(JsonParser jp, DeserializationContext ctxt)
+ public CustomMap deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
CustomMap result = new CustomMap();
- result.put("x", jp.getText());
+ result.put("x", p.getText());
return result;
}
}
@@ -57,7 +57,6 @@
}
}
- // [databind#142]
public static class EnumMapContainer {
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
public EnumMap<KeyEnum,ITestType> testTypes;
@@ -79,6 +78,10 @@
static class ClassStringMap extends HashMap<Class<?>,String> { }
+ static class AbstractMapWrapper {
+ public AbstractMap<String, Integer> values;
+ }
+
/*
/**********************************************************
/* Test methods, untyped (Object valued) maps
@@ -87,28 +90,6 @@
private final ObjectMapper MAPPER = new ObjectMapper();
- public void testUntypedMap() throws Exception
- {
- // to get "untyped" default map-to-map, pass Object.class
- String JSON = "{ \"foo\" : \"bar\", \"crazy\" : true, \"null\" : null }";
-
- // Not a guaranteed cast theoretically, but will work:
- @SuppressWarnings("unchecked")
- Map<String,Object> result = (Map<String,Object>)MAPPER.readValue(JSON, Object.class);
- assertNotNull(result);
- assertTrue(result instanceof Map<?,?>);
-
- assertEquals(3, result.size());
-
- assertEquals("bar", result.get("foo"));
- assertEquals(Boolean.TRUE, result.get("crazy"));
- assertNull(result.get("null"));
-
- // Plus, non existing:
- assertNull(result.get("bar"));
- assertNull(result.get(3));
- }
-
public void testBigUntypedMap() throws Exception
{
Map<String,Object> map = new LinkedHashMap<String,Object>();
@@ -246,8 +227,8 @@
{
// to get typing, must use type reference
String JSON = "{ \"1\" : true, \"-1\" : false }";
- Map<String,Integer> result = MAPPER.readValue
- (JSON, new TypeReference<HashMap<Integer,Boolean>>() { });
+ Map<?,Object> result = MAPPER.readValue
+ (JSON, new TypeReference<HashMap<Integer,Object>>() { });
assertNotNull(result);
assertEquals(HashMap.class, result.getClass());
@@ -298,6 +279,14 @@
assertNull(result.get(""));
}
+ public void testAbstractMapDefault() throws Exception
+ {
+ final AbstractMapWrapper result = MAPPER.readValue("{\"values\":{\"foo\":42}}",
+ AbstractMapWrapper.class);
+ assertNotNull(result);
+ assertEquals(LinkedHashMap.class, result.values.getClass());
+ }
+
/*
/**********************************************************
/* Test methods, maps with enums
@@ -504,78 +493,6 @@
/*
/**********************************************************
- /* Test methods, annotated Map.Entry
- /**********************************************************
- */
-
- public void testMapEntrySimpleTypes() throws Exception
- {
- List<Map.Entry<String,Long>> stuff = MAPPER.readValue(aposToQuotes("[{'a':15},{'b':42}]"),
- new TypeReference<List<Map.Entry<String,Long>>>() { });
- assertNotNull(stuff);
- assertEquals(2, stuff.size());
- assertNotNull(stuff.get(1));
- assertEquals("b", stuff.get(1).getKey());
- assertEquals(Long.valueOf(42), stuff.get(1).getValue());
- }
-
- public void testMapEntryWithStringBean() throws Exception
- {
- List<Map.Entry<Integer,StringWrapper>> stuff = MAPPER.readValue(aposToQuotes("[{'28':'Foo'},{'13':'Bar'}]"),
- new TypeReference<List<Map.Entry<Integer,StringWrapper>>>() { });
- assertNotNull(stuff);
- assertEquals(2, stuff.size());
- assertNotNull(stuff.get(1));
- assertEquals(Integer.valueOf(13), stuff.get(1).getKey());
-
- StringWrapper sw = stuff.get(1).getValue();
- assertEquals("Bar", sw.str);
- }
-
- public void testMapEntryFail() throws Exception
- {
- try {
- /*List<Map.Entry<Integer,StringWrapper>> stuff =*/ MAPPER.readValue(aposToQuotes("[{'28':'Foo','13':'Bar'}]"),
- new TypeReference<List<Map.Entry<Integer,StringWrapper>>>() { });
- fail("Should not have passed");
- } catch (Exception e) {
- verifyException(e, "more than one entry in JSON");
- }
- }
-
- /*
- /**********************************************************
- /* Test methods, other exotic Map types
- /**********************************************************
- */
-
- // [databind#810]
- public void testReadProperties() throws Exception
- {
- Properties props = MAPPER.readValue(aposToQuotes("{'a':'foo', 'b':123, 'c':true}"),
- Properties.class);
- assertEquals(3, props.size());
- assertEquals("foo", props.getProperty("a"));
- assertEquals("123", props.getProperty("b"));
- assertEquals("true", props.getProperty("c"));
- }
-
- // JDK singletonMap
- public void testSingletonMapRoundtrip() throws Exception
- {
- final TypeReference<?> type = new TypeReference<Map<String,IntWrapper>>() { };
-
- String json = MAPPER.writeValueAsString(Collections.singletonMap("value", new IntWrapper(5)));
- Map<String,IntWrapper> result = MAPPER.readValue(json, type);
- assertNotNull(result);
- assertEquals(1, result.size());
- IntWrapper w = result.get("value");
- assertNotNull(w);
- assertEquals(5, w.i);
- }
-
- /*
- /**********************************************************
/* Error tests
/**********************************************************
*/
@@ -584,7 +501,7 @@
{
try {
Object result = MAPPER.readValue("[ 1, 2 ]",
- new TypeReference<Map<String,String>>() { });
+ new TypeReference<Map<String,String>>() { });
fail("Expected an exception, but got result value: "+result);
} catch (JsonMappingException jex) {
verifyException(jex, "START_ARRAY");
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapKeyDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapKeyDeserializationTest.java
new file mode 100644
index 0000000..32a616c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapKeyDeserializationTest.java
@@ -0,0 +1,141 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.core.Base64Variants;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+
+import org.junit.Assert;
+
+public class MapKeyDeserializationTest extends BaseMapTest
+{
+ static class FullName {
+ private String _firstname, _lastname;
+
+ private FullName(String firstname, String lastname) {
+ _firstname = firstname;
+ _lastname = lastname;
+ }
+
+ @JsonCreator
+ public static FullName valueOf(String value) {
+ String[] mySplit = value.split("\\.");
+ return new FullName(mySplit[0], mySplit[1]);
+ }
+
+ public static FullName valueOf(String firstname, String lastname) {
+ return new FullName(firstname, lastname);
+ }
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return _firstname + "." + _lastname;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, wrapper keys
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = objectMapper();
+
+ public void testBooleanMapKeyDeserialization() throws Exception
+ {
+ TypeReference<?> type = new TypeReference<MapWrapper<Boolean, String>>() { };
+ MapWrapper<byte[], String> result = MAPPER.readValue(aposToQuotes("{'map':{'true':'foobar'}}"), type);
+
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Boolean.TRUE, result.map.entrySet().iterator().next().getKey());
+
+ result = MAPPER.readValue(aposToQuotes("{'map':{'false':'foobar'}}"), type);
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Boolean.FALSE, result.map.entrySet().iterator().next().getKey());
+ }
+
+ public void testByteMapKeyDeserialization() throws Exception
+ {
+ TypeReference<?> type = new TypeReference<MapWrapper<Byte, String>>() { };
+ MapWrapper<byte[], String> result = MAPPER.readValue(aposToQuotes("{'map':{'13':'foobar'}}"), type);
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Byte.valueOf((byte) 13), result.map.entrySet().iterator().next().getKey());
+ }
+
+ public void testShortMapKeyDeserialization() throws Exception
+ {
+ TypeReference<?> type = new TypeReference<MapWrapper<Short, String>>() { };
+ MapWrapper<byte[], String> result = MAPPER.readValue(aposToQuotes("{'map':{'13':'foobar'}}"), type);
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Short.valueOf((short) 13), result.map.entrySet().iterator().next().getKey());
+ }
+
+ public void testIntegerMapKeyDeserialization() throws Exception
+ {
+ TypeReference<?> type = new TypeReference<MapWrapper<Integer, String>>() { };
+ MapWrapper<byte[], String> result = MAPPER.readValue(aposToQuotes("{'map':{'-3':'foobar'}}"), type);
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Integer.valueOf(-3), result.map.entrySet().iterator().next().getKey());
+ }
+
+ public void testLongMapKeyDeserialization() throws Exception
+ {
+ TypeReference<?> type = new TypeReference<MapWrapper<Long, String>>() { };
+ MapWrapper<byte[], String> result = MAPPER.readValue(aposToQuotes("{'map':{'42':'foobar'}}"), type);
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Long.valueOf(42), result.map.entrySet().iterator().next().getKey());
+ }
+
+ public void testFloatMapKeyDeserialization() throws Exception
+ {
+ TypeReference<?> type = new TypeReference<MapWrapper<Float, String>>() { };
+ MapWrapper<byte[], String> result = MAPPER.readValue(aposToQuotes("{'map':{'3.5':'foobar'}}"), type);
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Float.valueOf(3.5f), result.map.entrySet().iterator().next().getKey());
+ }
+
+ public void testDoubleMapKeyDeserialization() throws Exception
+ {
+ TypeReference<?> type = new TypeReference<MapWrapper<Double, String>>() { };
+ MapWrapper<byte[], String> result = MAPPER.readValue(aposToQuotes("{'map':{'0.25':'foobar'}}"), type);
+ assertEquals(1, result.map.size());
+ Assert.assertEquals(Double.valueOf(0.25), result.map.entrySet().iterator().next().getKey());
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, other
+ /**********************************************************
+ */
+
+ public void testDeserializeKeyViaFactory() throws Exception
+ {
+ Map<FullName, Double> map =
+ MAPPER.readValue("{\"first.last\": 42}",
+ new TypeReference<Map<FullName, Double>>() { });
+ Map.Entry<FullName, Double> entry = map.entrySet().iterator().next();
+ FullName key = entry.getKey();
+ assertEquals(key._firstname, "first");
+ assertEquals(key._lastname, "last");
+ assertEquals(entry.getValue().doubleValue(), 42, 0);
+ }
+
+ public void testByteArrayMapKeyDeserialization() throws Exception
+ {
+ byte[] binary = new byte[] { 1, 2, 4, 8, 16, 33, 79 };
+ String encoded = Base64Variants.MIME.encode(binary);
+
+ MapWrapper<byte[], String> result = MAPPER.readValue(
+ aposToQuotes("{'map':{'"+encoded+"':'foobar'}}"),
+ new TypeReference<MapWrapper<byte[], String>>() { });
+ assertEquals(1, result.map.size());
+ Map.Entry<byte[],String> entry = result.map.entrySet().iterator().next();
+ assertEquals("foobar", entry.getValue());
+ byte[] key = entry.getKey();
+ Assert.assertArrayEquals(binary, key);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapRelatedTypesDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapRelatedTypesDeserTest.java
new file mode 100644
index 0000000..bc8dc16
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapRelatedTypesDeserTest.java
@@ -0,0 +1,84 @@
+package com.fasterxml.jackson.databind.deser.jdk;
+
+import java.util.*;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+
+public class MapRelatedTypesDeserTest
+ extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ /*
+ /**********************************************************
+ /* Test methods, Map.Entry
+ /**********************************************************
+ */
+
+ public void testMapEntrySimpleTypes() throws Exception
+ {
+ List<Map.Entry<String,Long>> stuff = MAPPER.readValue(aposToQuotes("[{'a':15},{'b':42}]"),
+ new TypeReference<List<Map.Entry<String,Long>>>() { });
+ assertNotNull(stuff);
+ assertEquals(2, stuff.size());
+ assertNotNull(stuff.get(1));
+ assertEquals("b", stuff.get(1).getKey());
+ assertEquals(Long.valueOf(42), stuff.get(1).getValue());
+ }
+
+ public void testMapEntryWithStringBean() throws Exception
+ {
+ List<Map.Entry<Integer,StringWrapper>> stuff = MAPPER.readValue(aposToQuotes("[{'28':'Foo'},{'13':'Bar'}]"),
+ new TypeReference<List<Map.Entry<Integer,StringWrapper>>>() { });
+ assertNotNull(stuff);
+ assertEquals(2, stuff.size());
+ assertNotNull(stuff.get(1));
+ assertEquals(Integer.valueOf(13), stuff.get(1).getKey());
+
+ StringWrapper sw = stuff.get(1).getValue();
+ assertEquals("Bar", sw.str);
+ }
+
+ public void testMapEntryFail() throws Exception
+ {
+ try {
+ /*List<Map.Entry<Integer,StringWrapper>> stuff =*/ MAPPER.readValue(aposToQuotes("[{'28':'Foo','13':'Bar'}]"),
+ new TypeReference<List<Map.Entry<Integer,StringWrapper>>>() { });
+ fail("Should not have passed");
+ } catch (Exception e) {
+ verifyException(e, "more than one entry in JSON");
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, other exotic Map types
+ /**********************************************************
+ */
+
+ // [databind#810]
+ public void testReadProperties() throws Exception
+ {
+ Properties props = MAPPER.readValue(aposToQuotes("{'a':'foo', 'b':123, 'c':true}"),
+ Properties.class);
+ assertEquals(3, props.size());
+ assertEquals("foo", props.getProperty("a"));
+ assertEquals("123", props.getProperty("b"));
+ assertEquals("true", props.getProperty("c"));
+ }
+
+ // JDK singletonMap
+ public void testSingletonMapRoundtrip() throws Exception
+ {
+ final TypeReference<?> type = new TypeReference<Map<String,IntWrapper>>() { };
+
+ String json = MAPPER.writeValueAsString(Collections.singletonMap("value", new IntWrapper(5)));
+ Map<String,IntWrapper> result = MAPPER.readValue(json, type);
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ IntWrapper w = result.get("value");
+ assertNotNull(w);
+ assertEquals(5, w.i);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestUntypedDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/UntypedDeserializationTest.java
similarity index 68%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestUntypedDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/UntypedDeserializationTest.java
index eaf2bc8..3b28b9d 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestUntypedDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/UntypedDeserializationTest.java
@@ -1,13 +1,17 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
@@ -17,7 +21,7 @@
* one that only uses core JDK types; wrappers, Maps and Lists.
*/
@SuppressWarnings("serial")
-public class TestUntypedDeserialization
+public class UntypedDeserializationTest
extends BaseMapTest
{
static class UCStringDeserializer
@@ -26,8 +30,8 @@
public UCStringDeserializer() { super(String.class); }
@Override
- public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
- return jp.getText().toUpperCase();
+ public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ return p.getText().toUpperCase();
}
}
@@ -42,7 +46,7 @@
}
@Override
- public Number deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+ public Number deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return value;
}
}
@@ -54,12 +58,12 @@
public ListDeserializer() { super(List.class); }
@Override
- public List<Object> deserialize(JsonParser jp, DeserializationContext ctxt)
+ public List<Object> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
ArrayList<Object> list = new ArrayList<Object>();
- while (jp.nextValue() != JsonToken.END_ARRAY) {
- list.add("X"+jp.getText());
+ while (p.nextValue() != JsonToken.END_ARRAY) {
+ list.add("X"+p.getText());
}
return list;
}
@@ -76,17 +80,17 @@
}
}
- static class MapDeserializer extends StdDeserializer<Map<String,Object>>
+ static class YMapDeserializer extends StdDeserializer<Map<String,Object>>
{
- public MapDeserializer() { super(Map.class); }
+ public YMapDeserializer() { super(Map.class); }
@Override
- public Map<String,Object> deserialize(JsonParser jp, DeserializationContext ctxt)
+ public Map<String,Object> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
Map<String,Object> map = new LinkedHashMap<String,Object>();
- while (jp.nextValue() != JsonToken.END_OBJECT) {
- map.put(jp.getCurrentName(), "Y"+jp.getText());
+ while (p.nextValue() != JsonToken.END_OBJECT) {
+ map.put(p.getCurrentName(), "Y"+p.getText());
}
return map;
}
@@ -101,6 +105,11 @@
}
}
+ static class WrappedPolymorphicUntyped {
+ @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS)
+ public Object value;
+ }
+
static class WrappedUntyped1460 {
public Object value;
}
@@ -166,7 +175,30 @@
// and that's all folks!
}
-
+
+ @SuppressWarnings("unlikely-arg-type")
+ public void testUntypedMap() throws Exception
+ {
+ // to get "untyped" default map-to-map, pass Object.class
+ String JSON = "{ \"foo\" : \"bar\", \"crazy\" : true, \"null\" : null }";
+
+ // Not a guaranteed cast theoretically, but will work:
+ @SuppressWarnings("unchecked")
+ Map<String,Object> result = (Map<String,Object>)MAPPER.readValue(JSON, Object.class);
+ assertNotNull(result);
+ assertTrue(result instanceof Map<?,?>);
+
+ assertEquals(3, result.size());
+
+ assertEquals("bar", result.get("foo"));
+ assertEquals(Boolean.TRUE, result.get("crazy"));
+ assertNull(result.get("null"));
+
+ // Plus, non existing:
+ assertNull(result.get("bar"));
+ assertNull(result.get(3));
+ }
+
public void testNestedUntypes() throws IOException
{
// 05-Apr-2014, tatu: Odd failures if using shared mapper; so work around:
@@ -223,6 +255,62 @@
assertEquals(Integer.valueOf(13), value);
}
+ // Test that exercises non-vanilla variant, with just one simple custom deserializer
+ public void testNonVanilla() throws IOException
+ {
+ SimpleModule m = new SimpleModule("test-module");
+ m.addDeserializer(String.class, new UCStringDeserializer());
+ final ObjectMapper mapper = new ObjectMapper()
+ .registerModule(m);
+
+ // Also: since this is now non-vanilla variant, try more alternatives
+ List<?> l = (List<?>) mapper.readValue("[ true, false, 7, 0.5, \"foo\"]", Object.class);
+ assertEquals(5, l.size());
+ assertEquals(Boolean.TRUE, l.get(0));
+ assertEquals(Boolean.FALSE, l.get(1));
+ assertEquals(Integer.valueOf(7), l.get(2));
+ assertEquals(Double.valueOf(0.5), l.get(3));
+ assertEquals("FOO", l.get(4));
+
+ l = (List<?>) mapper.readValue("[ {}, [] ]", Object.class);
+ assertEquals(2, l.size());
+ assertTrue(l.get(0) instanceof Map<?,?>);
+ assertTrue(l.get(1) instanceof List<?>);
+
+ ObjectReader rDefault = mapper.readerFor(WrappedPolymorphicUntyped.class);
+ ObjectReader rAlt = rDefault
+ .with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS,
+ DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);
+ WrappedPolymorphicUntyped w;
+
+ w = rDefault.readValue(aposToQuotes("{'value':10}"));
+ assertEquals(Integer.valueOf(10), w.value);
+ w = rAlt.readValue(aposToQuotes("{'value':10}"));
+ assertEquals(BigInteger.TEN, w.value);
+
+ w = rDefault.readValue(aposToQuotes("{'value':5.0}"));
+ assertEquals(Double.valueOf(5.0), w.value);
+ w = rAlt.readValue(aposToQuotes("{'value':5.0}"));
+ assertEquals(new BigDecimal("5.0"), w.value);
+
+ StringBuilder sb = new StringBuilder(100).append("[0");
+ for (int i = 1; i < 100; ++i) {
+ sb.append(", ").append(i);
+ }
+ sb.append("]");
+ final String INT_ARRAY_JSON = sb.toString();
+
+ // First read as-is, no type wrapping
+ Object ob = mapper.readerFor(Object.class)
+ .with(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)
+ .readValue(INT_ARRAY_JSON);
+ assertTrue(ob instanceof Object[]);
+ Object[] obs = (Object[]) ob;
+ for (int i = 0; i < 100; ++i) {
+ assertEquals(Integer.valueOf(i), obs[i]);
+ }
+ }
+
public void testUntypedWithListDeser() throws IOException
{
SimpleModule m = new SimpleModule("test-module");
@@ -243,7 +331,7 @@
public void testUntypedWithMapDeser() throws IOException
{
SimpleModule m = new SimpleModule("test-module");
- m.addDeserializer(Map.class, new MapDeserializer());
+ m.addDeserializer(Map.class, new YMapDeserializer());
final ObjectMapper mapper = new ObjectMapper()
.registerModule(m);
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestDefaultForUtilCollections1868.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/UtilCollectionsTypesTest.java
similarity index 62%
rename from src/test/java/com/fasterxml/jackson/failing/TestDefaultForUtilCollections1868.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/UtilCollectionsTypesTest.java
index 4dc9cd1..79d91d8 100644
--- a/src/test/java/com/fasterxml/jackson/failing/TestDefaultForUtilCollections1868.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/UtilCollectionsTypesTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.deser.jdk;
import java.util.*;
@@ -6,8 +6,8 @@
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
-// Unit tests for [databind#1868], related
-public class TestDefaultForUtilCollections1868 extends BaseMapTest
+// Unit tests for [databind#1868], [databind#1880], [databind#2265]
+public class UtilCollectionsTypesTest extends BaseMapTest
{
private final ObjectMapper DEFAULT_MAPPER = new ObjectMapper();
{
@@ -60,6 +60,21 @@
_verifyCollection(Collections.unmodifiableList(Arrays.asList("first", "second")));
}
+ // [databind#2265]
+ public void testUnmodifiableListFromLinkedList() throws Exception {
+ final List<String> input = new LinkedList<>();
+ input.add("first");
+ input.add("second");
+
+ // Can't use simple "_verifyCollection" as type may change; instead use
+ // bit more flexible check:
+ Collection<?> act = _writeReadCollection(Collections.unmodifiableList(input));
+ assertEquals(input, act);
+
+ // and this check may be bit fragile (may need to revisit), but is good enough for now:
+ assertEquals(Collections.unmodifiableList(new ArrayList<>(input)).getClass(), act.getClass());
+ }
+
public void testUnmodifiableSet() throws Exception
{
Set<String> input = new LinkedHashSet<>(Arrays.asList("first", "second"));
@@ -76,19 +91,37 @@
/*
/**********************************************************
+ /* Unit tests, other
+ /**********************************************************
+ */
+
+ public void testArraysAsList() throws Exception
+ {
+ // Here there are no semantics to preserve, so simply check that
+ // contents remain the same
+ List<String> input = Arrays.asList("a", "bc", "def");
+ String json = DEFAULT_MAPPER.writeValueAsString(input);
+ List<?> result = DEFAULT_MAPPER.readValue(json, List.class);
+ assertEquals(input, result);
+ }
+
+ /*
+ /**********************************************************
/* Helper methods
/**********************************************************
*/
- protected void _verifyCollection(Collection<?> exp) throws Exception
- {
- String json = DEFAULT_MAPPER.writeValueAsString(exp);
- Collection<?> act = DEFAULT_MAPPER.readValue(json, Collection.class);
-
+ protected void _verifyCollection(Collection<?> exp) throws Exception {
+ Collection<?> act = _writeReadCollection(exp);
assertEquals(exp, act);
assertEquals(exp.getClass(), act.getClass());
}
+ protected Collection<?> _writeReadCollection(Collection<?> input) throws Exception {
+ final String json = DEFAULT_MAPPER.writeValueAsString(input);
+ return DEFAULT_MAPPER.readValue(json, Collection.class);
+ }
+
protected void _verifyMap(Map<?,?> exp) throws Exception
{
String json = DEFAULT_MAPPER.writeValueAsString(exp);
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/ArrayMergeTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/ArrayMergeTest.java
new file mode 100644
index 0000000..908ddc5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/ArrayMergeTest.java
@@ -0,0 +1,161 @@
+package com.fasterxml.jackson.databind.deser.merge;
+
+import org.junit.Assert;
+
+import com.fasterxml.jackson.annotation.JsonMerge;
+import com.fasterxml.jackson.annotation.OptBoolean;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import com.fasterxml.jackson.databind.*;
+
+public class ArrayMergeTest extends BaseMapTest
+{
+ static class MergedX<T>
+ {
+ @JsonMerge(OptBoolean.TRUE)
+ public T value;
+
+ public MergedX(T v) { value = v; }
+ protected MergedX() { }
+ }
+
+ /*
+ /********************************************************
+ /* Test methods
+ /********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper()
+ // 26-Oct-2016, tatu: Make sure we'll report merge problems by default
+ .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)
+ ;
+
+ public void testObjectArrayMerging() throws Exception
+ {
+ MergedX<Object[]> input = new MergedX<Object[]>(new Object[] {
+ "foo"
+ });
+ final JavaType type = MAPPER.getTypeFactory().constructType(new TypeReference<MergedX<Object[]>>() {});
+ MergedX<Object[]> result = MAPPER.readerFor(type)
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':['bar']}"));
+ assertSame(input, result);
+ assertEquals(2, result.value.length);
+ assertEquals("foo", result.value[0]);
+ assertEquals("bar", result.value[1]);
+
+ // and with one trick
+ result = MAPPER.readerFor(type)
+ .with(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':'zap'}"));
+ assertSame(input, result);
+ assertEquals(3, result.value.length);
+ assertEquals("foo", result.value[0]);
+ assertEquals("bar", result.value[1]);
+ assertEquals("zap", result.value[2]);
+ }
+
+ public void testStringArrayMerging() throws Exception
+ {
+ MergedX<String[]> input = new MergedX<String[]>(new String[] { "foo" });
+ MergedX<String[]> result = MAPPER
+ .readerFor(new TypeReference<MergedX<String[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':['bar']}"));
+ assertSame(input, result);
+ assertEquals(2, result.value.length);
+ assertEquals("foo", result.value[0]);
+ assertEquals("bar", result.value[1]);
+ }
+
+ public void testBooleanArrayMerging() throws Exception
+ {
+ MergedX<boolean[]> input = new MergedX<boolean[]>(new boolean[] { true, false });
+ MergedX<boolean[]> result = MAPPER
+ .readerFor(new TypeReference<MergedX<boolean[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':[true]}"));
+ assertSame(input, result);
+ assertEquals(3, result.value.length);
+ Assert.assertArrayEquals(new boolean[] { true, false, true }, result.value);
+ }
+
+ public void testByteArrayMerging() throws Exception
+ {
+ MergedX<byte[]> input = new MergedX<byte[]>(new byte[] { 1, 2 });
+ MergedX<byte[]> result = MAPPER
+ .readerFor(new TypeReference<MergedX<byte[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':[4, 6.0, null]}"));
+ assertSame(input, result);
+ assertEquals(5, result.value.length);
+ Assert.assertArrayEquals(new byte[] { 1, 2, 4, 6, 0 }, result.value);
+ }
+
+ public void testShortArrayMerging() throws Exception
+ {
+ MergedX<short[]> input = new MergedX<short[]>(new short[] { 1, 2 });
+ MergedX<short[]> result = MAPPER
+ .readerFor(new TypeReference<MergedX<short[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':[4, 6]}"));
+ assertSame(input, result);
+ assertEquals(4, result.value.length);
+ Assert.assertArrayEquals(new short[] { 1, 2, 4, 6 }, result.value);
+ }
+
+ public void testCharArrayMerging() throws Exception
+ {
+ MergedX<char[]> input = new MergedX<char[]>(new char[] { 'a', 'b' });
+ MergedX<char[]> result = MAPPER
+ .readerFor(new TypeReference<MergedX<char[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':['c']}"));
+ assertSame(input, result);
+ Assert.assertArrayEquals(new char[] { 'a', 'b', 'c' }, result.value);
+
+ // also some variation
+ input = new MergedX<char[]>(new char[] { });
+ result = MAPPER
+ .readerFor(new TypeReference<MergedX<char[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':['c']}"));
+ assertSame(input, result);
+ Assert.assertArrayEquals(new char[] { 'c' }, result.value);
+ }
+
+ public void testIntArrayMerging() throws Exception
+ {
+ MergedX<int[]> input = new MergedX<int[]>(new int[] { 1, 2 });
+ MergedX<int[]> result = MAPPER
+ .readerFor(new TypeReference<MergedX<int[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':[4, 6]}"));
+ assertSame(input, result);
+ assertEquals(4, result.value.length);
+ Assert.assertArrayEquals(new int[] { 1, 2, 4, 6 }, result.value);
+
+ // also some variation
+ input = new MergedX<int[]>(new int[] { 3, 4, 6 });
+ result = MAPPER
+ .readerFor(new TypeReference<MergedX<int[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':[ ]}"));
+ assertSame(input, result);
+ Assert.assertArrayEquals(new int[] { 3, 4, 6 }, result.value);
+ }
+
+ public void testLongArrayMerging() throws Exception
+ {
+ MergedX<long[]> input = new MergedX<long[]>(new long[] { 1, 2 });
+ MergedX<long[]> result = MAPPER
+ .readerFor(new TypeReference<MergedX<long[]>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':[4, 6]}"));
+ assertSame(input, result);
+ assertEquals(4, result.value.length);
+ Assert.assertArrayEquals(new long[] { 1, 2, 4, 6 }, result.value);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/CollectionMergeTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/CollectionMergeTest.java
new file mode 100644
index 0000000..3305d37
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/CollectionMergeTest.java
@@ -0,0 +1,102 @@
+package com.fasterxml.jackson.databind.deser.merge;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonMerge;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class CollectionMergeTest extends BaseMapTest
+{
+ static class CollectionWrapper {
+ @JsonMerge
+ public Collection<String> bag = new TreeSet<String>();
+ {
+ bag.add("a");
+ }
+ }
+
+ static class MergedList
+ {
+ @JsonMerge
+ public List<String> values = new ArrayList<>();
+ {
+ values.add("a");
+ }
+ }
+
+ static class MergedEnumSet
+ {
+ @JsonMerge
+ public EnumSet<ABC> abc = EnumSet.of(ABC.B);
+ }
+
+ static class MergedX<T>
+ {
+ @JsonMerge
+ T value;
+
+ public MergedX(T v) { value = v; }
+ protected MergedX() { }
+
+ public void setValue(T v) { value = v; }
+ }
+
+ /*
+ /********************************************************
+ /* Test methods
+ /********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper()
+ // 26-Oct-2016, tatu: Make sure we'll report merge problems by default
+ .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)
+ ;
+
+ public void testCollectionMerging() throws Exception
+ {
+ CollectionWrapper w = MAPPER.readValue(aposToQuotes("{'bag':['b']}"), CollectionWrapper.class);
+ assertEquals(2, w.bag.size());
+ assertTrue(w.bag.contains("a"));
+ assertTrue(w.bag.contains("b"));
+ }
+
+ public void testListMerging() throws Exception
+ {
+ MergedList w = MAPPER.readValue(aposToQuotes("{'values':['x']}"), MergedList.class);
+ assertEquals(2, w.values.size());
+ assertTrue(w.values.contains("a"));
+ assertTrue(w.values.contains("x"));
+ }
+
+ // Test that uses generic type
+ public void testGenericListMerging() throws Exception
+ {
+ Collection<String> l = new ArrayList<>();
+ l.add("foo");
+ MergedX<Collection<String>> input = new MergedX<Collection<String>>(l);
+
+ MergedX<Collection<String>> result = MAPPER
+ .readerFor(new TypeReference<MergedX<Collection<String>>>() {})
+ .withValueToUpdate(input)
+ .readValue(aposToQuotes("{'value':['bar']}"));
+ assertSame(input, result);
+ assertEquals(2, result.value.size());
+ Iterator<String> it = result.value.iterator();
+ assertEquals("foo", it.next());
+ assertEquals("bar", it.next());
+ }
+
+ public void testEnumSetMerging() throws Exception
+ {
+ MergedEnumSet result = MAPPER.readValue(aposToQuotes("{'abc':['A']}"), MergedEnumSet.class);
+ assertEquals(2, result.abc.size());
+ assertTrue(result.abc.contains(ABC.B)); // original
+ assertTrue(result.abc.contains(ABC.A)); // added
+ }
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/MapMerge1844Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/MapMerge1844Test.java
new file mode 100644
index 0000000..e5fd75d
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/MapMerge1844Test.java
@@ -0,0 +1,69 @@
+package com.fasterxml.jackson.databind.deser.merge;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+// for [databind#1844]
+public class MapMerge1844Test extends BaseMapTest
+{
+ static class TestMap1844 {
+ public Map<String, Integer> getMapStringInteger() {
+ return mapStringInteger;
+ }
+
+ @JsonProperty("key1")
+ public void setMapStringInteger(Map<String, Integer> mapStringInteger) {
+ this.mapStringInteger = mapStringInteger;
+ }
+
+ public Map<Integer, Integer> getMapIntegerInteger() {
+ return mapIntegerInteger;
+ }
+
+ @JsonProperty("key2")
+ public void setMapIntegerInteger(Map<Integer, Integer> mapIntegerInteger) {
+ this.mapIntegerInteger = mapIntegerInteger;
+ }
+
+ private Map<String, Integer> mapStringInteger = new LinkedHashMap<>();
+
+ private Map<Integer, Integer> mapIntegerInteger = new LinkedHashMap<>();
+ }
+
+ // for [databind#1844]
+ public void testMap1844() throws Exception
+ {
+ final ObjectMapper mapper = newObjectMapper();
+ mapper.setDefaultMergeable(true);
+
+ final String f1 = aposToQuotes(
+"{ 'key1' : {\n"
++" '1': 1, '2': 2, '3': 3\n"
++"}, 'key2': {\n"
++" '1': 1, '2': 2, '3': 3\n"
++"} }"
+);
+ final String f2 = aposToQuotes(
+"{ 'key1' : {\n"
++" '1': 2, '2': 3, '4': 5\n"
++"}, 'key2': {\n"
++" '1': 2, '2': 3, '4': 5\n"
++"} }"
+);
+ TestMap1844 testMap = mapper.readerFor(TestMap1844.class).readValue(f1);
+ testMap = mapper.readerForUpdating(testMap).readValue(f2);
+
+ assertEquals(Integer.valueOf(2), testMap.getMapStringInteger().get("1"));
+ assertEquals(Integer.valueOf(3), testMap.getMapStringInteger().get("2"));
+ assertEquals(Integer.valueOf(3), testMap.getMapStringInteger().get("3"));
+ assertEquals(Integer.valueOf(5), testMap.getMapStringInteger().get("4"));
+
+ assertEquals(Integer.valueOf(2), testMap.getMapIntegerInteger().get(1));
+ assertEquals(Integer.valueOf(3), testMap.getMapIntegerInteger().get(2));
+ assertEquals(Integer.valueOf(3), testMap.getMapIntegerInteger().get(3));
+ assertEquals(Integer.valueOf(5), testMap.getMapIntegerInteger().get(4));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/MapMergeTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/MapMergeTest.java
new file mode 100644
index 0000000..647d934
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/MapMergeTest.java
@@ -0,0 +1,215 @@
+package com.fasterxml.jackson.databind.deser.merge;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonMerge;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.databind.*;
+
+public class MapMergeTest extends BaseMapTest
+{
+ static class MergedMap
+ {
+ @JsonMerge
+ public Map<String,Object> values;
+
+ protected MergedMap() {
+ values = new LinkedHashMap<>();
+ values.put("a", "x");
+ }
+
+ public MergedMap(String a, String b) {
+ values = new LinkedHashMap<>();
+ values.put(a, b);
+ }
+
+ public MergedMap(Map<String,Object> src) {
+ values = src;
+ }
+ }
+
+ static class MergedIntMap
+ {
+ @JsonMerge
+ public Map<Integer,Object> values;
+
+ protected MergedIntMap() {
+ values = new LinkedHashMap<>();
+ values.put(Integer.valueOf(13), "a");
+ }
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, Map merging
+ /********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper()
+ // 26-Oct-2016, tatu: Make sure we'll report merge problems by default
+ .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)
+ ;
+
+ private final ObjectMapper MAPPER_SKIP_NULLS = newObjectMapper()
+ .setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP));
+ ;
+
+ public void testShallowMapMerging() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':{'c':'y','d':null}}");
+ MergedMap v = MAPPER.readValue(JSON, MergedMap.class);
+ assertEquals(3, v.values.size());
+ assertEquals("y", v.values.get("c"));
+ assertEquals("x", v.values.get("a"));
+ assertNull(v.values.get("d"));
+
+ // but also, skip nulls
+ v = MAPPER_SKIP_NULLS.readValue(JSON, MergedMap.class);
+ assertEquals(2, v.values.size());
+ assertEquals("y", v.values.get("c"));
+ assertEquals("x", v.values.get("a"));
+ }
+
+ public void testShallowNonStringMerging() throws Exception
+ {
+ final String JSON = aposToQuotes("{'values':{'72':'b','666':null}}");
+ MergedIntMap v = MAPPER.readValue(JSON , MergedIntMap.class);
+ assertEquals(3, v.values.size());
+ assertEquals("a", v.values.get(Integer.valueOf(13)));
+ assertEquals("b", v.values.get(Integer.valueOf(72)));
+ assertNull(v.values.get(Integer.valueOf(666)));
+
+ v = MAPPER_SKIP_NULLS.readValue(JSON , MergedIntMap.class);
+ assertEquals(2, v.values.size());
+ assertEquals("a", v.values.get(Integer.valueOf(13)));
+ assertEquals("b", v.values.get(Integer.valueOf(72)));
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testDeeperMapMerging() throws Exception
+ {
+ // first, create base Map
+ MergedMap base = new MergedMap("name", "foobar");
+ Map<String,Object> props = new LinkedHashMap<>();
+ props.put("default", "yes");
+ props.put("x", "abc");
+ Map<String,Object> innerProps = new LinkedHashMap<>();
+ innerProps.put("z", Integer.valueOf(13));
+ props.put("extra", innerProps);
+ base.values.put("props", props);
+
+ // to be update
+ MergedMap v = MAPPER.readerForUpdating(base)
+ .readValue(aposToQuotes("{'values':{'props':{'x':'xyz','y' : '...','extra':{ 'ab' : true}}}}"));
+ assertEquals(2, v.values.size());
+ assertEquals("foobar", v.values.get("name"));
+ assertNotNull(v.values.get("props"));
+ props = (Map<String,Object>) v.values.get("props");
+ assertEquals(4, props.size());
+ assertEquals("yes", props.get("default"));
+ assertEquals("xyz", props.get("x"));
+ assertEquals("...", props.get("y"));
+ assertNotNull(props.get("extra"));
+ innerProps = (Map<String,Object>) props.get("extra");
+ assertEquals(2, innerProps.size());
+ assertEquals(Integer.valueOf(13), innerProps.get("z"));
+ assertEquals(Boolean.TRUE, innerProps.get("ab"));
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testMapMergingWithArray() throws Exception
+ {
+ // first, create base Map
+ MergedMap base = new MergedMap("name", "foobar");
+ Map<String,Object> props = new LinkedHashMap<>();
+ List<String> names = new ArrayList<>();
+ names.add("foo");
+ props.put("names", names);
+ base.values.put("props", props);
+ props.put("extra", "misc");
+
+ // to be update
+ MergedMap v = MAPPER.readerForUpdating(base)
+ .readValue(aposToQuotes("{'values':{'props':{'names': [ 'bar' ] }}}"));
+ assertEquals(2, v.values.size());
+ assertEquals("foobar", v.values.get("name"));
+ assertNotNull(v.values.get("props"));
+ props = (Map<String,Object>) v.values.get("props");
+ assertEquals(2, props.size());
+ assertEquals("misc", props.get("extra"));
+ assertNotNull(props.get("names"));
+ names = (List<String>) props.get("names");
+ assertEquals(2, names.size());
+ assertEquals("foo", names.get(0));
+ assertEquals("bar", names.get(1));
+ }
+
+ /*
+ /********************************************************
+ /* Forcing shallow merge of root Maps:
+ /********************************************************
+ */
+
+ public void testDefaultDeepMapMerge() throws Exception
+ {
+ // First: deep merge should be enabled by default
+ HashMap<String,Object> input = new HashMap<>();
+ input.put("list", new ArrayList<>(Arrays.asList("a")));
+
+ Map<?,?> resultMap = MAPPER.readerForUpdating(input)
+ .readValue(aposToQuotes("{'list':['b']}"));
+
+ List<?> resultList = (List<?>) resultMap.get("list");
+ assertEquals(Arrays.asList("a", "b"), resultList);
+ }
+
+ public void testDisabledMergeViaGlobal() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper();
+ // disable merging, globally; does not affect main level
+ mapper.setDefaultMergeable(false);
+
+ HashMap<String,Object> input = new HashMap<>();
+ input.put("list", new ArrayList<>(Arrays.asList("a")));
+
+ Map<?,?> resultMap = mapper.readerForUpdating(input)
+ .readValue(aposToQuotes("{'list':['b']}"));
+
+ List<?> resultList = (List<?>) resultMap.get("list");
+
+ assertEquals(Arrays.asList("b"), resultList);
+ }
+
+ public void testDisabledMergeByType() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper();
+ // disable merging for "untyped", that is, `Object.class`
+ mapper.configOverride(Object.class)
+ .setMergeable(false);
+
+ HashMap<String,Object> input = new HashMap<>();
+ input.put("list", new ArrayList<>(Arrays.asList("a")));
+
+ Map<?,?> resultMap = mapper.readerForUpdating(input)
+ .readValue(aposToQuotes("{'list':['b']}"));
+ List<?> resultList = (List<?>) resultMap.get("list");
+ assertEquals(Arrays.asList("b"), resultList);
+
+ // and for extra points, disable by default but ENABLE for type,
+ // which should once again allow merging
+
+ mapper = newObjectMapper();
+ mapper.setDefaultMergeable(false);
+ mapper.configOverride(Object.class)
+ .setMergeable(true);
+
+ input = new HashMap<>();
+ input.put("list", new ArrayList<>(Arrays.asList("x")));
+
+ resultMap = mapper.readerForUpdating(input)
+ .readValue(aposToQuotes("{'list':['y']}"));
+ resultList = (List<?>) resultMap.get("list");
+ assertEquals(Arrays.asList("x", "y"), resultList);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/MergeWithNullTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/MergeWithNullTest.java
new file mode 100644
index 0000000..bc80ad3
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/MergeWithNullTest.java
@@ -0,0 +1,133 @@
+package com.fasterxml.jackson.databind.deser.merge;
+
+import com.fasterxml.jackson.annotation.JsonMerge;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class MergeWithNullTest extends BaseMapTest
+{
+ static class ConfigDefault {
+ @JsonMerge
+ public AB loc = new AB(1, 2);
+
+ protected ConfigDefault() { }
+ public ConfigDefault(int a, int b) {
+ loc = new AB(a, b);
+ }
+ }
+
+ static class ConfigSkipNull {
+ @JsonMerge
+ @JsonSetter(nulls=Nulls.SKIP)
+ public AB loc = new AB(1, 2);
+
+ protected ConfigSkipNull() { }
+ public ConfigSkipNull(int a, int b) {
+ loc = new AB(a, b);
+ }
+ }
+
+ static class ConfigAllowNullOverwrite {
+ @JsonMerge
+ @JsonSetter(nulls=Nulls.SET)
+ public AB loc = new AB(1, 2);
+
+ protected ConfigAllowNullOverwrite() { }
+ public ConfigAllowNullOverwrite(int a, int b) {
+ loc = new AB(a, b);
+ }
+ }
+
+ // another variant where all we got is a getter
+ static class NoSetterConfig {
+ AB _value = new AB(2, 3);
+
+ @JsonMerge
+ public AB getValue() { return _value; }
+ }
+
+ static class AB {
+ public int a;
+ public int b;
+
+ protected AB() { }
+ public AB(int a0, int b0) {
+ a = a0;
+ b = b0;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper()
+ // 26-Oct-2016, tatu: Make sure we'll report merge problems by default
+ .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)
+ ;
+
+ public void testBeanMergingWithNullDefault() throws Exception
+ {
+ // By default `null` should simply overwrite value
+ ConfigDefault config = MAPPER.readerForUpdating(new ConfigDefault(5, 7))
+ .readValue(aposToQuotes("{'loc':null}"));
+ assertNotNull(config);
+ assertNull(config.loc);
+
+ // but it should be possible to override setting to, say, skip
+
+ // First: via specific type override
+ // important! We'll specify for value type to be merged
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configOverride(AB.class)
+ .setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SKIP));
+ config = mapper.readerForUpdating(new ConfigDefault(137, -3))
+ .readValue(aposToQuotes("{'loc':null}"));
+ assertNotNull(config.loc);
+ assertEquals(137, config.loc.a);
+ assertEquals(-3, config.loc.b);
+
+ // Second: by global defaults
+ mapper = newObjectMapper();
+ mapper.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SKIP));
+ config = mapper.readerForUpdating(new ConfigDefault(12, 34))
+ .readValue(aposToQuotes("{'loc':null}"));
+ assertNotNull(config.loc);
+ assertEquals(12, config.loc.a);
+ assertEquals(34, config.loc.b);
+ }
+
+ public void testBeanMergingWithNullSkip() throws Exception
+ {
+ ConfigSkipNull config = MAPPER.readerForUpdating(new ConfigSkipNull(5, 7))
+ .readValue(aposToQuotes("{'loc':null}"));
+ assertNotNull(config);
+ assertNotNull(config.loc);
+ assertEquals(5, config.loc.a);
+ assertEquals(7, config.loc.b);
+ }
+
+ public void testBeanMergingWithNullSet() throws Exception
+ {
+ ConfigAllowNullOverwrite config = MAPPER.readerForUpdating(new ConfigAllowNullOverwrite(5, 7))
+ .readValue(aposToQuotes("{'loc':null}"));
+ assertNotNull(config);
+ assertNull(config.loc);
+ }
+
+ public void testSetterlessMergingWithNull() throws Exception
+ {
+ NoSetterConfig input = new NoSetterConfig();
+ NoSetterConfig result = MAPPER.readerForUpdating(input)
+ .readValue(aposToQuotes("{'value':null}"));
+ assertNotNull(result.getValue());
+ assertEquals(2, result.getValue().a);
+ assertEquals(3, result.getValue().b);
+ assertSame(input, result);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/NodeMergeTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/NodeMergeTest.java
new file mode 100644
index 0000000..435ecdb
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/NodeMergeTest.java
@@ -0,0 +1,117 @@
+package com.fasterxml.jackson.databind.deser.merge;
+
+import com.fasterxml.jackson.annotation.JsonMerge;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+public class NodeMergeTest extends BaseMapTest
+{
+ private final static ObjectMapper MAPPER = newObjectMapper()
+ // 26-Oct-2016, tatu: Make sure we'll report merge problems by default
+ .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)
+ ;
+
+ static class ObjectNodeWrapper {
+ @JsonMerge
+ public ObjectNode props = MAPPER.createObjectNode();
+ {
+ props.put("default", "enabled");
+ }
+ }
+
+ static class ArrayNodeWrapper {
+ @JsonMerge
+ public ArrayNode list = MAPPER.createArrayNode();
+ {
+ list.add(123);
+ }
+ }
+
+ /*
+ /********************************************************
+ /* Test methods
+ /********************************************************
+ */
+
+ public void testObjectNodeUpdateValue() throws Exception
+ {
+ ObjectNode base = MAPPER.createObjectNode();
+ base.put("first", "foo");
+ assertSame(base,
+ MAPPER.readerForUpdating(base)
+ .readValue(aposToQuotes("{'second':'bar', 'third':5, 'fourth':true}")));
+ assertEquals(4, base.size());
+ assertEquals("bar", base.path("second").asText());
+ assertEquals("foo", base.path("first").asText());
+ assertEquals(5, base.path("third").asInt());
+ assertTrue(base.path("fourth").asBoolean());
+ }
+
+ public void testObjectNodeMerge() throws Exception
+ {
+ ObjectNodeWrapper w = MAPPER.readValue(aposToQuotes("{'props':{'stuff':'xyz'}}"),
+ ObjectNodeWrapper.class);
+ assertEquals(2, w.props.size());
+ assertEquals("enabled", w.props.path("default").asText());
+ assertEquals("xyz", w.props.path("stuff").asText());
+ }
+
+ public void testObjectDeepUpdate() throws Exception
+ {
+ ObjectNode base = MAPPER.createObjectNode();
+ ObjectNode props = base.putObject("props");
+ props.put("base", 123);
+ props.put("value", 456);
+ ArrayNode a = props.putArray("array");
+ a.add(true);
+ base.putNull("misc");
+ assertSame(base,
+ MAPPER.readerForUpdating(base)
+ .readValue(aposToQuotes(
+ "{'props':{'value':true, 'extra':25.5, 'array' : [ 3 ]}}")));
+ assertEquals(2, base.size());
+ ObjectNode resultProps = (ObjectNode) base.get("props");
+ assertEquals(4, resultProps.size());
+
+ assertEquals(123, resultProps.path("base").asInt());
+ assertTrue(resultProps.path("value").asBoolean());
+ assertEquals(25.5, resultProps.path("extra").asDouble());
+ JsonNode n = resultProps.get("array");
+ assertEquals(ArrayNode.class, n.getClass());
+ assertEquals(2, n.size());
+ assertEquals(3, n.get(1).asInt());
+ }
+
+ public void testArrayNodeUpdateValue() throws Exception
+ {
+ ArrayNode base = MAPPER.createArrayNode();
+ base.add("first");
+ assertSame(base,
+ MAPPER.readerForUpdating(base)
+ .readValue(aposToQuotes("['second',false,null]")));
+ assertEquals(4, base.size());
+ assertEquals("first", base.path(0).asText());
+ assertEquals("second", base.path(1).asText());
+ assertFalse(base.path(2).asBoolean());
+ assertTrue(base.path(3).isNull());
+ }
+
+ public void testArrayNodeMerge() throws Exception
+ {
+ ArrayNodeWrapper w = MAPPER.readValue(aposToQuotes("{'list':[456,true,{}, [], 'foo']}"),
+ ArrayNodeWrapper.class);
+ assertEquals(6, w.list.size());
+ assertEquals(123, w.list.get(0).asInt());
+ assertEquals(456, w.list.get(1).asInt());
+ assertTrue(w.list.get(2).asBoolean());
+ JsonNode n = w.list.get(3);
+ assertTrue(n.isObject());
+ assertEquals(0, n.size());
+ n = w.list.get(4);
+ assertTrue(n.isArray());
+ assertEquals(0, n.size());
+ assertEquals("foo", w.list.get(5).asText());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/PropertyMergeTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/PropertyMergeTest.java
new file mode 100644
index 0000000..826d465
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/PropertyMergeTest.java
@@ -0,0 +1,229 @@
+package com.fasterxml.jackson.databind.deser.merge;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat.Shape;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+
+/**
+ * Tests to make sure that the new "merging" property of
+ * <code>JsonSetter</code> annotation works as expected.
+ *
+ * @since 2.9
+ */
+@SuppressWarnings("serial")
+public class PropertyMergeTest extends BaseMapTest
+{
+ static class Config {
+ @JsonMerge
+ public AB loc = new AB(1, 2);
+
+ protected Config() { }
+ public Config(int a, int b) {
+ loc = new AB(a, b);
+ }
+ }
+
+ static class NonMergeConfig {
+ public AB loc = new AB(1, 2);
+ }
+
+ // another variant where all we got is a getter
+ static class NoSetterConfig {
+ AB _value = new AB(1, 2);
+
+ @JsonMerge
+ public AB getValue() { return _value; }
+ }
+
+ static class AB {
+ public int a;
+ public int b;
+
+ protected AB() { }
+ public AB(int a0, int b0) {
+ a = a0;
+ b = b0;
+ }
+ }
+
+ @JsonPropertyOrder(alphabetic=true)
+ @JsonFormat(shape=Shape.ARRAY)
+ static class ABAsArray {
+ public int a;
+ public int b;
+ }
+
+ // Custom type that would be deserializable by default
+ static class StringReference extends AtomicReference<String> {
+ public StringReference(String str) {
+ set(str);
+ }
+ }
+
+ static class MergedReference
+ {
+ @JsonMerge
+ public StringReference value = new StringReference("default");
+ }
+
+ static class MergedX<T>
+ {
+ @JsonMerge
+ public T value;
+
+ public MergedX(T v) { value = v; }
+ protected MergedX() { }
+ }
+
+ // // // Classes with invalid merge definition(s)
+
+ static class CantMergeInts {
+ @JsonMerge
+ public int value;
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, POJO merging
+ /********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper()
+ // 26-Oct-2016, tatu: Make sure we'll report merge problems by default
+ .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE)
+ ;
+
+ public void testBeanMergingViaProp() throws Exception
+ {
+ Config config = MAPPER.readValue(aposToQuotes("{'loc':{'b':3}}"), Config.class);
+ assertEquals(1, config.loc.a);
+ assertEquals(3, config.loc.b);
+
+ config = MAPPER.readerForUpdating(new Config(5, 7))
+ .readValue(aposToQuotes("{'loc':{'b':2}}"));
+ assertEquals(5, config.loc.a);
+ assertEquals(2, config.loc.b);
+ }
+
+ public void testBeanMergingViaType() throws Exception
+ {
+ // by default, no merging
+ NonMergeConfig config = MAPPER.readValue(aposToQuotes("{'loc':{'a':3}}"), NonMergeConfig.class);
+ assertEquals(3, config.loc.a);
+ assertEquals(0, config.loc.b); // not passed, nor merge from original
+
+ // but with type-overrides
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configOverride(AB.class).setMergeable(true);
+ config = mapper.readValue(aposToQuotes("{'loc':{'a':3}}"), NonMergeConfig.class);
+ assertEquals(3, config.loc.a);
+ assertEquals(2, config.loc.b); // original, merged
+ }
+
+ public void testBeanMergingViaGlobal() throws Exception
+ {
+ // but with type-overrides
+ ObjectMapper mapper = newObjectMapper()
+ .setDefaultMergeable(true);
+ NonMergeConfig config = mapper.readValue(aposToQuotes("{'loc':{'a':3}}"), NonMergeConfig.class);
+ assertEquals(3, config.loc.a);
+ assertEquals(2, config.loc.b); // original, merged
+
+ // also, test with bigger POJO type; just as smoke test
+ FiveMinuteUser user0 = new FiveMinuteUser("Bob", "Bush", true, FiveMinuteUser.Gender.MALE,
+ new byte[] { 1, 2, 3, 4, 5 });
+ FiveMinuteUser user = mapper.readerFor(FiveMinuteUser.class)
+ .withValueToUpdate(user0)
+ .readValue(aposToQuotes("{'name':{'last':'Brown'}}"));
+ assertEquals("Bob", user.getName().getFirst());
+ assertEquals("Brown", user.getName().getLast());
+ }
+
+ // should even work with no setter
+ public void testBeanMergingWithoutSetter() throws Exception
+ {
+ NoSetterConfig config = MAPPER.readValue(aposToQuotes("{'value':{'b':99}}"),
+ NoSetterConfig.class);
+ assertEquals(99, config._value.b);
+ assertEquals(1, config._value.a);
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, as array
+ /********************************************************
+ */
+
+ public void testBeanAsArrayMerging() throws Exception
+ {
+ ABAsArray input = new ABAsArray();
+ input.a = 4;
+ input.b = 6;
+
+ assertSame(input, MAPPER.readerForUpdating(input)
+ .readValue("[1, 3]"));
+ assertEquals(1, input.a);
+ assertEquals(3, input.b);
+
+ // then with one too few
+ assertSame(input, MAPPER.readerForUpdating(input)
+ .readValue("[9]"));
+ assertEquals(9, input.a);
+ assertEquals(3, input.b);
+
+ // and finally with extra, failing
+ try {
+ MAPPER.readerForUpdating(input)
+ .readValue("[9, 8, 14]");
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "expected at most 2 properties");
+ }
+
+ try {
+ MAPPER.readerForUpdating(input)
+ .readValue("\"blob\"");
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot deserialize");
+ verifyException(e, "from non-Array representation");
+ }
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, reference types
+ /********************************************************
+ */
+
+ public void testReferenceMerging() throws Exception
+ {
+ MergedReference result = MAPPER.readValue(aposToQuotes("{'value':'override'}"),
+ MergedReference.class);
+ assertEquals("override", result.value.get());
+ }
+
+ /*
+ /********************************************************
+ /* Test methods, failure checking
+ /********************************************************
+ */
+
+ public void testInvalidPropertyMerge() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper()
+ .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE);
+
+ try {
+ mapper.readValue("{\"value\":3}", CantMergeInts.class);
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "cannot be merged");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestValueUpdate.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/UpdateValueTest.java
similarity index 75%
rename from src/test/java/com/fasterxml/jackson/databind/creators/TestValueUpdate.java
rename to src/test/java/com/fasterxml/jackson/databind/deser/merge/UpdateValueTest.java
index 759f618..ce38f86 100644
--- a/src/test/java/com/fasterxml/jackson/databind/creators/TestValueUpdate.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/UpdateValueTest.java
@@ -1,11 +1,11 @@
-package com.fasterxml.jackson.databind.creators;
+package com.fasterxml.jackson.databind.deser.merge;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.*;
-public class TestValueUpdate extends BaseMapTest
+public class UpdateValueTest extends BaseMapTest
{
static class Bean
{
@@ -36,7 +36,7 @@
}
}
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newObjectMapper();
// [databind#318] (and Scala module issue #83]
public void testValueUpdateWithCreator() throws Exception
@@ -50,8 +50,11 @@
public void testValueUpdateOther() throws Exception
{
Bean bean = new Bean("abc", "def");
- ObjectReader r = MAPPER.reader().withValueToUpdate(bean);
+ ObjectReader r = MAPPER.readerFor(Bean.class).withValueToUpdate(bean);
// but, changed our minds, no update
r = r.withValueToUpdate(null);
+ // should be safe to read regardless
+ Bean result = r.readValue(aposToQuotes("{'a':'x'}"));
+ assertNotNull(result);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/exc/BasicExceptionTest.java b/src/test/java/com/fasterxml/jackson/databind/exc/BasicExceptionTest.java
new file mode 100644
index 0000000..3c4d2bb
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/BasicExceptionTest.java
@@ -0,0 +1,131 @@
+package com.fasterxml.jackson.databind.exc;
+
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+public class BasicExceptionTest extends BaseMapTest
+{
+ final ObjectMapper MAPPER = new ObjectMapper();
+ final JsonFactory JSON_F = MAPPER.getFactory();
+
+ public void testBadDefinition() throws Exception
+ {
+ JavaType t = TypeFactory.defaultInstance().constructType(String.class);
+ JsonParser p = JSON_F.createParser("[]");
+ InvalidDefinitionException e = new InvalidDefinitionException(p,
+ "Testing", t);
+ assertEquals("Testing", e.getOriginalMessage());
+ assertEquals(String.class, e.getType().getRawClass());
+ assertNull(e.getBeanDescription());
+ assertNull(e.getProperty());
+ assertSame(p, e.getProcessor());
+ p.close();
+
+ // and via factory method:
+ BeanDescription beanDef = MAPPER.getSerializationConfig().introspectClassAnnotations(getClass());
+ e = InvalidDefinitionException.from(p, "Testing",
+ beanDef, (BeanPropertyDefinition) null);
+ assertEquals(beanDef.getType(), e.getType());
+ assertNotNull(e);
+
+ // and the other constructor too
+ JsonGenerator g = JSON_F.createGenerator(new StringWriter());
+ e = new InvalidDefinitionException(p,
+ "Testing", t);
+ assertEquals("Testing", e.getOriginalMessage());
+ assertEquals(String.class, e.getType().getRawClass());
+
+ // and factory
+ e = InvalidDefinitionException.from(g, "Testing",
+ beanDef, (BeanPropertyDefinition) null);
+ assertEquals(beanDef.getType(), e.getType());
+ assertNotNull(e);
+
+ g.close();
+ }
+
+ @SuppressWarnings("deprecation")
+ public void testInvalidFormat() throws Exception
+ {
+ // deprecated methods should still work:
+ InvalidFormatException e = new InvalidFormatException("Testing", Boolean.TRUE,
+ String.class);
+ assertSame(Boolean.TRUE, e.getValue());
+ assertNull(e.getProcessor());
+ assertNotNull(e);
+
+ e = new InvalidFormatException("Testing", JsonLocation.NA,
+ Boolean.TRUE, String.class);
+ assertSame(Boolean.TRUE, e.getValue());
+ assertNull(e.getProcessor());
+ assertNotNull(e);
+ }
+
+ public void testIgnoredProperty() throws Exception
+ {
+ // first just construct valid instance with some variations
+ JsonParser p = JSON_F.createParser("{ }");
+ IgnoredPropertyException e = IgnoredPropertyException.from(p,
+ this, // to get class from
+ "testProp", Collections.<Object>singletonList("x"));
+ assertNotNull(e);
+
+ e = IgnoredPropertyException.from(p,
+ getClass(),
+ "testProp", null);
+ assertNotNull(e);
+ assertNull(e.getKnownPropertyIds());
+ p.close();
+
+ // also, verify failure if null passed for "value"
+ try {
+ IgnoredPropertyException.from(p, null,
+ "testProp", Collections.<Object>singletonList("x"));
+ fail("Should not pass");
+ } catch (NullPointerException e2) {
+ }
+ }
+
+ public void testUnrecognizedProperty() throws Exception
+ {
+ JsonParser p = JSON_F.createParser("{ }");
+ UnrecognizedPropertyException e = UnrecognizedPropertyException.from(p, this,
+ "testProp", Collections.<Object>singletonList("y"));
+ assertNotNull(e);
+ assertEquals(getClass(), e.getReferringClass());
+ Collection<Object> ids = e.getKnownPropertyIds();
+ assertNotNull(ids);
+ assertEquals(1, ids.size());
+ assertTrue(ids.contains("y"));
+
+ e = UnrecognizedPropertyException.from(p, getClass(),
+ "testProp", Collections.<Object>singletonList("y"));
+
+ assertEquals(getClass(), e.getReferringClass());
+ p.close();
+ }
+
+ // [databind#2128]: ensure Location added once and only once
+ public void testLocationAddition() throws Exception
+ {
+ try {
+ /*Map<?,?> map =*/ MAPPER.readValue("{\"value\":\"foo\"}",
+ new TypeReference<Map<ABC, Integer>>() { });
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ String msg = e.getMessage();
+ String[] str = msg.split(" at \\[");
+ if (str.length != 2) {
+ fail("Should only get one 'at [' marker, got "+(str.length-1)+", source: "+msg);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandling.java b/src/test/java/com/fasterxml/jackson/databind/exc/DeserExceptionTypeTest.java
similarity index 61%
rename from src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandling.java
rename to src/test/java/com/fasterxml/jackson/databind/exc/DeserExceptionTypeTest.java
index 00c88ec..3b794c2 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandling.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/DeserExceptionTypeTest.java
@@ -1,34 +1,47 @@
-package com.fasterxml.jackson.databind.deser.exc;
+package com.fasterxml.jackson.databind.exc;
import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
/**
* Unit test for verifying that exceptions are properly handled (caught,
- * re-thrown or wrapped, depending)
- * with Object deserialization.
+ * re-thrown or wrapped, depending) with Object deserialization,
+ * including using concrete subtypes of {@link JsonMappingException}
+ * (or, for low-level parsing, {@link JsonParseException}).
*/
-public class TestExceptionHandling
+public class DeserExceptionTypeTest
extends BaseMapTest
{
static class Bean {
public String propX;
}
+ // Class that has no applicable creators and thus cannot be instantiated;
+ // definition problem
+ static class NoCreatorsBean {
+ public int x;
+
+ // Constructor that is not detectable as Creator
+ public NoCreatorsBean(boolean foo, int foo2) { }
+ }
+
/*
/**********************************************************
/* Test methods
/**********************************************************
*/
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
public void testHandlingOfUnrecognized() throws Exception
{
UnrecognizedPropertyException exc = null;
try {
- new ObjectMapper().readValue("{\"bar\":3}", Bean.class);
+ MAPPER.readValue("{\"bar\":3}", Bean.class);
} catch (UnrecognizedPropertyException e) {
exc = e;
}
@@ -48,12 +61,11 @@
*/
public void testExceptionWithEmpty() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
try {
- Object result = mapper.readValue(" ", Object.class);
+ Object result = MAPPER.readValue(" ", Object.class);
fail("Expected an exception, but got result value: "+result);
} catch (Exception e) {
- verifyException(e, JsonMappingException.class, "No content");
+ verifyException(e, MismatchedInputException.class, "No content");
}
}
@@ -62,12 +74,10 @@
throws Exception
{
BrokenStringReader r = new BrokenStringReader("[ 1, ", "TEST");
- JsonFactory f = new JsonFactory();
- JsonParser jp = f.createParser(r);
- ObjectMapper mapper = new ObjectMapper();
+ JsonParser p = MAPPER.getFactory().createParser(r);
try {
@SuppressWarnings("unused")
- Object ob = mapper.readValue(jp, Object.class);
+ Object ob = MAPPER.readValue(p, Object.class);
fail("Should have gotten an exception");
} catch (IOException e) {
/* For "bona fide" IO problems (due to low-level problem,
@@ -77,30 +87,37 @@
}
}
- public void testExceptionWithEOF()
- throws Exception
+ public void testExceptionWithEOF() throws Exception
{
- StringReader r = new StringReader(" 3");
- JsonFactory f = new JsonFactory();
- JsonParser jp = f.createParser(r);
- ObjectMapper mapper = new ObjectMapper();
+ JsonParser p = MAPPER.getFactory().createParser(" 3");
- Integer I = mapper.readValue(jp, Integer.class);
+ Integer I = MAPPER.readValue(p, Integer.class);
assertEquals(3, I.intValue());
// and then end-of-input...
try {
- I = mapper.readValue(jp, Integer.class);
+ I = MAPPER.readValue(p, Integer.class);
fail("Should have gotten an exception");
} catch (IOException e) {
- verifyException(e, JsonMappingException.class, "No content");
+ verifyException(e, MismatchedInputException.class, "No content");
}
// also: should have no current token after end-of-input
- JsonToken t = jp.getCurrentToken();
+ JsonToken t = p.getCurrentToken();
if (t != null) {
fail("Expected current token to be null after end-of-stream, was: "+t);
}
- jp.close();
+ p.close();
+ }
+
+ // [databind#1414]
+ public void testExceptionForNoCreators() throws Exception
+ {
+ try {
+ NoCreatorsBean b = MAPPER.readValue("{}", NoCreatorsBean.class);
+ fail("Should not succeed, got: "+b);
+ } catch (JsonMappingException e) {
+ verifyException(e, InvalidDefinitionException.class, "no Creators");
+ }
}
/*
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java
similarity index 92%
rename from src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java
index d89bfd6..536a5b4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser.exc;
+package com.fasterxml.jackson.databind.exc;
import java.io.IOException;
import java.util.*;
@@ -10,7 +10,7 @@
/**
* Unit tests for verifying that simple exceptions can be deserialized.
*/
-public class TestExceptionDeserialization
+public class ExceptionDeserializationTest
extends BaseMapTest
{
@SuppressWarnings("serial")
@@ -170,4 +170,15 @@
), IOException.class);
assertNotNull(exc);
}
+
+ // [databind#1842]:
+ public void testNullAsMessage() throws IOException
+ {
+ Exception exc = MAPPER.readValue(aposToQuotes(
+ "{'message':null, 'localizedMessage':null }"
+ ), IOException.class);
+ assertNotNull(exc);
+ assertNull(exc.getMessage());
+ assertNull(exc.getLocalizedMessage());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/exc/ExceptionPathTest.java b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionPathTest.java
similarity index 86%
rename from src/test/java/com/fasterxml/jackson/databind/deser/exc/ExceptionPathTest.java
rename to src/test/java/com/fasterxml/jackson/databind/exc/ExceptionPathTest.java
index c4a87bb..70ee6b6 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/exc/ExceptionPathTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionPathTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser.exc;
+package com.fasterxml.jackson.databind.exc;
import com.fasterxml.jackson.annotation.*;
@@ -38,9 +38,4 @@
reference.toString());
}
}
-
- public static void main(String[] args)
- {
- System.err.println("Int, full: "+Integer.TYPE.getName());
- }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionSerialization.java b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionSerializationTest.java
similarity index 85%
rename from src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionSerialization.java
rename to src/test/java/com/fasterxml/jackson/databind/exc/ExceptionSerializationTest.java
index 07fc70a..fdee7db 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/ExceptionSerializationTest.java
@@ -1,15 +1,16 @@
-package com.fasterxml.jackson.databind.deser.exc;
+package com.fasterxml.jackson.databind.exc;
import java.io.IOException;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
/**
* Unit tests for verifying that simple exceptions can be serialized.
*/
-public class TestExceptionSerialization
+public class ExceptionSerializationTest
extends BaseMapTest
{
@SuppressWarnings("serial")
@@ -42,7 +43,7 @@
*/
private final ObjectMapper MAPPER = new ObjectMapper();
-
+
public void testSimple() throws Exception
{
String TEST = "test exception";
@@ -66,6 +67,16 @@
}
}
+ // to double-check [databind#1413]
+ public void testSimpleOther() throws Exception
+ {
+ JsonParser p = MAPPER.getFactory().createParser("{ }");
+ InvalidFormatException exc = InvalidFormatException.from(p, "Test", getClass(), String.class);
+ String json = MAPPER.writeValueAsString(exc);
+ p.close();
+ assertNotNull(json);
+ }
+
// for [databind#877]
@SuppressWarnings("unchecked")
public void testIgnorals() throws Exception
@@ -109,15 +120,15 @@
MAPPER.readValue( "{ \"val\": \"foo\" }", NoSerdeConstructor.class );
fail("Should not pass");
} catch (JsonMappingException e0) {
- verifyException(e0, "no suitable constructor");
+ verifyException(e0, "cannot deserialize from Object");
e = e0;
}
// but should be able to serialize new exception we got
String json = MAPPER.writeValueAsString(e);
JsonNode root = MAPPER.readTree(json);
String msg = root.path("message").asText();
- String MATCH = "no suitable constructor";
- if (!msg.contains(MATCH)) {
+ String MATCH = "cannot construct instance";
+ if (!msg.toLowerCase().contains(MATCH)) {
fail("Exception should contain '"+MATCH+"', does not: '"+msg+"'");
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/exc/StackTraceElementTest.java b/src/test/java/com/fasterxml/jackson/databind/exc/StackTraceElementTest.java
new file mode 100644
index 0000000..2ed2786
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/StackTraceElementTest.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.databind.exc;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.*;
+
+// for [databind#1794]
+public class StackTraceElementTest extends BaseMapTest
+{
+ public static class ErrorObject {
+
+ public String throwable;
+ public String message;
+
+// @JsonDeserialize(contentUsing = StackTraceElementDeserializer.class)
+ public StackTraceElement[] stackTrace;
+
+ ErrorObject() {}
+
+ public ErrorObject(Throwable throwable) {
+ this.throwable = throwable.getClass().getName();
+ message = throwable.getMessage();
+ stackTrace = throwable.getStackTrace();
+ }
+ }
+
+ // for [databind#1794] where extra `declaringClass` is serialized from private field.
+ public void testCustomStackTraceDeser() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper();
+ mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+
+ String json = mapper
+ .writerWithDefaultPrettyPrinter()
+ .writeValueAsString(new ErrorObject(new Exception("exception message")));
+
+ ErrorObject result = mapper.readValue(json, ErrorObject.class);
+ assertNotNull(result);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandlingWithDefaultDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionHandlingWithDefaultDeserialization.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandlingWithDefaultDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionHandlingWithDefaultDeserialization.java
index 3888722..928b083 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandlingWithDefaultDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionHandlingWithDefaultDeserialization.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser.exc;
+package com.fasterxml.jackson.databind.exc;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.JsonMappingException;
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandlingWithJsonCreatorDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionHandlingWithJsonCreatorDeserialization.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandlingWithJsonCreatorDeserialization.java
rename to src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionHandlingWithJsonCreatorDeserialization.java
index 8065bc4..3797c80 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionHandlingWithJsonCreatorDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionHandlingWithJsonCreatorDeserialization.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser.exc;
+package com.fasterxml.jackson.databind.exc;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionsDuringWriting.java b/src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionsDuringWriting.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionsDuringWriting.java
rename to src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionsDuringWriting.java
index df51963..6c96343 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/exc/TestExceptionsDuringWriting.java
+++ b/src/test/java/com/fasterxml/jackson/databind/exc/TestExceptionsDuringWriting.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser.exc;
+package com.fasterxml.jackson.databind.exc;
import java.io.*;
import java.util.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/ext/TestJava7Types.java b/src/test/java/com/fasterxml/jackson/databind/ext/TestJava7Types.java
index 459978d..b18b8d1 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ext/TestJava7Types.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ext/TestJava7Types.java
@@ -1,12 +1,10 @@
package com.fasterxml.jackson.databind.ext;
-import java.nio.file.FileSystem;
import java.nio.file.Path;
+import java.nio.file.Paths;
import com.fasterxml.jackson.databind.*;
-
-import com.google.common.jimfs.Configuration;
-import com.google.common.jimfs.Jimfs;
+import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
/**
* @since 2.7
@@ -17,17 +15,32 @@
{
ObjectMapper mapper = new ObjectMapper();
- FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
- Path input = fs.getPath("/tmp", "foo.txt");
+ Path input = Paths.get("/tmp", "foo.txt");
String json = mapper.writeValueAsString(input);
assertNotNull(json);
-
+
Path p = mapper.readValue(json, Path.class);
assertNotNull(p);
assertEquals(input.toUri(), p.toUri());
assertEquals(input, p);
- fs.close();
+ }
+
+ // [databind#1688]:
+ public void testPolymorphicPath() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enableDefaultTyping(DefaultTyping.NON_FINAL);
+ Path input = Paths.get("/tmp", "foo.txt");
+
+ String json = mapper.writeValueAsString(new Object[] { input });
+
+ Object[] obs = mapper.readValue(json, Object[].class);
+ assertEquals(1, obs.length);
+ Object ob = obs[0];
+ assertTrue(ob instanceof Path);
+
+ assertEquals(input.toString(), ob.toString());
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropertiesDeser1575Test.java b/src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropertiesDeser1575Test.java
deleted file mode 100644
index e35e09b..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropertiesDeser1575Test.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.fasterxml.jackson.databind.filter;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import com.fasterxml.jackson.databind.*;
-
-public class IgnorePropertiesDeser1575Test extends BaseMapTest
-{
- static class Person {
- public String name;
-
- @JsonProperty("person_z") // renaming this to person_p works
- @JsonIgnoreProperties({"person_z"}) // renaming this to person_p works
-// public Set<Person> personZ;
- public Person personZ;
- }
-
- public void testIgnorePropDeser1575() throws Exception
- {
- String st = aposToQuotes("{ 'name': 'admin',\n"
-// + " 'person_z': [ { 'name': 'admin' } ]"
- + " 'person_z': { 'name': 'admin' }"
- + "}");
-
- ObjectMapper mapper = new ObjectMapper();
- Person result = mapper.readValue(st, Person.class);
- assertEquals("admin", result.name);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/MapInclusionTest.java b/src/test/java/com/fasterxml/jackson/databind/filter/MapInclusionTest.java
deleted file mode 100644
index a5ccc57..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/filter/MapInclusionTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.fasterxml.jackson.databind.filter;
-
-import java.io.IOException;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.*;
-
-public class MapInclusionTest extends BaseMapTest
-{
- static class NoEmptiesMapContainer {
- @JsonInclude(value=JsonInclude.Include.NON_EMPTY,
- content=JsonInclude.Include.NON_EMPTY)
- public Map<String,String> stuff = new LinkedHashMap<String,String>();
-
- public NoEmptiesMapContainer add(String key, String value) {
- stuff.put(key, value);
- return this;
- }
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- final private ObjectMapper MAPPER = objectMapper();
-
- // [databind#588]
- public void testNonNullValueMapViaProp() throws IOException
- {
- String json = MAPPER.writeValueAsString(new NoEmptiesMapContainer()
- .add("a", null)
- .add("b", ""));
- assertEquals(aposToQuotes("{}"), json);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/format/BooleanFormatTest.java b/src/test/java/com/fasterxml/jackson/databind/format/BooleanFormatTest.java
new file mode 100644
index 0000000..5794cf2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/format/BooleanFormatTest.java
@@ -0,0 +1,71 @@
+package com.fasterxml.jackson.databind.format;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+// [databind#1480]
+public class BooleanFormatTest extends BaseMapTest
+{
+ @JsonPropertyOrder({ "b1", "b2", "b3" })
+ static class BeanWithBoolean
+ {
+ @JsonFormat(shape=JsonFormat.Shape.NUMBER)
+ public boolean b1;
+
+ @JsonFormat(shape=JsonFormat.Shape.NUMBER)
+ public Boolean b2;
+
+ public boolean b3;
+
+ public BeanWithBoolean() { }
+ public BeanWithBoolean(boolean b1, Boolean b2, boolean b3) {
+ this.b1 = b1;
+ this.b2 = b2;
+ this.b3 = b3;
+ }
+ }
+
+ /**
+ * Simple wrapper around boolean types, usually to test value
+ * conversions or wrapping
+ */
+ protected static class BooleanWrapper {
+ public Boolean b;
+
+ public BooleanWrapper() { }
+ public BooleanWrapper(Boolean value) { b = value; }
+ }
+
+ static class AltBoolean extends BooleanWrapper
+ {
+ public AltBoolean() { }
+ public AltBoolean(Boolean b) { super(b); }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final static ObjectMapper MAPPER = newObjectMapper();
+
+ public void testShapeViaDefaults() throws Exception
+ {
+ assertEquals(aposToQuotes("{'b':true}"),
+ MAPPER.writeValueAsString(new BooleanWrapper(true)));
+ ObjectMapper m = newObjectMapper();
+ m.configOverride(Boolean.class)
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.NUMBER));
+ assertEquals(aposToQuotes("{'b':1}"),
+ m.writeValueAsString(new BooleanWrapper(true)));
+ }
+
+ public void testShapeOnProperty() throws Exception
+ {
+ assertEquals(aposToQuotes("{'b1':1,'b2':0,'b3':true}"),
+ MAPPER.writeValueAsString(new BeanWithBoolean(true, false, true)));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestFormatForCollections.java b/src/test/java/com/fasterxml/jackson/databind/format/CollectionFormatShapeTest.java
similarity index 85%
rename from src/test/java/com/fasterxml/jackson/databind/struct/TestFormatForCollections.java
rename to src/test/java/com/fasterxml/jackson/databind/format/CollectionFormatShapeTest.java
index bd308b3..6eea8fe 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/TestFormatForCollections.java
+++ b/src/test/java/com/fasterxml/jackson/databind/format/CollectionFormatShapeTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.struct;
+package com.fasterxml.jackson.databind.format;
import java.util.ArrayList;
import java.util.List;
@@ -8,7 +8,7 @@
import com.fasterxml.jackson.databind.*;
-public class TestFormatForCollections extends BaseMapTest
+public class CollectionFormatShapeTest extends BaseMapTest
{
// [databind#40]: Allow serialization 'as POJO' (resulting in JSON Object)
@JsonPropertyOrder({ "size", "value" })
@@ -40,11 +40,9 @@
/**********************************************************
*/
- private final static ObjectMapper MAPPER = new ObjectMapper();
+ private final static ObjectMapper MAPPER = newObjectMapper();
-
- // [Issue#40]
- public void testListAsObject() throws Exception
+ public void testListAsObjectRoundtrip() throws Exception
{
// First, serialize a "POJO-List"
CollectionAsPOJO list = new CollectionAsPOJO();
diff --git a/src/test/java/com/fasterxml/jackson/databind/format/DateFormatTest.java b/src/test/java/com/fasterxml/jackson/databind/format/DateFormatTest.java
index 19b97f7..f94bd02 100644
--- a/src/test/java/com/fasterxml/jackson/databind/format/DateFormatTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/format/DateFormatTest.java
@@ -18,7 +18,7 @@
public void testTypeDefaults() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
+ ObjectMapper mapper = newObjectMapper();
mapper.configOverride(Date.class)
.setFormat(JsonFormat.Value.forPattern("yyyy.dd.MM"));
// First serialize, should result in this (in UTC):
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/EnumFormatShapeTest.java b/src/test/java/com/fasterxml/jackson/databind/format/EnumFormatShapeTest.java
similarity index 75%
rename from src/test/java/com/fasterxml/jackson/databind/struct/EnumFormatShapeTest.java
rename to src/test/java/com/fasterxml/jackson/databind/format/EnumFormatShapeTest.java
index 8e7f13b..9a376a1 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/EnumFormatShapeTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/format/EnumFormatShapeTest.java
@@ -1,4 +1,7 @@
-package com.fasterxml.jackson.databind.struct;
+package com.fasterxml.jackson.databind.format;
+
+import java.util.Collections;
+import java.util.Map;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
@@ -70,13 +73,31 @@
}
}
+ // [databind#2576]
+ @JsonFormat(shape = JsonFormat.Shape.OBJECT)
+ public enum Enum2576 {
+ DEFAULT("default"),
+ ATTRIBUTES("attributes") {
+ @Override
+ public String toString() {
+ return name();
+ }
+ };
+
+ private final String key;
+ private Enum2576(String key) {
+ this.key = key;
+ }
+ public String getKey() { return this.key; }
+ }
+
/*
/**********************************************************
/* Tests
/**********************************************************
*/
- private final ObjectMapper MAPPER = new ObjectMapper();
+ private final ObjectMapper MAPPER = newObjectMapper();
// Tests for JsonFormat.shape
@@ -113,4 +134,12 @@
assertEquals(String.format(aposToQuotes("{'color':%s}"), Color.GREEN.ordinal()),
MAPPER.writeValueAsString(new ColorWrapper(Color.GREEN)));
}
+
+ // [databind#2576]
+ public void testEnumWithMethodOverride() throws Exception {
+ String stringResult = MAPPER.writeValueAsString(Enum2576.ATTRIBUTES);
+ Map<?,?> result = MAPPER.readValue(stringResult, Map.class);
+ Map<String,String> exp = Collections.singletonMap("key", "attributes");
+ assertEquals(exp, result);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/format/MapEntryFormatTest.java b/src/test/java/com/fasterxml/jackson/databind/format/MapEntryFormatTest.java
new file mode 100644
index 0000000..ce875a5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/format/MapEntryFormatTest.java
@@ -0,0 +1,179 @@
+package com.fasterxml.jackson.databind.format;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.*;
+
+public class MapEntryFormatTest extends BaseMapTest
+{
+ static class BeanWithMapEntry {
+ // would work with any other shape than OBJECT, or without annotation:
+ @JsonFormat(shape=JsonFormat.Shape.NATURAL)
+ public Map.Entry<String,String> entry;
+
+ protected BeanWithMapEntry() { }
+ public BeanWithMapEntry(String key, String value) {
+ Map<String,String> map = new HashMap<>();
+ map.put(key, value);
+ entry = map.entrySet().iterator().next();
+ }
+ }
+
+ @JsonFormat(shape=JsonFormat.Shape.OBJECT)
+ static class MapEntryAsObject implements Map.Entry<String,String> {
+ protected String key, value;
+
+ protected MapEntryAsObject() { }
+ public MapEntryAsObject(String k, String v) {
+ key = k;
+ value = v;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String setValue(String v) {
+ value = v;
+ return v; // wrong, whatever
+ }
+ }
+
+ static class EntryWithNullWrapper {
+ @JsonInclude(value=JsonInclude.Include.NON_EMPTY,
+ content=JsonInclude.Include.NON_NULL)
+ public Map.Entry<String,String> entry;
+
+ public EntryWithNullWrapper(String key, String value) {
+ HashMap<String,String> map = new HashMap<>();
+ map.put(key, value);
+ entry = map.entrySet().iterator().next();
+ }
+ }
+
+ static class EntryWithDefaultWrapper {
+ @JsonInclude(value=JsonInclude.Include.NON_EMPTY,
+ content=JsonInclude.Include.NON_DEFAULT)
+ public Map.Entry<String,String> entry;
+
+ public EntryWithDefaultWrapper(String key, String value) {
+ HashMap<String,String> map = new HashMap<>();
+ map.put(key, value);
+ entry = map.entrySet().iterator().next();
+ }
+ }
+
+ static class EntryWithNonAbsentWrapper {
+ @JsonInclude(value=JsonInclude.Include.NON_EMPTY,
+ content=JsonInclude.Include.NON_ABSENT)
+ public Map.Entry<String,AtomicReference<String>> entry;
+
+ public EntryWithNonAbsentWrapper(String key, String value) {
+ HashMap<String,AtomicReference<String>> map = new HashMap<>();
+ map.put(key, new AtomicReference<String>(value));
+ entry = map.entrySet().iterator().next();
+ }
+ }
+
+ static class EmptyEntryWrapper {
+ @JsonInclude(value=JsonInclude.Include.NON_EMPTY,
+ content=JsonInclude.Include.NON_EMPTY)
+ public Map.Entry<String,String> entry;
+
+ public EmptyEntryWrapper(String key, String value) {
+ HashMap<String,String> map = new HashMap<>();
+ map.put(key, value);
+ entry = map.entrySet().iterator().next();
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, basic
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testInclusion() throws Exception
+ {
+ assertEquals(aposToQuotes("{'entry':{'a':'b'}}"),
+ MAPPER.writeValueAsString(new EmptyEntryWrapper("a", "b")));
+ assertEquals(aposToQuotes("{'entry':{'a':'b'}}"),
+ MAPPER.writeValueAsString(new EntryWithDefaultWrapper("a", "b")));
+ assertEquals(aposToQuotes("{'entry':{'a':'b'}}"),
+ MAPPER.writeValueAsString(new EntryWithNullWrapper("a", "b")));
+
+ assertEquals(aposToQuotes("{}"),
+ MAPPER.writeValueAsString(new EmptyEntryWrapper("a", "")));
+ assertEquals(aposToQuotes("{}"),
+ MAPPER.writeValueAsString(new EntryWithDefaultWrapper("a", "")));
+ assertEquals(aposToQuotes("{'entry':{'a':''}}"),
+ MAPPER.writeValueAsString(new EntryWithNullWrapper("a", "")));
+ assertEquals(aposToQuotes("{}"),
+ MAPPER.writeValueAsString(new EntryWithNullWrapper("a", null)));
+ }
+
+ public void testInclusionWithReference() throws Exception
+ {
+ assertEquals(aposToQuotes("{'entry':{'a':'b'}}"),
+ MAPPER.writeValueAsString(new EntryWithNonAbsentWrapper("a", "b")));
+ // empty String not excluded since reference is not absent, just points to empty
+ // (so would need 3rd level inclusion definition)
+ assertEquals(aposToQuotes("{'entry':{'a':''}}"),
+ MAPPER.writeValueAsString(new EntryWithNonAbsentWrapper("a", "")));
+ assertEquals(aposToQuotes("{}"),
+ MAPPER.writeValueAsString(new EntryWithNonAbsentWrapper("a", null)));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, as-Object (Shape)
+ /**********************************************************
+ */
+
+ public void testAsNaturalRoundtrip() throws Exception
+ {
+ BeanWithMapEntry input = new BeanWithMapEntry("foo" ,"bar");
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("{'entry':{'foo':'bar'}}"), json);
+ BeanWithMapEntry result = MAPPER.readValue(json, BeanWithMapEntry.class);
+ assertEquals("foo", result.entry.getKey());
+ assertEquals("bar", result.entry.getValue());
+ }
+ // should work via class annotation
+ public void testAsObjectRoundtrip() throws Exception
+ {
+ MapEntryAsObject input = new MapEntryAsObject("foo" ,"bar");
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("{'key':'foo','value':'bar'}"), json);
+
+ // 16-Oct-2016, tatu: Happens to work by default because it's NOT basic
+ // `Map.Entry` but subtype.
+
+ MapEntryAsObject result = MAPPER.readValue(json, MapEntryAsObject.class);
+ assertEquals("foo", result.getKey());
+ assertEquals("bar", result.getValue());
+ }
+
+ // [databind#1895]
+ public void testDefaultShapeOverride() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(Map.Entry.class)
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.OBJECT));
+ Map.Entry<String,String> input = new BeanWithMapEntry("foo", "bar").entry;
+ assertEquals(aposToQuotes("{'key':'foo','value':'bar'}"),
+ mapper.writeValueAsString(input));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/format/MapFormatShapeTest.java b/src/test/java/com/fasterxml/jackson/databind/format/MapFormatShapeTest.java
new file mode 100644
index 0000000..a475d40
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/format/MapFormatShapeTest.java
@@ -0,0 +1,205 @@
+package com.fasterxml.jackson.databind.format;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.*;
+
+@SuppressWarnings("serial")
+public class MapFormatShapeTest extends BaseMapTest
+{
+ @JsonPropertyOrder({ "extra" })
+ static class Map476Base extends LinkedHashMap<String,Integer> {
+ public int extra = 13;
+ }
+
+ @JsonFormat(shape=JsonFormat.Shape.OBJECT)
+ static class Map476AsPOJO extends Map476Base { }
+
+ @JsonPropertyOrder({ "a", "b", "c" })
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ static class Bean476Container
+ {
+ public Map476AsPOJO a;
+ public Map476Base b;
+ @JsonFormat(shape=JsonFormat.Shape.OBJECT)
+ public Map476Base c;
+
+ public Bean476Container(int forA, int forB, int forC) {
+ if (forA != 0) {
+ a = new Map476AsPOJO();
+ a.put("value", forA);
+ }
+ if (forB != 0) {
+ b = new Map476Base();
+ b.put("value", forB);
+ }
+ if (forC != 0) {
+ c = new Map476Base();
+ c.put("value", forC);
+ }
+ }
+ }
+
+ static class Bean476Override
+ {
+ @JsonFormat(shape=JsonFormat.Shape.NATURAL)
+ public Map476AsPOJO stuff;
+
+ public Bean476Override(int value) {
+ stuff = new Map476AsPOJO();
+ stuff.put("value", value);
+ }
+ }
+
+ // from [databind#1540]
+ @JsonFormat(shape = JsonFormat.Shape.OBJECT)
+ @JsonPropertyOrder({ "property", "map" })
+ static class Map1540Implementation implements Map<Integer, Integer> {
+ public int property;
+ public Map<Integer, Integer> map = new HashMap<>();
+
+ public Map<Integer, Integer> getMap() {
+ return map;
+ }
+
+ public void setMap(Map<Integer, Integer> map) {
+ this.map = map;
+ }
+
+ @Override
+ public Integer put(Integer key, Integer value) {
+ return map.put(key, value);
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @JsonIgnore
+ @Override
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return map.containsValue(value);
+ }
+
+ @Override
+ public Integer get(Object key) {
+ return map.get(key);
+ }
+
+ @Override
+ public Integer remove(Object key) {
+ return map.remove(key);
+ }
+
+ @Override
+ public void putAll(Map<? extends Integer, ? extends Integer> m) {
+ map.putAll(m);
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+
+ @Override
+ public Set<Integer> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<Integer> values() {
+ return map.values();
+ }
+
+ @Override
+ public Set<java.util.Map.Entry<Integer, Integer>> entrySet() {
+ return map.entrySet();
+ }
+ }
+
+
+ /*
+ /**********************************************************
+ /* Test methods, serialization
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = objectMapper();
+
+ // for [databind#476]: Maps as POJOs
+ public void testSerializeAsPOJOViaClass() throws Exception
+ {
+ String result = MAPPER.writeValueAsString(new Bean476Container(1,2,0));
+ assertEquals(aposToQuotes("{'a':{'extra':13,'empty':false},'b':{'value':2}}"),
+ result);
+ }
+
+ // Can't yet use per-property overrides at all, see [databind#1419]
+
+ /*
+ public void testSerializeAsPOJOViaProperty() throws Exception
+ {
+ String result = MAPPER.writeValueAsString(new Bean476Container(1,0,3));
+ assertEquals(aposToQuotes("{'a':{'extra':13,'empty':false},'c':{'empty':false,'value':3}}"),
+ result);
+ }
+
+ public void testSerializeNaturalViaOverride() throws Exception
+ {
+ String result = MAPPER.writeValueAsString(new Bean476Override(123));
+ assertEquals(aposToQuotes("{'stuff':{'value':123}}"),
+ result);
+ }
+ */
+
+ /*
+ /**********************************************************
+ /* Test methods, deserialization/roundtrip
+ /**********************************************************
+ */
+
+ // [databind#1540]
+ public void testRoundTrip() throws Exception
+ {
+ Map1540Implementation input = new Map1540Implementation();
+ input.property = 55;
+ input.put(12, 45);
+ input.put(6, 88);
+
+ String json = MAPPER.writeValueAsString(input);
+
+ assertEquals(aposToQuotes("{'property':55,'map':{'6':88,'12':45}}"), json);
+
+ Map1540Implementation result = MAPPER.readValue(json, Map1540Implementation.class);
+ assertEquals(result.property, input.property);
+ assertEquals(input.getMap(), input.getMap());
+ }
+
+ // [databind#1554]
+ public void testDeserializeAsPOJOViaClass() throws Exception
+ {
+ Map476AsPOJO result = MAPPER.readValue(aposToQuotes("{'extra':42}"),
+ Map476AsPOJO.class);
+ assertEquals(0, result.size());
+ assertEquals(42, result.extra);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/interop/IllegalTypesCheckTest.java b/src/test/java/com/fasterxml/jackson/databind/interop/IllegalTypesCheckTest.java
index f7d1517..5ccb0fb 100644
--- a/src/test/java/com/fasterxml/jackson/databind/interop/IllegalTypesCheckTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/interop/IllegalTypesCheckTest.java
@@ -8,10 +8,9 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.*;
-import com.mchange.v2.c3p0.jacksontest.ComboPooledDataSource;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
-import java.util.ArrayList;
-import java.util.List;
+import com.mchange.v2.c3p0.jacksontest.ComboPooledDataSource;
/**
* Test case(s) to guard against handling of types that are illegal to handle
@@ -138,8 +137,7 @@
protected void _verifySecurityException(Throwable t, String clsName) throws Exception
{
- // 17-Aug-2017, tatu: Expected type more granular in 2.9 (over 2.8)
- _verifyException(t, JsonMappingException.class,
+ _verifyException(t, InvalidDefinitionException.class,
"Illegal type",
"to deserialize",
"prevented for security reasons");
diff --git a/src/test/java/com/fasterxml/jackson/databind/interop/TestFormatDetection.java b/src/test/java/com/fasterxml/jackson/databind/interop/TestFormatDetection.java
index 18345a6..3d1da2f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/interop/TestFormatDetection.java
+++ b/src/test/java/com/fasterxml/jackson/databind/interop/TestFormatDetection.java
@@ -23,7 +23,7 @@
/* Test methods
/**********************************************************
*/
-
+
public void testSimpleWithJSON() throws Exception
{
ObjectReader detecting = READER.forType(POJO.class);
@@ -33,6 +33,45 @@
assertEquals(1, pojo.x);
}
+ public void testSequenceWithJSON() throws Exception
+ {
+ ObjectReader detecting = READER.forType(POJO.class);
+ detecting = detecting.withFormatDetection(detecting);
+ MappingIterator<POJO> it = detecting.
+ readValues(utf8Bytes(aposToQuotes("{'x':1}\n{'x':2,'y':5}")));
+
+ assertTrue(it.hasNextValue());
+ POJO pojo = it.nextValue();
+ assertEquals(1, pojo.x);
+
+ assertTrue(it.hasNextValue());
+ pojo = it.nextValue();
+ assertEquals(2, pojo.x);
+ assertEquals(5, pojo.y);
+
+ assertFalse(it.hasNextValue());
+ it.close();
+
+ // And again with nodes
+ ObjectReader r2 = READER.forType(JsonNode.class);
+ r2 = r2.withFormatDetection(r2);
+ MappingIterator<JsonNode> nodes = r2.
+ readValues(utf8Bytes(aposToQuotes("{'x':1}\n{'x':2,'y':5}")));
+
+ assertTrue(nodes.hasNextValue());
+ JsonNode n = nodes.nextValue();
+ assertEquals(1, n.size());
+
+ assertTrue(nodes.hasNextValue());
+ n = nodes.nextValue();
+ assertEquals(2, n.size());
+ assertEquals(2, n.path("x").asInt());
+ assertEquals(5, n.path("y").asInt());
+
+ assertFalse(nodes.hasNextValue());
+ nodes.close();
+ }
+
public void testInvalid() throws Exception
{
ObjectReader detecting = READER.forType(POJO.class);
@@ -41,7 +80,7 @@
detecting.readValue(utf8Bytes("<POJO><x>1</x></POJO>"));
fail("Should have failed");
} catch (JsonProcessingException e) {
- verifyException(e, "Can not detect format from input");
+ verifyException(e, "Cannot detect format from input");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/AutoDetect1947Test.java b/src/test/java/com/fasterxml/jackson/databind/introspect/AutoDetect1947Test.java
new file mode 100644
index 0000000..c048917
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/AutoDetect1947Test.java
@@ -0,0 +1,45 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import com.fasterxml.jackson.databind.*;
+
+// Test(s) for [databind#1947], regression for 2.9
+public class AutoDetect1947Test extends BaseMapTest
+{
+ static class Entity1947 {
+ public int shouldBeDetected;
+ public String shouldNotBeDetected;
+
+ @JsonProperty
+ public int getShouldBeDetected() {
+ return shouldBeDetected;
+ }
+
+ public void setShouldBeDetected(int shouldBeDetected) {
+ this.shouldBeDetected = shouldBeDetected;
+ }
+
+ public String getShouldNotBeDetected() {
+ return shouldNotBeDetected;
+ }
+
+ public void setShouldNotBeDetected(String shouldNotBeDetected) {
+ this.shouldNotBeDetected = shouldNotBeDetected;
+ }
+ }
+ public void testDisablingAll() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper()
+ .disable(MapperFeature.AUTO_DETECT_SETTERS)
+ .disable(MapperFeature.AUTO_DETECT_FIELDS)
+ .disable(MapperFeature.AUTO_DETECT_GETTERS)
+ .disable(MapperFeature.AUTO_DETECT_CREATORS)
+ .disable(MapperFeature.AUTO_DETECT_IS_GETTERS);
+ String json = mapper.writeValueAsString(new Entity1947());
+ JsonNode n = mapper.readTree(json);
+ assertEquals(1, n.size());
+ assertTrue(n.has("shouldBeDetected"));
+ assertFalse(n.has("shouldNotBeDetected"));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/CustomAnnotationIntrospector1756Test.java b/src/test/java/com/fasterxml/jackson/databind/introspect/CustomAnnotationIntrospector1756Test.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/failing/CustomAnnotationIntrospector1756Test.java
rename to src/test/java/com/fasterxml/jackson/databind/introspect/CustomAnnotationIntrospector1756Test.java
index 80e6438..62d6207 100644
--- a/src/test/java/com/fasterxml/jackson/failing/CustomAnnotationIntrospector1756Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/CustomAnnotationIntrospector1756Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.introspect;
import java.io.IOException;
import java.lang.annotation.*;
@@ -7,7 +7,6 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
@SuppressWarnings("serial")
@@ -88,6 +87,7 @@
return null;
}
+ @SuppressWarnings("deprecation")
@Override
public boolean hasCreatorAnnotation(Annotated a) {
final AnnotatedConstructor ctor = (AnnotatedConstructor) a;
diff --git a/src/test/java/com/fasterxml/jackson/failing/IgnoredCreatorProperty1572Test.java b/src/test/java/com/fasterxml/jackson/databind/introspect/IgnoredCreatorProperty1572Test.java
similarity index 95%
rename from src/test/java/com/fasterxml/jackson/failing/IgnoredCreatorProperty1572Test.java
rename to src/test/java/com/fasterxml/jackson/databind/introspect/IgnoredCreatorProperty1572Test.java
index e3ed8b9..a6eeca8 100644
--- a/src/test/java/com/fasterxml/jackson/failing/IgnoredCreatorProperty1572Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/IgnoredCreatorProperty1572Test.java
@@ -1,10 +1,9 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.introspect;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.introspect.*;
public class IgnoredCreatorProperty1572Test extends BaseMapTest
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/IgnoredFieldPresentInCreatorProperty2001Test.java b/src/test/java/com/fasterxml/jackson/databind/introspect/IgnoredFieldPresentInCreatorProperty2001Test.java
new file mode 100644
index 0000000..a551c0a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/IgnoredFieldPresentInCreatorProperty2001Test.java
@@ -0,0 +1,27 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.beans.ConstructorProperties;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+
+public class IgnoredFieldPresentInCreatorProperty2001Test extends BaseMapTest
+{
+ static public class Foo {
+ @JsonIgnore
+ public String query;
+
+ // 01-May-2018, tatu: Important! Without this there is no problem
+ @ConstructorProperties("rawQuery")
+ @JsonCreator
+ public Foo(@JsonProperty("query") String rawQuery) {
+ query = rawQuery;
+ }
+ }
+
+ public void testIgnoredFieldPresentInPropertyCreator() throws Exception {
+ Foo deserialized = newObjectMapper().readValue("{\"query\": \"bar\"}", Foo.class);
+ assertEquals("bar", deserialized.query);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java
index 73a3a42..a862713 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java
@@ -1,8 +1,21 @@
package com.fasterxml.jackson.databind.introspect;
+import java.lang.annotation.Annotation;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
+
import com.fasterxml.jackson.core.Version;
+
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.MapperConfig;
+import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
+import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;
+import com.fasterxml.jackson.databind.jsontype.NamedType;
+import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
+import com.fasterxml.jackson.databind.ser.std.StringSerializer;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
// started with [databind#1025] in mind
@SuppressWarnings("serial")
@@ -36,9 +49,440 @@
}
}
+ static class IntrospectorWithHandlers extends AnnotationIntrospector {
+ final Object _deserializer;
+ final Object _serializer;
+
+ public IntrospectorWithHandlers(Object deser, Object ser) {
+ _deserializer = deser;
+ _serializer = ser;
+ }
+
+ @Override
+ public Version version() {
+ return Version.unknownVersion();
+ }
+
+ @Override
+ public Object findDeserializer(Annotated am) {
+ return _deserializer;
+ }
+
+ @Override
+ public Object findSerializer(Annotated am) {
+ return _serializer;
+ }
+ }
+
+ static class IntrospectorWithMap extends AnnotationIntrospector
+ {
+ private final Map<String, Object> values = new HashMap<>();
+
+ private Version version = Version.unknownVersion();
+
+ public IntrospectorWithMap add(String key, Object value) {
+ values.put(key, value);
+ return this;
+ }
+
+ public IntrospectorWithMap version(Version v) {
+ version = v;
+ return this;
+ }
+
+ @Override
+ public Version version() {
+ return version;
+ }
+
+ @Override
+ public JsonInclude.Value findPropertyInclusion(Annotated a) {
+ return JsonInclude.Value.empty()
+ .withContentInclusion(JsonInclude.Include.NON_EMPTY)
+ .withValueInclusion(JsonInclude.Include.USE_DEFAULTS);
+ }
+
+ @Override
+ public boolean isAnnotationBundle(Annotation ann) {
+ return _boolean("isAnnotationBundle");
+ }
+
+ /*
+ /******************************************************
+ /* General class annotations
+ /******************************************************
+ */
+
+ @Override
+ public PropertyName findRootName(AnnotatedClass ac) {
+ return (PropertyName) values.get("findRootName");
+ }
+
+ @Override
+ public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated a) {
+ return (JsonIgnoreProperties.Value) values.get("findPropertyIgnorals");
+ }
+
+ @Override
+ public Boolean isIgnorableType(AnnotatedClass ac) {
+ return (Boolean) values.get("isIgnorableType");
+ }
+
+ @Override
+ public Object findFilterId(Annotated ann) {
+ return (Object) values.get("findFilterId");
+ }
+
+ @Override
+ public Object findNamingStrategy(AnnotatedClass ac) {
+ return (Object) values.get("findNamingStrategy");
+ }
+
+ @Override
+ public String findClassDescription(AnnotatedClass ac) {
+ return (String) values.get("findClassDescription");
+ }
+
+ /*
+ /******************************************************
+ /* Property auto-detection
+ /******************************************************
+ */
+
+ @Override
+ public VisibilityChecker<?> findAutoDetectVisibility(AnnotatedClass ac,
+ VisibilityChecker<?> checker)
+ {
+ VisibilityChecker<?> vc = (VisibilityChecker<?>) values.get("findAutoDetectVisibility");
+ // not really good but:
+ return (vc == null) ? checker : vc;
+ }
+
+ /*
+ /******************************************************
+ /* Type handling
+ /******************************************************
+ */
+
+ @Override
+ public TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config,
+ AnnotatedClass ac, JavaType baseType)
+ {
+ return (TypeResolverBuilder<?>) values.get("findTypeResolver");
+ }
+
+ @Override
+ public TypeResolverBuilder<?> findPropertyTypeResolver(MapperConfig<?> config,
+ AnnotatedMember am, JavaType baseType)
+ {
+ return (TypeResolverBuilder<?>) values.get("findPropertyTypeResolver");
+ }
+
+ @Override
+ public TypeResolverBuilder<?> findPropertyContentTypeResolver(MapperConfig<?> config,
+ AnnotatedMember am, JavaType baseType)
+ {
+ return (TypeResolverBuilder<?>) values.get("findPropertyContentTypeResolver");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<NamedType> findSubtypes(Annotated a)
+ {
+ return (List<NamedType>) values.get("findSubtypes");
+ }
+
+ @Override
+ public String findTypeName(AnnotatedClass ac) {
+ return (String) values.get("findTypeName");
+ }
+
+ /*
+ /******************************************************
+ /* Deserialization introspection
+ /******************************************************
+ */
+
+ @Override
+ public Boolean hasAnySetter(Annotated a) {
+ return (Boolean) values.get("hasAnySetter");
+ }
+
+ /*
+ /******************************************************
+ /* Helper methods
+ /******************************************************
+ */
+
+ private boolean _boolean(String key) {
+ Object ob = values.get(key);
+ return Boolean.TRUE.equals(ob);
+ }
+ }
+
/*
/**********************************************************
- /* Test methods
+ /* Test methods, misc
+ /**********************************************************
+ */
+
+ private final AnnotationIntrospector NO_ANNOTATIONS = AnnotationIntrospector.nopInstance();
+
+ public void testVersion() throws Exception
+ {
+ Version v = new Version(1, 2, 3, null,
+ "com.fasterxml", "IntrospectorPairTest");
+ IntrospectorWithMap withVersion = new IntrospectorWithMap()
+ .version(v);
+ assertEquals(v,
+ new AnnotationIntrospectorPair(withVersion, NO_ANNOTATIONS).version());
+ IntrospectorWithMap noVersion = new IntrospectorWithMap();
+ assertEquals(Version.unknownVersion(),
+ new AnnotationIntrospectorPair(noVersion, withVersion).version());
+ }
+
+ public void testAccess() throws Exception
+ {
+ IntrospectorWithMap intr1 = new IntrospectorWithMap();
+ AnnotationIntrospectorPair pair = new AnnotationIntrospectorPair(intr1,
+ NO_ANNOTATIONS);
+ Collection<AnnotationIntrospector> intrs = pair.allIntrospectors();
+ assertEquals(2, intrs.size());
+ Iterator<AnnotationIntrospector> it = intrs.iterator();
+ assertSame(intr1, it.next());
+ assertSame(NO_ANNOTATIONS, it.next());
+ }
+
+ public void testAnnotationBundle() throws Exception
+ {
+ IntrospectorWithMap isBundle = new IntrospectorWithMap()
+ .add("isAnnotationBundle", true);
+ assertTrue(new AnnotationIntrospectorPair(NO_ANNOTATIONS, isBundle)
+ .isAnnotationBundle(null));
+ assertTrue(new AnnotationIntrospectorPair(isBundle, NO_ANNOTATIONS)
+ .isAnnotationBundle(null));
+ assertFalse(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS)
+ .isAnnotationBundle(null));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, general class annotations
+ /**********************************************************
+ */
+
+ public void testFindRootName() throws Exception
+ {
+ PropertyName name = new PropertyName("test");
+ IntrospectorWithMap intr = new IntrospectorWithMap()
+ .add("findRootName", name);
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS).findRootName(null));
+ assertEquals(name, new AnnotationIntrospectorPair(NO_ANNOTATIONS, intr).findRootName(null));
+ assertEquals(name, new AnnotationIntrospectorPair(intr, NO_ANNOTATIONS).findRootName(null));
+ }
+
+ public void testPropertyIgnorals() throws Exception
+ {
+ JsonIgnoreProperties.Value incl = JsonIgnoreProperties.Value.forIgnoredProperties("foo");
+ IntrospectorWithMap intr = new IntrospectorWithMap()
+ .add("findPropertyIgnorals", incl);
+ IntrospectorWithMap intrEmpty = new IntrospectorWithMap()
+ .add("findPropertyIgnorals", JsonIgnoreProperties.Value.empty());
+ assertEquals(JsonIgnoreProperties.Value.empty(),
+ new AnnotationIntrospectorPair(intrEmpty, intrEmpty).findPropertyIgnorals(null));
+ // should actually verify inclusion combining, but there are separate tests for that
+ assertEquals(incl, new AnnotationIntrospectorPair(intrEmpty, intr).findPropertyIgnorals(null));
+ assertEquals(incl, new AnnotationIntrospectorPair(intr, intrEmpty).findPropertyIgnorals(null));
+ }
+
+ public void testIsIgnorableType() throws Exception
+ {
+ IntrospectorWithMap intr1 = new IntrospectorWithMap()
+ .add("isIgnorableType", Boolean.TRUE);
+ IntrospectorWithMap intr2 = new IntrospectorWithMap()
+ .add("isIgnorableType", Boolean.FALSE);
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS).isIgnorableType(null));
+ assertEquals(Boolean.TRUE, new AnnotationIntrospectorPair(intr1, intr2).isIgnorableType(null));
+ assertEquals(Boolean.FALSE, new AnnotationIntrospectorPair(intr2, intr1).isIgnorableType(null));
+ }
+
+ public void testFindFilterId() throws Exception
+ {
+ IntrospectorWithMap intr1 = new IntrospectorWithMap()
+ .add("findFilterId", "a");
+ IntrospectorWithMap intr2 = new IntrospectorWithMap()
+ .add("findFilterId", "b");
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS).findFilterId(null));
+ assertEquals("a", new AnnotationIntrospectorPair(intr1, intr2).findFilterId(null));
+ assertEquals("b", new AnnotationIntrospectorPair(intr2, intr1).findFilterId(null));
+ }
+
+ public void testFindNamingStrategy() throws Exception
+ {
+ // shouldn't be bogus Classes for real use, but works here
+ IntrospectorWithMap intr1 = new IntrospectorWithMap()
+ .add("findNamingStrategy", Integer.class);
+ IntrospectorWithMap intr2 = new IntrospectorWithMap()
+ .add("findNamingStrategy", String.class);
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS).findNamingStrategy(null));
+ assertEquals(Integer.class,
+ new AnnotationIntrospectorPair(intr1, intr2).findNamingStrategy(null));
+ assertEquals(String.class,
+ new AnnotationIntrospectorPair(intr2, intr1).findNamingStrategy(null));
+ }
+
+ public void testFindClassDescription() throws Exception
+ {
+ IntrospectorWithMap intr1 = new IntrospectorWithMap()
+ .add("findClassDescription", "Desc1");
+ IntrospectorWithMap intr2 = new IntrospectorWithMap()
+ .add("findClassDescription", "Desc2");
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS).findClassDescription(null));
+ assertEquals("Desc1",
+ new AnnotationIntrospectorPair(intr1, intr2).findClassDescription(null));
+ assertEquals("Desc2",
+ new AnnotationIntrospectorPair(intr2, intr1).findClassDescription(null));
+ }
+
+ // // // 3 deprecated methods, skip
+
+ /*
+ /**********************************************************
+ /* Test methods, ser/deser
+ /**********************************************************
+ */
+
+ public void testFindSerializer() throws Exception
+ {
+ final JsonSerializer<?> serString = new StringSerializer();
+ final JsonSerializer<?> serToString = ToStringSerializer.instance;
+
+ AnnotationIntrospector intr1 = new IntrospectorWithHandlers(null, serString);
+ AnnotationIntrospector intr2 = new IntrospectorWithHandlers(null, serToString);
+ AnnotationIntrospector nop = AnnotationIntrospector.nopInstance();
+ AnnotationIntrospector nop2 = new IntrospectorWithHandlers(null, JsonSerializer.None.class);
+
+ assertSame(serString,
+ new AnnotationIntrospectorPair(intr1, intr2).findSerializer(null));
+ assertSame(serToString,
+ new AnnotationIntrospectorPair(intr2, intr1).findSerializer(null));
+
+ // also: no-op instance should not block real one, regardless
+ assertSame(serString,
+ new AnnotationIntrospectorPair(nop, intr1).findSerializer(null));
+ assertSame(serString,
+ new AnnotationIntrospectorPair(nop2, intr1).findSerializer(null));
+
+ // nor should no-op result in non-null result
+ assertNull(new AnnotationIntrospectorPair(nop, nop2).findSerializer(null));
+ assertNull(new AnnotationIntrospectorPair(nop2, nop).findSerializer(null));
+ }
+
+ public void testFindDeserializer() throws Exception
+ {
+ final JsonDeserializer<?> deserString = StringDeserializer.instance;
+ final JsonDeserializer<?> deserObject = UntypedObjectDeserializer.Vanilla.std;
+
+ AnnotationIntrospector intr1 = new IntrospectorWithHandlers(deserString, null);
+ AnnotationIntrospector intr2 = new IntrospectorWithHandlers(deserObject, null);
+ AnnotationIntrospector nop = AnnotationIntrospector.nopInstance();
+ AnnotationIntrospector nop2 = new IntrospectorWithHandlers(JsonDeserializer.None.class, null);
+
+ assertSame(deserString,
+ new AnnotationIntrospectorPair(intr1, intr2).findDeserializer(null));
+ assertSame(deserObject,
+ new AnnotationIntrospectorPair(intr2, intr1).findDeserializer(null));
+ // also: no-op instance should not block real one, regardless
+ assertSame(deserString,
+ new AnnotationIntrospectorPair(nop, intr1).findDeserializer(null));
+ assertSame(deserString,
+ new AnnotationIntrospectorPair(nop2, intr1).findDeserializer(null));
+
+ // nor should no-op result in non-null result
+ assertNull(new AnnotationIntrospectorPair(nop, nop2).findDeserializer(null));
+ assertNull(new AnnotationIntrospectorPair(nop2, nop).findDeserializer(null));
+ }
+
+ /*
+ /******************************************************
+ /* Property auto-detection
+ /******************************************************
+ */
+
+ public void testFindAutoDetectVisibility() throws Exception
+ {
+ VisibilityChecker<?> vc = VisibilityChecker.Std.defaultInstance();
+ IntrospectorWithMap intr1 = new IntrospectorWithMap()
+ .add("findAutoDetectVisibility", vc);
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS)
+ .findAutoDetectVisibility(null, null));
+ assertSame(vc, new AnnotationIntrospectorPair(intr1, NO_ANNOTATIONS)
+ .findAutoDetectVisibility(null, null));
+ assertSame(vc, new AnnotationIntrospectorPair(NO_ANNOTATIONS, intr1)
+ .findAutoDetectVisibility(null, null));
+ }
+
+ /*
+ /******************************************************
+ /* Type handling
+ /******************************************************
+ */
+
+ public void testFindTypeResolver() throws Exception
+ {
+ /*
+ TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config,
+ AnnotatedClass ac, JavaType baseType)
+ return (TypeResolverBuilder<?>) values.get("findTypeResolver");
+ */
+ }
+ public void testFindPropertyTypeResolver() {
+ }
+
+ public void testFindPropertyContentTypeResolver() {
+ }
+
+ public void testFindSubtypes() {
+ }
+
+ public void testFindTypeName() {
+ IntrospectorWithMap intr1 = new IntrospectorWithMap()
+ .add("findTypeName", "type1");
+ IntrospectorWithMap intr2 = new IntrospectorWithMap()
+ .add("findTypeName", "type2");
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS).findTypeName(null));
+ assertEquals("type1",
+ new AnnotationIntrospectorPair(intr1, intr2).findTypeName(null));
+ assertEquals("type2",
+ new AnnotationIntrospectorPair(intr2, intr1).findTypeName(null));
+ }
+
+ /*
+ /******************************************************
+ /* Deserialization introspection
+ /******************************************************
+ */
+
+ // for [databind#1672]
+ public void testHasAnySetter() {
+ IntrospectorWithMap intr1 = new IntrospectorWithMap()
+ .add("hasAnySetter", Boolean.TRUE);
+ IntrospectorWithMap intr2 = new IntrospectorWithMap()
+ .add("hasAnySetter", Boolean.FALSE);
+ assertNull(new AnnotationIntrospectorPair(NO_ANNOTATIONS, NO_ANNOTATIONS).hasAnySetter(null));
+ assertEquals(Boolean.TRUE,
+ new AnnotationIntrospectorPair(intr1, intr2).hasAnySetter(null));
+ assertEquals(Boolean.TRUE,
+ new AnnotationIntrospectorPair(NO_ANNOTATIONS, intr1).hasAnySetter(null));
+ assertEquals(Boolean.FALSE,
+ new AnnotationIntrospectorPair(intr2, intr1).hasAnySetter(null));
+ assertEquals(Boolean.FALSE,
+ new AnnotationIntrospectorPair(NO_ANNOTATIONS, intr2).hasAnySetter(null));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, others
/**********************************************************
*/
@@ -47,7 +491,7 @@
private final AnnotationIntrospectorPair introPair21
= new AnnotationIntrospectorPair(new Introspector2(), new Introspector1());
-
+
// for [databind#1025]
public void testInclusionMerging() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java b/src/test/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollectorTest.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java
rename to src/test/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollectorTest.java
index e621ed4..378b135 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollectorTest.java
@@ -11,7 +11,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-public class TestPOJOPropertiesCollector
+public class POJOPropertiesCollectorTest
extends BaseMapTest
{
static class Simple {
@@ -438,13 +438,16 @@
public void testJackson744() throws Exception
{
- BeanDescription beanDesc = MAPPER.getDeserializationConfig().introspect(MAPPER.constructType(Issue744Bean.class));
+ BeanDescription beanDesc = MAPPER.getDeserializationConfig().introspect
+ (MAPPER.constructType(Issue744Bean.class));
assertNotNull(beanDesc);
- AnnotatedMethod setter = beanDesc.findAnySetter();
+ AnnotatedMember setter = beanDesc.findAnySetterAccessor();
assertNotNull(setter);
+ assertEquals("addAdditionalProperty", setter.getName());
+ assertTrue(setter instanceof AnnotatedMethod);
}
- // [#269]: Support new @JsonPropertyDescription
+ // [databind#269]: Support new @JsonPropertyDescription
public void testPropertyDesc() throws Exception
{
// start via deser
@@ -455,7 +458,7 @@
_verifyProperty(beanDesc, true, false, "13");
}
- // [#438]: Support @JsonProperty.index
+ // [databind#438]: Support @JsonProperty.index
public void testPropertyIndex() throws Exception
{
BeanDescription beanDesc = MAPPER.getDeserializationConfig().introspect(MAPPER.constructType(PropDescBean.class));
@@ -523,7 +526,6 @@
}
}
}
-
/*
/**********************************************************
/* Helper methods
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/PropertyMetadataTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/PropertyMetadataTest.java
new file mode 100644
index 0000000..84dcc54
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/PropertyMetadataTest.java
@@ -0,0 +1,82 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import com.fasterxml.jackson.annotation.Nulls;
+import com.fasterxml.jackson.databind.*;
+
+public class PropertyMetadataTest extends BaseMapTest
+{
+ public void testPropertyName()
+ {
+ PropertyName name = PropertyName.NO_NAME;
+
+ assertFalse(name.hasSimpleName());
+ assertFalse(name.hasNamespace());
+ assertSame(name, name.internSimpleName());
+ assertSame(name, name.withSimpleName(null));
+ assertSame(name, name.withSimpleName(""));
+ assertSame(name, name.withNamespace(null));
+ assertEquals("", name.toString());
+ assertTrue(name.isEmpty());
+ assertFalse(name.hasSimpleName("foo"));
+ // just to trigger it, ensure to exception
+ name.hashCode();
+
+ PropertyName newName = name.withNamespace("");
+ assertNotSame(name, newName);
+ assertTrue(name.equals(name));
+ assertFalse(name.equals(newName));
+ assertFalse(newName.equals(name));
+
+ name = name.withSimpleName("foo");
+ assertEquals("foo", name.toString());
+ assertTrue(name.hasSimpleName("foo"));
+ assertFalse(name.isEmpty());
+ newName = name.withNamespace("ns");
+ assertEquals("{ns}foo", newName.toString());
+ assertFalse(newName.equals(name));
+ assertFalse(name.equals(newName));
+
+ // just to trigger it, ensure to exception
+ name.hashCode();
+ }
+
+ public void testPropertyMetadata()
+ {
+ PropertyMetadata md = PropertyMetadata.STD_OPTIONAL;
+ assertNull(md.getValueNulls());
+ assertNull(md.getContentNulls());
+ assertNull(md.getDefaultValue());
+ assertEquals(Boolean.FALSE, md.getRequired());
+
+ md = md.withNulls(Nulls.AS_EMPTY,
+ Nulls.FAIL);
+ assertEquals(Nulls.AS_EMPTY, md.getValueNulls());
+ assertEquals(Nulls.FAIL, md.getContentNulls());
+
+ assertFalse(md.hasDefaultValue());
+ assertSame(md, md.withDefaultValue(null));
+ assertSame(md, md.withDefaultValue(""));
+ md = md.withDefaultValue("foo");
+ assertEquals("foo", md.getDefaultValue());
+ assertTrue(md.hasDefaultValue());
+ assertSame(md, md.withDefaultValue("foo"));
+ md = md.withDefaultValue(null);
+ assertFalse(md.hasDefaultValue());
+ assertNull(md.getDefaultValue());
+
+ md = md.withRequired(null);
+ assertNull(md.getRequired());
+ assertFalse(md.isRequired());
+ md = md.withRequired(Boolean.TRUE);
+ assertTrue(md.isRequired());
+ assertSame(md, md.withRequired(Boolean.TRUE));
+ md = md.withRequired(null);
+ assertNull(md.getRequired());
+ assertFalse(md.isRequired());
+
+ assertFalse(md.hasIndex());
+ md = md.withIndex(Integer.valueOf(3));
+ assertTrue(md.hasIndex());
+ assertEquals(Integer.valueOf(3), md.getIndex());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestAnnotionBundles.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestAnnotationBundles.java
similarity index 82%
rename from src/test/java/com/fasterxml/jackson/databind/introspect/TestAnnotionBundles.java
rename to src/test/java/com/fasterxml/jackson/databind/introspect/TestAnnotationBundles.java
index 761f0bc..e9a0714 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestAnnotionBundles.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestAnnotationBundles.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -13,7 +14,7 @@
/* Tests mostly for [JACKSON-754]: ability to create "annotation bundles"
*/
-public class TestAnnotionBundles extends com.fasterxml.jackson.databind.BaseMapTest
+public class TestAnnotationBundles extends com.fasterxml.jackson.databind.BaseMapTest
{
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@@ -71,6 +72,18 @@
@HolderA public int unimportant = 42;
}
+ static class RecursiveHolder2 {
+ @HolderA public int getValue() { return 28; }
+ }
+
+ static class RecursiveHolder3 {
+ public int x;
+
+ @JsonCreator
+ @HolderA
+ public RecursiveHolder3(int x) { this.x = x; }
+ }
+
@JsonProperty
@JacksonAnnotationsInside
@Retention(RetentionPolicy.RUNTIME)
@@ -110,11 +123,20 @@
assertEquals("{\"important\":42}", MAPPER.writeValueAsString(new InformingHolder()));
}
- public void testRecursiveBundles() throws Exception
- {
+ public void testRecursiveBundlesField() throws Exception {
assertEquals("{\"unimportant\":42}", MAPPER.writeValueAsString(new RecursiveHolder()));
}
+ public void testRecursiveBundlesMethod() throws Exception {
+ assertEquals("{\"value\":28}", MAPPER.writeValueAsString(new RecursiveHolder2()));
+ }
+
+ public void testRecursiveBundlesConstructor() throws Exception {
+ RecursiveHolder3 result = MAPPER.readValue("17", RecursiveHolder3.class);
+ assertNotNull(result);
+ assertEquals(17, result.x);
+ }
+
public void testBundledIgnore() throws Exception
{
assertEquals("{\"foobar\":13}", MAPPER.writeValueAsString(new Bean()));
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestAutoDetect.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestAutoDetect.java
index c5817bc..9ab933d 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestAutoDetect.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestAutoDetect.java
@@ -1,7 +1,7 @@
package com.fasterxml.jackson.databind.introspect;
import com.fasterxml.jackson.annotation.*;
-
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
@@ -16,13 +16,33 @@
private PrivateBean(String a) { this.a = a; }
}
-
+
+ // test for [databind#1347], config overrides for visibility
+ @JsonPropertyOrder(alphabetic=true)
+ static class Feature1347SerBean {
+ public int field = 2;
+
+ public int getValue() { return 3; }
+ }
+
+ // let's promote use of fields; but not block setters yet
+ @JsonAutoDetect(fieldVisibility=Visibility.NON_PRIVATE)
+ static class Feature1347DeserBean {
+ int value;
+
+ public void setValue(int x) {
+ throw new IllegalArgumentException("Should NOT get called");
+ }
+ }
+
/*
/********************************************************
/* Unit tests
/********************************************************
*/
-
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
public void testPrivateCtor() throws Exception
{
// first, default settings, with which construction works ok
@@ -43,4 +63,42 @@
}
}
+ // [databind#1347]
+ public void testVisibilityConfigOverridesForSer() throws Exception
+ {
+ // first, by default, both field/method should be visible
+ final Feature1347SerBean input = new Feature1347SerBean();
+ assertEquals(aposToQuotes("{'field':2,'value':3}"),
+ MAPPER.writeValueAsString(input));
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(Feature1347SerBean.class)
+ .setVisibility(JsonAutoDetect.Value.construct(PropertyAccessor.GETTER,
+ Visibility.NONE));
+ assertEquals(aposToQuotes("{'field':2}"),
+ mapper.writeValueAsString(input));
+ }
+
+ // [databind#1347]
+ public void testVisibilityConfigOverridesForDeser() throws Exception
+ {
+ final String JSON = aposToQuotes("{'value':3}");
+
+ // by default, should throw exception
+ try {
+ /*Feature1347DeserBean bean =*/
+ MAPPER.readValue(JSON, Feature1347DeserBean.class);
+ fail("Should not pass");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Should NOT get called");
+ }
+
+ // but when instructed to ignore setter, should work
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(Feature1347DeserBean.class)
+ .setVisibility(JsonAutoDetect.Value.construct(PropertyAccessor.SETTER,
+ Visibility.NONE));
+ Feature1347DeserBean result = mapper.readValue(JSON, Feature1347DeserBean.class);
+ assertEquals(3, result.value);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestJacksonAnnotationIntrospector.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestJacksonAnnotationIntrospector.java
index d506473..ef9adde 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestJacksonAnnotationIntrospector.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestJacksonAnnotationIntrospector.java
@@ -185,7 +185,8 @@
{
ObjectMapper mapper = new ObjectMapper();
JacksonAnnotationIntrospector ai = new JacksonAnnotationIntrospector();
- AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(TypeResolverBean.class, mapper.getSerializationConfig());
+ AnnotatedClass ac = AnnotatedClassResolver.resolveWithoutSuperTypes(mapper.getSerializationConfig(),
+ TypeResolverBean.class);
JavaType baseType = TypeFactory.defaultInstance().constructType(TypeResolverBean.class);
TypeResolverBuilder<?> rb = ai.findTypeResolver(mapper.getDeserializationConfig(), ac, baseType);
assertNotNull(rb);
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java
index 14fd27a..f231f0c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java
@@ -168,8 +168,10 @@
// [databind#1026]
{"usId", "us_id" },
{"uId", "u_id" },
+ // [databind#2267]
+ {"xCoordinate", "x_coordinate" },
});
-
+
private ObjectMapper _lcWithUndescoreMapper;
@Override
@@ -363,30 +365,30 @@
public void testExplicitRename() throws Exception
{
- ObjectMapper m = new ObjectMapper();
- m.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
- m.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
- // by default, renaming will not take place on explicitly named fields
- assertEquals(aposToQuotes("{'firstName':'Peter','lastName':'Venkman','user_age':'35'}"),
- m.writeValueAsString(new ExplicitBean()));
+ ObjectMapper m = new ObjectMapper();
+ m.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
+ m.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
+ // by default, renaming will not take place on explicitly named fields
+ assertEquals(aposToQuotes("{'firstName':'Peter','lastName':'Venkman','user_age':'35'}"),
+ m.writeValueAsString(new ExplicitBean()));
- m = new ObjectMapper();
- m.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
- m.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
- m.enable(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING);
- // w/ feature enabled, ALL property names should get re-written
- assertEquals(aposToQuotes("{'first_name':'Peter','last_name':'Venkman','user_age':'35'}"),
- m.writeValueAsString(new ExplicitBean()));
+ m = new ObjectMapper();
+ m.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
+ m.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
+ m.enable(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING);
+ // w/ feature enabled, ALL property names should get re-written
+ assertEquals(aposToQuotes("{'first_name':'Peter','last_name':'Venkman','user_age':'35'}"),
+ m.writeValueAsString(new ExplicitBean()));
- // test deserialization as well
- ExplicitBean bean =
- m.readValue(aposToQuotes("{'first_name':'Egon','last_name':'Spengler','user_age':'32'}"),
- ExplicitBean.class);
+ // test deserialization as well
+ ExplicitBean bean =
+ m.readValue(aposToQuotes("{'first_name':'Egon','last_name':'Spengler','user_age':'32'}"),
+ ExplicitBean.class);
- assertNotNull(bean);
- assertEquals("Egon", bean.userFirstName);
- assertEquals("Spengler", bean.userLastName);
- assertEquals("32", bean.userAge);
+ assertNotNull(bean);
+ assertEquals("Egon", bean.userFirstName);
+ assertEquals("Spengler", bean.userLastName);
+ assertEquals("32", bean.userAge);
}
// Also verify that "no naming strategy" should be ok
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestScalaLikeImplicitProperties.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestScalaLikeImplicitProperties.java
index b6121bb..397db2f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestScalaLikeImplicitProperties.java
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestScalaLikeImplicitProperties.java
@@ -1,6 +1,8 @@
package com.fasterxml.jackson.databind.introspect;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.cfg.MapperConfig;
/**
* Tests Scala-style JVM naming patterns for properties.
@@ -49,13 +51,21 @@
return null;
}
+ /* Deprecated since 2.9
@Override
public boolean hasCreatorAnnotation(Annotated a) {
- // A placeholder for legitmate creator detection.
+ return (a instanceof AnnotatedConstructor);
+ }
+ */
+
+ @Override
+ public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
+ // A placeholder for legitimate creator detection.
// In Scala, all primary constructors should be creators,
// but I can't obtain a reference to the AnnotatedClass from the
// AnnotatedConstructor, so it's simulated here.
- return (a instanceof AnnotatedConstructor);
+ return (a instanceof AnnotatedConstructor)
+ ? JsonCreator.Mode.DEFAULT : null;
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TypeCoercion1592Test.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TypeCoercion1592Test.java
new file mode 100644
index 0000000..c1feb65
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TypeCoercion1592Test.java
@@ -0,0 +1,35 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import com.fasterxml.jackson.databind.*;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+// [databind#1592]: allow "coercion" between primitive/wrapper (mostly just ignoring)
+public class TypeCoercion1592Test extends BaseMapTest
+{
+ static class Bean1592
+ {
+ @JsonSerialize(as=Integer.class)
+ public int i;
+
+ @JsonDeserialize(as=Long.class)
+ public long l;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testTypeCoercion1592() throws Exception
+ {
+ // first, serialize
+ MAPPER.writeValueAsString(new Bean1592());
+ Bean1592 result = MAPPER.readValue("{}", Bean1592.class);
+ assertNotNull(result);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/VisibilityForSerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/VisibilityForSerializationTest.java
new file mode 100644
index 0000000..949ba6e
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/VisibilityForSerializationTest.java
@@ -0,0 +1,160 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import java.io.*;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
+
+/**
+ * Unit tests for checking handling of some of {@link MapperFeature}s
+ * and {@link SerializationFeature}s for serialization.
+ */
+public class VisibilityForSerializationTest
+ extends BaseMapTest
+{
+ /**
+ * Class with one explicitly defined getter, one name-based
+ * auto-detectable getter.
+ */
+ static class GetterClass
+ {
+ @JsonProperty("x") public int getX() { return -2; }
+ public int getY() { return 1; }
+ }
+
+ /**
+ * Another test-class that explicitly disables auto-detection
+ */
+ @JsonAutoDetect(getterVisibility=Visibility.NONE)
+ static class DisabledGetterClass
+ {
+ @JsonProperty("x") public int getX() { return -2; }
+ public int getY() { return 1; }
+ }
+
+ /**
+ * Another test-class that explicitly enables auto-detection
+ */
+ @JsonAutoDetect(isGetterVisibility=Visibility.NONE)
+ static class EnabledGetterClass
+ {
+ @JsonProperty("x") public int getX() { return -2; }
+ public int getY() { return 1; }
+
+ // not auto-detected, since "is getter" auto-detect disabled
+ public boolean isOk() { return true; }
+ }
+
+ /**
+ * One more: only detect "isXxx", not "getXXX"
+ */
+ @JsonAutoDetect(getterVisibility=Visibility.NONE)
+ static class EnabledIsGetterClass
+ {
+ // Won't be auto-detected any more
+ public int getY() { return 1; }
+
+ // but this will be
+ public boolean isOk() { return true; }
+ }
+
+ static class TCls {
+ @JsonProperty("groupname")
+ private String groupname;
+
+ public void setName(String str) {
+ this.groupname = str;
+ }
+ public String getName() {
+ return groupname;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testGlobalAutoDetection() throws IOException
+ {
+ // First: auto-detection enabled (default):
+ ObjectMapper m = new ObjectMapper();
+ Map<String,Object> result = writeAndMap(m, new GetterClass());
+ assertEquals(2, result.size());
+ assertEquals(Integer.valueOf(-2), result.get("x"));
+ assertEquals(Integer.valueOf(1), result.get("y"));
+
+ // Then auto-detection disabled. But note: we MUST create a new
+ // mapper, since old version of serializer may be cached by now
+ m = new ObjectMapper();
+ m.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
+ result = writeAndMap(m, new GetterClass());
+ assertEquals(1, result.size());
+ assertTrue(result.containsKey("x"));
+ }
+
+ public void testPerClassAutoDetection() throws IOException
+ {
+ // First: class-level auto-detection disabling
+ ObjectMapper m = new ObjectMapper();
+ Map<String,Object> result = writeAndMap(m, new DisabledGetterClass());
+ assertEquals(1, result.size());
+ assertTrue(result.containsKey("x"));
+
+ // And then class-level auto-detection enabling, should override defaults
+ m.configure(MapperFeature.AUTO_DETECT_GETTERS, true);
+ result = writeAndMap(m, new EnabledGetterClass());
+ assertEquals(2, result.size());
+ assertTrue(result.containsKey("x"));
+ assertTrue(result.containsKey("y"));
+ }
+
+ public void testPerClassAutoDetectionForIsGetter() throws IOException
+ {
+ ObjectMapper m = new ObjectMapper();
+ // class level should override
+ m.configure(MapperFeature.AUTO_DETECT_GETTERS, true);
+ m.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
+ Map<String,Object> result = writeAndMap(m, new EnabledIsGetterClass());
+ assertEquals(0, result.size());
+ assertFalse(result.containsKey("ok"));
+ }
+
+ // Simple test verifying that chainable methods work ok...
+ public void testConfigChainability()
+ {
+ ObjectMapper m = new ObjectMapper();
+ assertTrue(m.isEnabled(MapperFeature.AUTO_DETECT_SETTERS));
+ assertTrue(m.isEnabled(MapperFeature.AUTO_DETECT_GETTERS));
+ m.configure(MapperFeature.AUTO_DETECT_SETTERS, false)
+ .configure(MapperFeature.AUTO_DETECT_GETTERS, false);
+ assertFalse(m.isEnabled(MapperFeature.AUTO_DETECT_SETTERS));
+ assertFalse(m.isEnabled(MapperFeature.AUTO_DETECT_GETTERS));
+ }
+
+ public void testVisibilityFeatures() throws Exception
+ {
+ ObjectMapper om = new ObjectMapper();
+ // Only use explicitly specified values to be serialized/deserialized (i.e., JSONProperty).
+ om.configure(MapperFeature.AUTO_DETECT_FIELDS, false);
+ om.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
+ om.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
+ om.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
+ om.configure(MapperFeature.USE_GETTERS_AS_SETTERS, false);
+ om.configure(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS, true);
+ om.configure(MapperFeature.INFER_PROPERTY_MUTATORS, false);
+ om.configure(MapperFeature.USE_ANNOTATIONS, true);
+
+ JavaType javaType = om.getTypeFactory().constructType(TCls.class);
+ BeanDescription desc = (BeanDescription) om.getSerializationConfig().introspect(javaType);
+ List<BeanPropertyDefinition> props = desc.findProperties();
+ if (props.size() != 1) {
+ fail("Should find 1 property, not "+props.size()+"; properties = "+props);
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java b/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java
index ea31611..e62a025 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java
@@ -3,13 +3,16 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonParser.NumberType;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
/**
* Basic tests to exercise low-level support added for JSON Schema module and
@@ -45,12 +48,155 @@
public EnumMap<TestEnum,Double> weights;
}
+ static class POJOWithScalars {
+ public boolean boo;
+ public byte b;
+ public char c;
+ public short s;
+ public int i;
+ public long l;
+ public float f;
+ public double d;
+
+ public byte[] arrayBoo;
+ public byte[] arrayb;
+ public char[] arrayc;
+ public short[] arrays;
+ public int[] arrayi;
+ public long[] arrayl;
+ public float[] arrayf;
+ public double[] arrayd;
+
+ public Boolean Boo;
+ public Byte B;
+ public Character C;
+ public Short S;
+ public Integer I;
+ public Long L;
+ public Float F;
+ public Double D;
+
+ public TestEnum en;
+ public String str;
+ public String[] strs;
+ public java.util.Date date;
+ public java.util.Calendar calendar;
+ }
+
+ static class POJOWithRefs {
+ public AtomicReference<POJO> maybePOJO;
+
+ public AtomicReference<String> maybeString;
+ }
+
+ // [databind#1793]
+ static class POJOWithJsonValue {
+ private Point[] value;
+
+ @JsonCreator(mode=JsonCreator.Mode.DELEGATING)
+ public POJOWithJsonValue(Point[] v) { value = v; }
+
+ @JsonValue
+ public Point[] serialization() { return value; }
+ }
+
@JsonPropertyOrder({ "dec", "bigInt" })
static class Numbers {
public BigDecimal dec;
public BigInteger bigInt;
}
+ static class BogusJsonFormatVisitorWrapper
+ extends JsonFormatVisitorWrapper.Base
+ {
+ // Implement handlers just to get more exercise...
+
+ @Override
+ public JsonObjectFormatVisitor expectObjectFormat(JavaType type) {
+ return new JsonObjectFormatVisitor.Base(getProvider()) {
+ @Override
+ public void property(BeanProperty prop) throws JsonMappingException {
+ _visit(prop);
+ }
+
+ @Override
+ public void property(String name, JsonFormatVisitable handler,
+ JavaType propertyTypeHint) { }
+
+ @Override
+ public void optionalProperty(BeanProperty prop) throws JsonMappingException {
+ _visit(prop);
+ }
+
+ @Override
+ public void optionalProperty(String name, JsonFormatVisitable handler,
+ JavaType propertyTypeHint) throws JsonMappingException { }
+
+ private void _visit(BeanProperty prop) throws JsonMappingException
+ {
+ if (!(prop instanceof BeanPropertyWriter)) {
+ return;
+ }
+ BeanPropertyWriter bpw = (BeanPropertyWriter) prop;
+ JsonSerializer<?> ser = bpw.getSerializer();
+ final SerializerProvider prov = getProvider();
+ if (ser == null) {
+ if (prov == null) {
+ throw new Error("SerializerProvider missing");
+ }
+ ser = prov.findValueSerializer(prop.getType(), prop);
+ }
+ // and this just for bit of extra coverage...
+ if (ser instanceof StdSerializer) {
+ assertNotNull(((StdSerializer<?>) ser).getSchema(prov, prop.getType()));
+ }
+ JsonFormatVisitorWrapper visitor = new JsonFormatVisitorWrapper.Base(getProvider());
+ ser.acceptJsonFormatVisitor(visitor, prop.getType());
+ }
+ };
+ }
+
+ @Override
+ public JsonArrayFormatVisitor expectArrayFormat(JavaType type) {
+ return new JsonArrayFormatVisitor.Base(getProvider());
+ }
+
+ @Override
+ public JsonStringFormatVisitor expectStringFormat(JavaType type) {
+ return new JsonStringFormatVisitor.Base();
+ }
+
+ @Override
+ public JsonNumberFormatVisitor expectNumberFormat(JavaType type) {
+ return new JsonNumberFormatVisitor.Base();
+ }
+
+ @Override
+ public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) {
+ return new JsonIntegerFormatVisitor.Base();
+ }
+
+ @Override
+ public JsonBooleanFormatVisitor expectBooleanFormat(JavaType type) {
+ return new JsonBooleanFormatVisitor.Base();
+ }
+
+ @Override
+ public JsonNullFormatVisitor expectNullFormat(JavaType type) {
+ return new JsonNullFormatVisitor.Base();
+ }
+
+ @Override
+ public JsonAnyFormatVisitor expectAnyFormat(JavaType type) {
+ return new JsonAnyFormatVisitor.Base();
+ }
+
+ @Override
+ public JsonMapFormatVisitor expectMapFormat(JavaType type) {
+ return new JsonMapFormatVisitor.Base();
+ }
+ }
+
/*
/**********************************************************
/* Test methods
@@ -65,9 +211,17 @@
*/
public void testBasicTraversal() throws Exception
{
- MAPPER.acceptJsonFormatVisitor(POJO.class, new JsonFormatVisitorWrapper.Base());
+ MAPPER.acceptJsonFormatVisitor(POJO.class, new BogusJsonFormatVisitorWrapper());
+ MAPPER.acceptJsonFormatVisitor(POJOWithScalars.class, new BogusJsonFormatVisitorWrapper());
+ MAPPER.acceptJsonFormatVisitor(LinkedHashMap.class, new BogusJsonFormatVisitorWrapper());
+ MAPPER.acceptJsonFormatVisitor(ArrayList.class, new BogusJsonFormatVisitorWrapper());
+ MAPPER.acceptJsonFormatVisitor(EnumSet.class, new BogusJsonFormatVisitorWrapper());
+
+ MAPPER.acceptJsonFormatVisitor(POJOWithRefs.class, new BogusJsonFormatVisitorWrapper());
+
+ MAPPER.acceptJsonFormatVisitor(POJOWithJsonValue.class, new BogusJsonFormatVisitorWrapper());
}
-
+
public void testSimpleEnum() throws Exception
{
final Set<String> values = new TreeSet<String>();
@@ -125,7 +279,7 @@
assertEquals(exp, values);
}
- // [2.7]: Ensure JsonValueFormat serializes/deserializes as expected
+ // Ensure JsonValueFormat serializes/deserializes as expected
public void testJsonValueFormatHandling() throws Exception
{
// first: serialize using 'toString()', not name
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/AbstracTypeMapping1186Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/AbstractTypeMapping1186Test.java
similarity index 88%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/AbstracTypeMapping1186Test.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/AbstractTypeMapping1186Test.java
index 9985dc6..a5a4537 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/AbstracTypeMapping1186Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/AbstractTypeMapping1186Test.java
@@ -6,7 +6,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
-public class AbstracTypeMapping1186Test extends BaseMapTest
+public class AbstractTypeMapping1186Test extends BaseMapTest
{
public interface IContainer<T> {
@JsonProperty("ts")
@@ -33,7 +33,7 @@
}
public void testDeserializeMyContainer() throws Exception {
- Module module = new SimpleModule().addAbstractTypeMapping(IContainer.class, MyContainer.class);
+ SimpleModule module = new SimpleModule().addAbstractTypeMapping(IContainer.class, MyContainer.class);
final ObjectMapper mapper = new ObjectMapper().registerModule(module);
String json = "{\"ts\": [ { \"msg\": \"hello\"} ] }";
final Object o = mapper.readValue(json,
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/DefaultTypingWithPrimitivesTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/DefaultTypingWithPrimitivesTest.java
deleted file mode 100644
index 707b33b..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/DefaultTypingWithPrimitivesTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.fasterxml.jackson.databind.jsontype;
-
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
-
-import java.util.*;
-
-// [databind#1395]: prevent attempts at including type info for primitives
-public class DefaultTypingWithPrimitivesTest extends BaseMapTest
-{
- static class Data {
- public long key;
- }
-
- public void testDefaultTypingWithLong() throws Exception
- {
- Data data = new Data();
- data.key = 1L;
- Map<String, Object> mapData = new HashMap<String, Object>();
- mapData.put("longInMap", 2L);
- mapData.put("longAsField", data);
-
- // Configure Jackson to preserve types
- ObjectMapper mapper = new ObjectMapper();
- StdTypeResolverBuilder resolver = new StdTypeResolverBuilder();
- resolver.init(JsonTypeInfo.Id.CLASS, null);
- resolver.inclusion(JsonTypeInfo.As.PROPERTY);
- resolver.typeProperty("__t");
- mapper.setDefaultTyping(resolver);
- mapper.enable(SerializationFeature.INDENT_OUTPUT);
-
- // Serialize
- String json = mapper.writeValueAsString(mapData);
-
- // Deserialize
- Map<?,?> result = mapper.readValue(json, Map.class);
- assertNotNull(result);
- assertEquals(2, result.size());
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesExistingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ExistingPropertyTest.java
similarity index 76%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesExistingProperty.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/ExistingPropertyTest.java
index 0bb7275..a4a81db 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesExistingProperty.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ExistingPropertyTest.java
@@ -12,18 +12,18 @@
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
-public class TestSubtypesExistingProperty extends BaseMapTest {
-
+public class ExistingPropertyTest extends BaseMapTest
+{
/**
* Polymorphic base class - existing property as simple property on subclasses
*/
- @JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type",
- visible=true)
- @JsonSubTypes({
- @Type(value = Apple.class, name = "apple") ,
- @Type(value = Orange.class, name = "orange")
- })
- static abstract class Fruit {
+ @JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type",
+ visible=true)
+ @JsonSubTypes({
+ @Type(value = Apple.class, name = "apple") ,
+ @Type(value = Orange.class, name = "orange")
+ })
+ static abstract class Fruit {
public String name;
protected Fruit(String n) { name = n; }
}
@@ -34,20 +34,20 @@
public int seedCount;
public String type;
- private Apple() { super(null);; }
+ private Apple() { super(null); }
public Apple(String name, int b) {
super(name);
seedCount = b;
type = "apple";
}
}
-
+
@JsonTypeName("orange")
static class Orange extends Fruit
{
public String color;
public String type;
-
+
private Orange() { super(null); }
public Orange(String name, String c) {
super(name);
@@ -61,7 +61,7 @@
public FruitWrapper() {}
public FruitWrapper(Fruit f) { fruit = f; }
}
-
+
/**
* Polymorphic base class - existing property forced by abstract method
*/
@@ -94,7 +94,7 @@
return "doggie";
}
}
-
+
@JsonTypeName("kitty")
static class Cat extends Animal
{
@@ -118,7 +118,6 @@
public AnimalWrapper(Animal a) { animal = a; }
}
-
/**
* Polymorphic base class - existing property NOT forced by abstract method on base class
*/
@@ -147,7 +146,7 @@
return "accord";
}
}
-
+
@JsonTypeName("camry")
static class Camry extends Car
{
@@ -169,8 +168,26 @@
public CarWrapper() {}
public CarWrapper(Car c) { car = c; }
}
-
- private final ObjectMapper MAPPER = new ObjectMapper();
+
+ // for [databind#1635]
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.EXISTING_PROPERTY,
+ // IMPORTANT! Must be defined as `visible`
+ visible=true,
+ property = "type",
+ defaultImpl=Bean1635Default.class)
+ @JsonSubTypes({ @JsonSubTypes.Type(Bean1635A.class) })
+ static class Bean1635 {
+ public ABC type;
+ }
+
+ @JsonTypeName("A")
+ static class Bean1635A extends Bean1635 {
+ public int value;
+ }
+
+ static class Bean1635Default extends Bean1635 { }
/*
/**********************************************************
@@ -178,39 +195,41 @@
/**********************************************************
*/
- private static final Orange mandarin = new Orange("Mandarin Orange", "orange");
- private static final String mandarinJson = "{\"name\":\"Mandarin Orange\",\"color\":\"orange\",\"type\":\"orange\"}";
- private static final Apple pinguo = new Apple("Apple-A-Day", 16);
- private static final String pinguoJson = "{\"name\":\"Apple-A-Day\",\"seedCount\":16,\"type\":\"apple\"}";
- private static final FruitWrapper pinguoWrapper = new FruitWrapper(pinguo);
- private static final String pinguoWrapperJson = "{\"fruit\":" + pinguoJson + "}";
- private static final List<Fruit> fruitList = Arrays.asList(pinguo, mandarin);
- private static final String fruitListJson = "[" + pinguoJson + "," + mandarinJson + "]";
+ private static final Orange mandarin = new Orange("Mandarin Orange", "orange");
+ private static final String mandarinJson = "{\"name\":\"Mandarin Orange\",\"color\":\"orange\",\"type\":\"orange\"}";
+ private static final Apple pinguo = new Apple("Apple-A-Day", 16);
+ private static final String pinguoJson = "{\"name\":\"Apple-A-Day\",\"seedCount\":16,\"type\":\"apple\"}";
+ private static final FruitWrapper pinguoWrapper = new FruitWrapper(pinguo);
+ private static final String pinguoWrapperJson = "{\"fruit\":" + pinguoJson + "}";
+ private static final List<Fruit> fruitList = Arrays.asList(pinguo, mandarin);
+ private static final String fruitListJson = "[" + pinguoJson + "," + mandarinJson + "]";
- private static final Cat beelzebub = new Cat("Beelzebub", "tabby");
- private static final String beelzebubJson = "{\"name\":\"Beelzebub\",\"furColor\":\"tabby\",\"type\":\"kitty\"}";
- private static final Dog rover = new Dog("Rover", 42);
- private static final String roverJson = "{\"name\":\"Rover\",\"boneCount\":42,\"type\":\"doggie\"}";
- private static final AnimalWrapper beelzebubWrapper = new AnimalWrapper(beelzebub);
- private static final String beelzebubWrapperJson = "{\"animal\":" + beelzebubJson + "}";
- private static final List<Animal> animalList = Arrays.asList(beelzebub, rover);
- private static final String animalListJson = "[" + beelzebubJson + "," + roverJson + "]";
+ private static final Cat beelzebub = new Cat("Beelzebub", "tabby");
+ private static final String beelzebubJson = "{\"name\":\"Beelzebub\",\"furColor\":\"tabby\",\"type\":\"kitty\"}";
+ private static final Dog rover = new Dog("Rover", 42);
+ private static final String roverJson = "{\"name\":\"Rover\",\"boneCount\":42,\"type\":\"doggie\"}";
+ private static final AnimalWrapper beelzebubWrapper = new AnimalWrapper(beelzebub);
+ private static final String beelzebubWrapperJson = "{\"animal\":" + beelzebubJson + "}";
+ private static final List<Animal> animalList = Arrays.asList(beelzebub, rover);
+ private static final String animalListJson = "[" + beelzebubJson + "," + roverJson + "]";
- private static final Camry camry = new Camry("Sweet Ride", "candy-apple-red");
- private static final String camryJson = "{\"name\":\"Sweet Ride\",\"exteriorColor\":\"candy-apple-red\",\"type\":\"camry\"}";
- private static final Accord accord = new Accord("Road Rage", 6);
- private static final String accordJson = "{\"name\":\"Road Rage\",\"speakerCount\":6,\"type\":\"accord\"}";
- private static final CarWrapper camryWrapper = new CarWrapper(camry);
- private static final String camryWrapperJson = "{\"car\":" + camryJson + "}";
- private static final List<Car> carList = Arrays.asList(camry, accord);
- private static final String carListJson = "[" + camryJson + "," + accordJson + "]";
+ private static final Camry camry = new Camry("Sweet Ride", "candy-apple-red");
+ private static final String camryJson = "{\"name\":\"Sweet Ride\",\"exteriorColor\":\"candy-apple-red\",\"type\":\"camry\"}";
+ private static final Accord accord = new Accord("Road Rage", 6);
+ private static final String accordJson = "{\"name\":\"Road Rage\",\"speakerCount\":6,\"type\":\"accord\"}";
+ private static final CarWrapper camryWrapper = new CarWrapper(camry);
+ private static final String camryWrapperJson = "{\"car\":" + camryJson + "}";
+ private static final List<Car> carList = Arrays.asList(camry, accord);
+ private static final String carListJson = "[" + camryJson + "," + accordJson + "]";
/*
/**********************************************************
- /* Unit tests
+ /* Test methods
/**********************************************************
*/
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
/**
* Fruits - serialization tests for simple property on sub-classes
*/
@@ -354,10 +373,10 @@
assertEquals(accord.name, result.get("name"));
assertEquals(accord.speakerCount, result.get("speakerCount"));
assertEquals(accord.getType(), result.get("type"));
-
+
String camrySerialized = MAPPER.writeValueAsString(camry);
assertEquals(camrySerialized, camryJson);
-
+
String accordSerialized = MAPPER.writeValueAsString(accord);
assertEquals(accordSerialized, accordJson);
@@ -400,4 +419,25 @@
assertTrue(result instanceof Accord);
assertSame(result.getClass(), Accord.class);
}
-}
+
+ // for [databind#1635]: simple usage
+ public void testExistingEnumTypeId() throws Exception
+ {
+ Bean1635 result = MAPPER.readValue(aposToQuotes("{'value':3, 'type':'A'}"),
+ Bean1635.class);
+ assertEquals(Bean1635A.class, result.getClass());
+ Bean1635A bean = (Bean1635A) result;
+ assertEquals(3, bean.value);
+ assertEquals(ABC.A, bean.type);
+ }
+
+ // for [databind#1635]: verify that `defaultImpl` does not block assignment of
+ // type id
+ public void testExistingEnumTypeIdViaDefault() throws Exception
+ {
+ Bean1635 result = MAPPER.readValue(aposToQuotes("{'type':'C'}"),
+ Bean1635.class);
+ assertEquals(Bean1635Default.class, result.getClass());
+ assertEquals(ABC.C, result.type);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/GenericTypeId1735Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/GenericTypeId1735Test.java
index 673d4c8..1a87f8f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/GenericTypeId1735Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/GenericTypeId1735Test.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
// for [databind#1735]:
public class GenericTypeId1735Test extends BaseMapTest
@@ -44,8 +45,9 @@
"{'w':{'type':'"+NEF_CLASS+"'}}"),
Wrapper1735.class);
fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "not subtype of");
+ } catch (InvalidTypeIdException e) {
+ verifyException(e, "could not resolve type id");
+ verifyException(e, "not a subtype");
}
}
@@ -57,8 +59,9 @@
"{'w':{'type':'java.util.HashMap<java.lang.String,java.lang.String>'}}"),
Wrapper1735.class);
fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "not subtype of");
+ } catch (InvalidTypeIdException e) {
+ verifyException(e, "could not resolve type id");
+ verifyException(e, "not a subtype");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestNoTypeInfo.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/NoTypeInfoTest.java
similarity index 87%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestNoTypeInfo.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/NoTypeInfoTest.java
index 3678179..65054d5 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestNoTypeInfo.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/NoTypeInfoTest.java
@@ -5,7 +5,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-public class TestNoTypeInfo extends BaseMapTest
+public class NoTypeInfoTest extends BaseMapTest
{
@JsonTypeInfo(use=JsonTypeInfo.Id.NONE)
@JsonDeserialize(as=NoType.class)
@@ -15,17 +15,16 @@
final static class NoType implements NoTypeInterface {
public int a = 3;
}
-
+
/*
/**********************************************************
- /* Unit tests
+ /* Test methods
/**********************************************************
*/
- // for [JACKSON-746]
public void testWithIdNone() throws Exception
{
- final ObjectMapper mapper = new ObjectMapper();
+ final ObjectMapper mapper = newObjectMapper();
mapper.enableDefaultTyping();
// serialize without type info
String json = mapper.writeValueAsString(new NoType());
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/SubTypeResolution1964Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/SubTypeResolution1964Test.java
new file mode 100644
index 0000000..616e316
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/SubTypeResolution1964Test.java
@@ -0,0 +1,106 @@
+package com.fasterxml.jackson.databind.jsontype;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * Test for [databind#1964], wherein slightly incompatible type hierarchy,
+ * where `Map` key is downcast from `String` to `Object` (via use of "raw"
+ * types to force compiler to ignore incompatibility) causes exception
+ * during serialization. Although ideally code would not force round peg
+ * through square hole, it makes sense to add specific exception to allow
+ * such downcast just for Map key types (for now at least).
+ */
+@SuppressWarnings("serial")
+public class SubTypeResolution1964Test extends BaseMapTest
+{
+ // [databind#1964]
+ static class AccessModel {
+ private Map<String, Collection<String>> repositoryPrivileges;
+
+ public AccessModel() {
+ repositoryPrivileges = new HashMap<>();
+ }
+
+ // 19-Apr-2018, tatu; this would prevent issues
+// @JsonSerialize(typing = JsonSerialize.Typing.STATIC)
+ public Map<String, Collection<String>> getRepositoryPrivileges() {
+ return repositoryPrivileges;
+ }
+
+ public void setRepositoryPrivileges(Map<String, Collection<String>> repositoryPrivileges) {
+ this.repositoryPrivileges = repositoryPrivileges;
+ }
+ }
+ static class CustomMap<T> extends LinkedHashMap<Object, T> { }
+
+ // [databind#2034]: specialization from `Object` to other types probably should
+ // just be allowed (at least in serialization case)
+ interface Dummy {
+ List<String> getStrings();
+ }
+
+ static class MetaModel<M, B> extends AbstractMetaValue<M, M, B> {
+
+ @JsonProperty
+ protected final Map<String, AbstractMetaValue<M, ?, B>> attributes = new HashMap<>();
+
+ public <V> ListMetaAttribute<M, V, B> describeList(final String attributeName) {
+ final ListMetaAttribute<M, V, B> metaAttribute = new ListMetaAttribute<>();
+ attributes.put(attributeName, metaAttribute);
+ return metaAttribute;
+ }
+ }
+
+ static abstract class AbstractMetaValue<M, V, B> {
+ public int getBogus() { return 3; }
+ }
+
+ static class ListMetaAttribute<M, V, B> extends MetaAttribute<M, List<V>, B> {
+ public ListMetaAttribute() { }
+ }
+
+ static class MetaAttribute<M, V, B> extends AbstractMetaValue<M, V, B> {
+ public MetaAttribute() { }
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests
+ /**********************************************************************
+ */
+
+ final ObjectMapper MAPPER = newObjectMapper();
+
+ // [databind#1964]
+ public void testTypeCompatibility1964() throws Exception
+ {
+ // Important! Must use raw type since assignment requires effectively
+ // casting due incompatible type parameters.
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ Map<String, Collection<String>> repoPrivilegesMap = new CustomMap();
+ String key = "/storages/storage0/releases";
+ Collection<String> values = new HashSet<>();
+ values.add("ARTIFACTS_RESOLVE");
+ repoPrivilegesMap.put(key, values);
+
+ AccessModel accessModel = new AccessModel();
+ accessModel.setRepositoryPrivileges(repoPrivilegesMap);
+
+ String jsonStr = MAPPER.writeValueAsString(accessModel);
+ // ... could/should verify more, perhaps, but for now let it be.
+ assertNotNull(jsonStr);
+ }
+
+ // [databind#2034]
+ public void testTypeSpecialization2034() throws Exception
+ {
+ MetaModel<Dummy, Dummy> metaModel = new MetaModel<>();
+ metaModel.describeList("a1");
+ String jsonStr = MAPPER.writeValueAsString(metaModel);
+ // ... could/should verify more, perhaps, but for now let it be.
+ assertNotNull(jsonStr);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestAbstractTypeNames.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestAbstractTypeNames.java
index 2635b12..b98ce41 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestAbstractTypeNames.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestAbstractTypeNames.java
@@ -88,14 +88,13 @@
public String toString() { return "sub!"; }
};
}
-
+
/*
/**********************************************************
- /* Unit tests
+ /* Test methods
/**********************************************************
*/
- // Testing [JACKSON-498], partial fix
public void testEmptyCollection() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestBaseTypeAsDefault.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestBaseTypeAsDefault.java
new file mode 100644
index 0000000..407d1e4
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestBaseTypeAsDefault.java
@@ -0,0 +1,91 @@
+package com.fasterxml.jackson.databind.jsontype;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+
+public class TestBaseTypeAsDefault extends BaseMapTest
+{
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class")
+ static class Parent {
+ }
+
+
+ static class Child extends Parent {
+ }
+
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class", defaultImpl = ChildOfChild.class)
+ static abstract class AbstractParentWithDefault {
+ }
+
+
+ static class ChildOfAbstract extends AbstractParentWithDefault {
+ }
+
+ static class ChildOfChild extends ChildOfAbstract {
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ protected ObjectMapper MAPPER_WITH_BASE = new ObjectMapper()
+ .enable(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL);
+
+ protected ObjectMapper MAPPER_WITHOUT_BASE = new ObjectMapper()
+ .disable(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL);
+
+ public void testPositiveForParent() throws IOException {
+ Object o = MAPPER_WITH_BASE.readerFor(Parent.class).readValue("{}");
+ assertEquals(o.getClass(), Parent.class);
+ }
+
+ public void testPositiveForChild() throws IOException {
+ Object o = MAPPER_WITH_BASE.readerFor(Child.class).readValue("{}");
+ assertEquals(o.getClass(), Child.class);
+ }
+
+ public void testNegativeForParent() throws IOException {
+ try {
+ /*Object o =*/ MAPPER_WITHOUT_BASE.readerFor(Parent.class).readValue("{}");
+ fail("Should not pass");
+ } catch (JsonMappingException ex) {
+ assertTrue(ex.getMessage().contains("missing type id property '@class'"));
+ }
+ }
+
+ public void testNegativeForChild() throws IOException {
+ try {
+ /*Object o =*/ MAPPER_WITHOUT_BASE.readerFor(Child.class).readValue("{}");
+ fail("Should not pass");
+ } catch (JsonMappingException ex) {
+ assertTrue(ex.getMessage().contains("missing type id property '@class'"));
+ }
+ }
+
+ public void testConversionForAbstractWithDefault() throws IOException {
+ // should pass shouldn't it?
+ Object o = MAPPER_WITH_BASE.readerFor(AbstractParentWithDefault.class).readValue("{}");
+ assertEquals(o.getClass(), ChildOfChild.class);
+ }
+
+ public void testPositiveWithTypeSpecification() throws IOException {
+ Object o = MAPPER_WITH_BASE.readerFor(Parent.class)
+ .readValue("{\"@class\":\""+Child.class.getName()+"\"}");
+ assertEquals(o.getClass(), Child.class);
+ }
+
+ public void testPositiveWithManualDefault() throws IOException {
+ Object o = MAPPER_WITH_BASE.readerFor(ChildOfAbstract.class).readValue("{}");
+
+ assertEquals(o.getClass(), ChildOfChild.class);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestPolymorphicDeserialization676.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicDeserialization676.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestPolymorphicDeserialization676.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicDeserialization676.java
index 945a5e7..d6b1bcc 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestPolymorphicDeserialization676.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicDeserialization676.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.jsontype;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java
index 439b7d6..7d260c7 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.NoClass;
+import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
/**
* Unit tests related to specialized handling of "default implementation"
@@ -264,9 +265,8 @@
try {
r.readValue("{ \"value\": \"\" }");
fail("Expected " + JsonMappingException.class);
- } catch (JsonMappingException e) {
- verifyException(e, "missing property 'type'");
- verifyException(e, "contain type id");
+ } catch (InvalidTypeIdException e) {
+ verifyException(e, "missing type id property 'type'");
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl1565.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl1565.java
new file mode 100644
index 0000000..26e7286
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl1565.java
@@ -0,0 +1,94 @@
+package com.fasterxml.jackson.databind.jsontype;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+
+public class TestPolymorphicWithDefaultImpl1565 extends BaseMapTest
+{
+ // [databind#1565]
+ @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY,
+ property="typeInfo", defaultImpl = CBaseClass1565.class)
+ @JsonSubTypes({
+ @JsonSubTypes.Type(CDerived1565.class)
+ })
+ public static interface CTestInterface1565
+ {
+ public String getName();
+ public void setName(String name);
+ public String getTypeInfo();
+ }
+
+ static class CBaseClass1565 implements CTestInterface1565
+ {
+ private String mName;
+
+ @Override
+ public String getName() {
+ return(mName);
+ }
+
+ @Override
+ public void setName(String name) {
+ mName = name;
+ }
+
+ @Override
+ public String getTypeInfo() {
+ return "base";
+ }
+ }
+
+ @JsonTypeName("derived")
+ static class CDerived1565 extends CBaseClass1565
+ {
+ public String description;
+
+ @Override
+ public String getTypeInfo() {
+ return "derived";
+ }
+ }
+
+ // [databind#1861]
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = DefaultImpl1861.class)
+ @JsonSubTypes({
+ @JsonSubTypes.Type(name = "a", value = Impl1861A.class)
+ })
+ static abstract class Bean1861 {
+ public String base;
+ }
+
+ static class DefaultImpl1861 extends Bean1861 {
+ public int id;
+ }
+
+ static class Impl1861A extends Bean1861 {
+ public int valueA;
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ // [databind#1565]
+ public void testIncompatibleDefaultImpl1565() throws Exception
+ {
+ String value = "{\"typeInfo\": \"derived\", \"name\": \"John\", \"description\": \"Owner\"}";
+ CDerived1565 result = MAPPER.readValue(value, CDerived1565.class);
+ assertNotNull(result);
+ }
+
+ // [databind#1861]
+ public void testWithIncompatibleTargetType1861() throws Exception
+ {
+ // Should allow deserialization even if `defaultImpl` incompatible
+ Impl1861A result = MAPPER.readValue(aposToQuotes("{'type':'a','base':'foo','valueA':3}"),
+ Impl1861A.class);
+ assertNotNull(result);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPropertyTypeInfo.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPropertyTypeInfo.java
index 484f6cc..5d68ce8 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPropertyTypeInfo.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPropertyTypeInfo.java
@@ -9,16 +9,19 @@
/**
* Testing to verify that {@link JsonTypeInfo} works
- * for properties as well as types (see [JACKSON-280] for details)
+ * for properties as well as types.
*/
@SuppressWarnings("serial")
public class TestPropertyTypeInfo extends BaseMapTest
{
- /*
- /**********************************************************
- /* Helper types
- /**********************************************************
- */
+ protected static class BooleanValue {
+ public Boolean b;
+
+ @JsonCreator
+ public BooleanValue(Boolean value) { b = value; }
+
+ @JsonValue public Boolean value() { return b; }
+ }
static class FieldWrapperBean
{
@@ -120,7 +123,7 @@
{
ObjectMapper mapper = new ObjectMapper();
MethodWrapperBeanList list = new MethodWrapperBeanList();
- list.add(new MethodWrapperBean(new BooleanWrapper(true)));
+ list.add(new MethodWrapperBean(new BooleanValue(true)));
list.add(new MethodWrapperBean(new StringWrapper("x")));
list.add(new MethodWrapperBean(new OtherBean()));
String json = mapper.writeValueAsString(list);
@@ -128,8 +131,8 @@
assertNotNull(result);
assertEquals(3, result.size());
MethodWrapperBean bean = result.get(0);
- assertEquals(BooleanWrapper.class, bean.value.getClass());
- assertEquals(((BooleanWrapper) bean.value).b, Boolean.TRUE);
+ assertEquals(BooleanValue.class, bean.value.getClass());
+ assertEquals(((BooleanValue) bean.value).b, Boolean.TRUE);
bean = result.get(1);
assertEquals(StringWrapper.class, bean.value.getClass());
assertEquals(((StringWrapper) bean.value).str, "x");
@@ -141,15 +144,15 @@
{
ObjectMapper mapper = new ObjectMapper();
FieldWrapperBeanArray array = new FieldWrapperBeanArray(new
- FieldWrapperBean[] { new FieldWrapperBean(new BooleanWrapper(true)) });
+ FieldWrapperBean[] { new FieldWrapperBean(new BooleanValue(true)) });
String json = mapper.writeValueAsString(array);
FieldWrapperBeanArray result = mapper.readValue(json, FieldWrapperBeanArray.class);
assertNotNull(result);
FieldWrapperBean[] beans = result.beans;
assertEquals(1, beans.length);
FieldWrapperBean bean = beans[0];
- assertEquals(BooleanWrapper.class, bean.value.getClass());
- assertEquals(((BooleanWrapper) bean.value).b, Boolean.TRUE);
+ assertEquals(BooleanValue.class, bean.value.getClass());
+ assertEquals(((BooleanValue) bean.value).b, Boolean.TRUE);
}
public void testSimpleArrayMethod() throws Exception
@@ -187,7 +190,7 @@
{
ObjectMapper mapper = new ObjectMapper();
MethodWrapperBeanMap map = new MethodWrapperBeanMap();
- map.put("xyz", new MethodWrapperBean(new BooleanWrapper(true)));
+ map.put("xyz", new MethodWrapperBean(new BooleanValue(true)));
String json = mapper.writeValueAsString(map);
MethodWrapperBeanMap result = mapper.readValue(json, MethodWrapperBeanMap.class);
assertNotNull(result);
@@ -195,7 +198,7 @@
MethodWrapperBean bean = result.get("xyz");
assertNotNull(bean);
Object ob = bean.value;
- assertEquals(BooleanWrapper.class, ob.getClass());
- assertEquals(((BooleanWrapper) ob).b, Boolean.TRUE);
+ assertEquals(BooleanValue.class, ob.getClass());
+ assertEquals(((BooleanValue) ob).b, Boolean.TRUE);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestScalars.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestScalars.java
index c08bf7e..89481bf 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestScalars.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestScalars.java
@@ -42,14 +42,14 @@
return this;
}
}
-
+
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
- final ObjectMapper MAPPER = new ObjectMapper();
+ final ObjectMapper MAPPER = newObjectMapper();
/**
* Ensure that per-property dynamic types work, both for "native" types
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 4450c04..5517a2c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
@@ -2,6 +2,9 @@
import com.fasterxml.jackson.core.Version;
+
+import java.util.*;
+
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
@@ -9,6 +12,7 @@
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;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.module.SimpleModule;
@@ -176,7 +180,7 @@
assertSame(SubC.class, result.value.getClass());
}
- // [JACKSON-748]: also works via modules
+ // also works via modules
public void testSubtypesViaModule() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
@@ -186,6 +190,19 @@
String json = mapper.writeValueAsString(new PropertyBean(new SubC()));
PropertyBean result = mapper.readValue(json, PropertyBean.class);
assertSame(SubC.class, result.value.getClass());
+
+ // and as per [databind#1653]:
+ mapper = new ObjectMapper();
+ module = new SimpleModule();
+ List<Class<?>> l = new ArrayList<>();
+ l.add(SubB.class);
+ l.add(SubC.class);
+ l.add(SubD.class);
+ module.registerSubtypes(l);
+ mapper.registerModule(module);
+ json = mapper.writeValueAsString(new PropertyBean(new SubC()));
+ result = mapper.readValue(json, PropertyBean.class);
+ assertSame(SubC.class, result.value.getClass());
}
public void testSerialization() throws Exception
@@ -282,8 +299,8 @@
try {
MAPPER.readValue(JSON, SuperTypeWithoutDefault.class);
fail("Expected an exception");
- } catch (JsonMappingException e) {
- verifyException(e, "missing property");
+ } catch (InvalidTypeIdException e) {
+ verifyException(e, "missing type id property '#type'");
}
// but then succeed when we register default impl
@@ -330,9 +347,11 @@
MAPPER.readValue(aposToQuotes("{'value':['"
+TheBomb.class.getName()+"',{'a':13}] }"), DateWrapper.class);
fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "not subtype of");
+ } catch (InvalidTypeIdException e) {
+ verifyException(e, "not a subtype");
verifyException(e, TheBomb.class.getName());
+ } catch (Exception e) {
+ fail("Should have hit `InvalidTypeIdException`, not `"+e.getClass().getName()+"`: "+e);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypeNames.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypeNames.java
index cfe6a6b..735b22d 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypeNames.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypeNames.java
@@ -7,19 +7,31 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver;
import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Separate tests for verifying that "type name" type id mechanism
* works.
- *
- * @author tatu
*/
public class TestTypeNames extends BaseMapTest
{
@SuppressWarnings("serial")
static class AnimalMap extends LinkedHashMap<String,Animal> { }
+
+ @JsonTypeInfo(property = "type", include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.NAME)
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = A1616.class,name = "A"),
+ @JsonSubTypes.Type(value = B1616.class)
+ })
+ static abstract class Base1616 { }
+
+ static class A1616 extends Base1616 { }
+
+ @JsonTypeName("B")
+ static class B1616 extends Base1616 { }
/*
/**********************************************************
@@ -27,31 +39,48 @@
/**********************************************************
*/
+ private final ObjectMapper MAPPER = objectMapper();
+
+ public void testBaseTypeId1616() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ Collection<NamedType> subtypes = new StdSubtypeResolver().collectAndResolveSubtypesByTypeId(
+ mapper.getDeserializationConfig(),
+ // note: `null` is fine here as `AnnotatedMember`:
+ null,
+ mapper.constructType(Base1616.class));
+ assertEquals(2, subtypes.size());
+ Set<String> ok = new HashSet<>(Arrays.asList("A", "B"));
+ for (NamedType type : subtypes) {
+ String id = type.getName();
+ if (!ok.contains(id)) {
+ fail("Unexpected id '"+id+"' (mapping to: "+type.getType()+"), should be one of: "+ok);
+ }
+ }
+ }
+
public void testSerialization() throws Exception
{
- ObjectMapper m = new ObjectMapper();
-
// Note: need to use wrapper array just so that we can define
// static type on serialization. If we had root static types,
// could use those; but at the moment root type is dynamic
assertEquals("[{\"doggy\":{\"name\":\"Spot\",\"ageInYears\":3}}]",
- m.writeValueAsString(new Animal[] { new Dog("Spot", 3) }));
+ MAPPER.writeValueAsString(new Animal[] { new Dog("Spot", 3) }));
assertEquals("[{\"MaineCoon\":{\"name\":\"Belzebub\",\"purrs\":true}}]",
- m.writeValueAsString(new Animal[] { new MaineCoon("Belzebub", true)}));
+ MAPPER.writeValueAsString(new Animal[] { new MaineCoon("Belzebub", true)}));
}
public void testRoundTrip() throws Exception
{
- ObjectMapper m = new ObjectMapper();
Animal[] input = new Animal[] {
new Dog("Odie", 7),
null,
new MaineCoon("Piru", false),
new Persian("Khomeini", true)
};
- String json = m.writeValueAsString(input);
- List<Animal> output = m.readValue(json,
+ String json = MAPPER.writeValueAsString(input);
+ List<Animal> output = MAPPER.readValue(json,
TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, Animal.class));
assertEquals(input.length, output.size());
for (int i = 0, len = input.length; i < len; ++i) {
@@ -62,12 +91,11 @@
public void testRoundTripMap() throws Exception
{
- ObjectMapper m = new ObjectMapper();
AnimalMap input = new AnimalMap();
input.put("venla", new MaineCoon("Venla", true));
input.put("ama", new Dog("Amadeus", 13));
- String json = m.writeValueAsString(input);
- AnimalMap output = m.readValue(json, AnimalMap.class);
+ String json = MAPPER.writeValueAsString(input);
+ AnimalMap output = MAPPER.readValue(json, AnimalMap.class);
assertNotNull(output);
assertEquals(AnimalMap.class, output.getClass());
assertEquals(input.size(), output.size());
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedArraySerialization.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedArraySerialization.java
index a7ddf02..3b416bd 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedArraySerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedArraySerialization.java
@@ -70,13 +70,14 @@
/**********************************************************
*/
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
public void testListWithPolymorphic() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
BeanListWrapper beans = new BeanListWrapper();
- assertEquals("{\"beans\":[{\"@type\":\"bean\",\"x\":0}]}", mapper.writeValueAsString(beans));
+ assertEquals("{\"beans\":[{\"@type\":\"bean\",\"x\":0}]}", MAPPER.writeValueAsString(beans));
// Related to [JACKSON-364]
- ObjectWriter w = mapper.writerWithView(Object.class);
+ ObjectWriter w = MAPPER.writerWithView(Object.class);
assertEquals("{\"beans\":[{\"@type\":\"bean\",\"x\":0}]}", w.writeValueAsString(beans));
}
@@ -86,7 +87,8 @@
input.add(5);
input.add(13);
// uses WRAPPER_ARRAY inclusion:
- assertEquals("[\""+TypedList.class.getName()+"\",[5,13]]", serializeAsString(input));
+ assertEquals("[\""+TypedList.class.getName()+"\",[5,13]]",
+ MAPPER.writeValueAsString(input));
}
// Similar to above, but this time let's request adding type info
@@ -99,7 +101,7 @@
input.add("a");
input.add("b");
assertEquals("[\""+TypedListAsProp.class.getName()+"\",[\"a\",\"b\"]]",
- serializeAsString(input));
+ MAPPER.writeValueAsString(input));
}
public void testStringListAsObjectWrapper() throws Exception
@@ -113,7 +115,7 @@
// annotations
String expName = "TestTypedArraySerialization$TypedListAsWrapper";
assertEquals("{\""+expName+"\":[true,null,false]}",
- serializeAsString(input));
+ MAPPER.writeValueAsString(input));
}
/*
@@ -128,7 +130,7 @@
m.addMixIn(int[].class, WrapperMixIn.class);
int[] input = new int[] { 1, 2, 3 };
String clsName = int[].class.getName();
- assertEquals("{\""+clsName+"\":[1,2,3]}", serializeAsString(m, input));
+ assertEquals("{\""+clsName+"\":[1,2,3]}", m.writeValueAsString(input));
}
/*
@@ -139,16 +141,14 @@
public void testGenericArray() throws Exception
{
- ObjectMapper m;
final A[] input = new A[] { new B() };
final String EXP = "[{\"BB\":{\"value\":2}}]";
// first, with defaults
- m = new ObjectMapper();
- assertEquals(EXP, m.writeValueAsString(input));
+ assertEquals(EXP, MAPPER.writeValueAsString(input));
// then with static typing enabled:
- m = new ObjectMapper();
+ ObjectMapper m = new ObjectMapper();
m.configure(MapperFeature.USE_STATIC_TYPING, true);
assertEquals(EXP, m.writeValueAsString(input));
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedContainerSerialization.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedContainerSerialization.java
index 92cc507..5c3bd75 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedContainerSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedContainerSerialization.java
@@ -9,12 +9,13 @@
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
import com.fasterxml.jackson.core.type.TypeReference;
+
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.databind.type.TypeFactory;
public class TestTypedContainerSerialization
extends BaseMapTest
@@ -123,13 +124,13 @@
public void testIssue329() throws Exception
{
- ArrayList<Animal> animals = new ArrayList<Animal>();
- animals.add(new Dog("Spot"));
- JavaType rootType = TypeFactory.defaultInstance().constructParametrizedType(Iterator.class, Iterator.class, Animal.class);
- String json = mapper.writerFor(rootType).writeValueAsString(animals.iterator());
- if (json.indexOf("\"object-type\":\"doggy\"") < 0) {
- fail("No polymorphic type retained, should be; JSON = '"+json+"'");
- }
+ ArrayList<Animal> animals = new ArrayList<Animal>();
+ animals.add(new Dog("Spot"));
+ JavaType rootType = mapper.getTypeFactory().constructParametricType(Iterator.class, Animal.class);
+ String json = mapper.writerFor(rootType).writeValueAsString(animals.iterator());
+ if (json.indexOf("\"object-type\":\"doggy\"") < 0) {
+ fail("No polymorphic type retained, should be; JSON = '"+json+"'");
+ }
}
public void testIssue508() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestVisibleTypeId.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestVisibleTypeId.java
index 3b9003e..3e7d317 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestVisibleTypeId.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestVisibleTypeId.java
@@ -6,7 +6,7 @@
import com.fasterxml.jackson.databind.*;
/**
- * Tests to verify [JACKSON-437], [JACKSON-762]
+ * Tests to verify that Type Id may be exposed during deserialization,
*/
public class TestVisibleTypeId extends BaseMapTest
{
@@ -92,7 +92,7 @@
static class ExternalIdBean2 {
public int a = 2;
- /* Type id property itself can not be external, as it is conceptually
+ /* Type id property itself cannot be external, as it is conceptually
* part of the bean for which info is written:
*/
@JsonTypeId
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 b3529f0..bb4cd9b 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestWithGenerics.java
@@ -156,7 +156,8 @@
{
Dog dog = new Dog("Fluffy", 3);
ContainerWithGetter<Animal> c2 = new ContainerWithGetter<Animal>(dog);
- String json = MAPPER.writerFor(MAPPER.getTypeFactory().constructParametrizedType(ContainerWithGetter.class, ContainerWithGetter.class, Animal.class)).writeValueAsString(c2);
+ String json = MAPPER.writerFor(MAPPER.getTypeFactory().constructParametricType(ContainerWithGetter.class,
+ Animal.class)).writeValueAsString(c2);
if (json.indexOf("\"object-type\":\"doggy\"") < 0) {
fail("polymorphic type not kept, result == "+json+"; should contain 'object-type':'...'");
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TypeDeserializerTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TypeDeserializerTest.java
new file mode 100644
index 0000000..2b1fa8e
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TypeDeserializerTest.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.databind.jsontype;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+
+public class TypeDeserializerTest extends BaseMapTest
+{
+ public void testUtilMethods() throws Exception
+ {
+ final JsonFactory f = new JsonFactory();
+
+ JsonParser p = f.createParser("true");
+ assertNull(TypeDeserializer.deserializeIfNatural(p, null, Object.class));
+ p.nextToken();
+ assertEquals(Boolean.TRUE, TypeDeserializer.deserializeIfNatural(p, null, Object.class));
+ p.close();
+
+ p = f.createParser("false ");
+ p.nextToken();
+ assertEquals(Boolean.FALSE, TypeDeserializer.deserializeIfNatural(p, null, Object.class));
+ p.close();
+
+ p = f.createParser("1");
+ p.nextToken();
+ assertEquals(Integer.valueOf(1), TypeDeserializer.deserializeIfNatural(p, null, Object.class));
+ p.close();
+
+ p = f.createParser("0.5 ");
+ p.nextToken();
+ assertEquals(Double.valueOf(0.5), TypeDeserializer.deserializeIfNatural(p, null, Object.class));
+ p.close();
+
+ p = f.createParser("\"foo\" [ ] ");
+ p.nextToken();
+ assertEquals("foo", TypeDeserializer.deserializeIfNatural(p, null, Object.class));
+
+ p.nextToken();
+ assertNull(TypeDeserializer.deserializeIfNatural(p, null, Object.class));
+
+ p.close();
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForArrays.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForArrays.java
similarity index 74%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForArrays.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForArrays.java
index 154630d..cd6d34e 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForArrays.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForArrays.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import java.util.ArrayList;
import java.util.HashMap;
@@ -23,7 +23,15 @@
public ArrayBean() { this(null); }
public ArrayBean(Object[] v) { values = v; }
}
-
+
+ static class PrimitiveArrayBean {
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+ public Object stuff;
+
+ protected PrimitiveArrayBean() { }
+ public PrimitiveArrayBean(Object value) { stuff = value; }
+ }
+
/*
/**********************************************************
/* Unit tests
@@ -57,7 +65,6 @@
assertEquals(String[][].class, result.values.getClass());
}
- // @since 1.8
public void testNodeInArray() throws Exception
{
JsonNode node = new ObjectMapper().readTree("{\"a\":3}");
@@ -72,6 +79,7 @@
assertTrue(ob instanceof JsonNode);
}
+ @SuppressWarnings("deprecation")
public void testNodeInEmptyArray() throws Exception {
Map<String, List<String>> outerMap = new HashMap<String, List<String>>();
outerMap.put("inner", new ArrayList<String>());
@@ -90,7 +98,6 @@
assertEquals("{}", result[0].toString());
}
- // test for [JACKSON-845]
public void testArraysOfArrays() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
@@ -105,6 +112,29 @@
_testArraysAs(mapper, json, Object.class);
}
+ public void testArrayTypingForPrimitiveArrays() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ m.enableDefaultTyping(DefaultTyping.NON_CONCRETE_AND_ARRAYS);
+ _testArrayTypingForPrimitiveArrays(m, new int[] { 1, 2, 3 });
+ _testArrayTypingForPrimitiveArrays(m, new long[] { 1, 2, 3 });
+ _testArrayTypingForPrimitiveArrays(m, new short[] { 1, 2, 3 });
+ _testArrayTypingForPrimitiveArrays(m, new double[] { 0.5, 5.5, -1.0 });
+ _testArrayTypingForPrimitiveArrays(m, new float[] { 0.5f, 5.5f, -1.0f });
+ _testArrayTypingForPrimitiveArrays(m, new boolean[] { true, false });
+ _testArrayTypingForPrimitiveArrays(m, new byte[] { 1, 2, 3 });
+
+ _testArrayTypingForPrimitiveArrays(m, new char[] { 'a', 'b' });
+ }
+
+ private void _testArrayTypingForPrimitiveArrays(ObjectMapper mapper, Object v) throws Exception {
+ PrimitiveArrayBean input = new PrimitiveArrayBean(v);
+ String json = mapper.writeValueAsString(input);
+ PrimitiveArrayBean result = mapper.readValue(json, PrimitiveArrayBean.class);
+ assertNotNull(result.stuff);
+ assertSame(v.getClass(), result.stuff.getClass());
+ }
+
/*
/**********************************************************
/* Helper methods
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForEnums.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java
similarity index 92%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForEnums.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java
index 645530c..b716c24 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForEnums.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import java.util.concurrent.TimeUnit;
@@ -23,7 +23,7 @@
protected static class TimeUnitBean {
public TimeUnit timeUnit;
}
-
+
/*
/**********************************************************
/* Test methods
@@ -57,7 +57,7 @@
// Typing is needed for enums
String json = m.writeValueAsString(new Object[] { TestEnum.A });
- assertEquals("[[\"com.fasterxml.jackson.databind.jsontype.TestDefaultForEnums$TestEnum\",\"A\"]]", json);
+ assertEquals("[[\"com.fasterxml.jackson.databind.jsontype.deftyping.TestDefaultForEnums$TestEnum\",\"A\"]]", json);
// and let's verify we get it back ok as well:
Object[] value = m.readValue(json, Object[].class);
@@ -70,7 +70,7 @@
ObjectMapper m = new ObjectMapper();
m.enableDefaultTyping();
String json = m.writeValueAsString(new EnumHolder(TestEnum.B));
- assertEquals("{\"value\":[\"com.fasterxml.jackson.databind.jsontype.TestDefaultForEnums$TestEnum\",\"B\"]}", json);
+ assertEquals("{\"value\":[\"com.fasterxml.jackson.databind.jsontype.deftyping.TestDefaultForEnums$TestEnum\",\"B\"]}", json);
EnumHolder holder = m.readValue(json, EnumHolder.class);
assertSame(TestEnum.B, holder.value);
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForLists.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForLists.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForLists.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForLists.java
index df55697..b061be6 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForLists.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForLists.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import java.util.*;
@@ -115,7 +115,7 @@
inputList.add(Locale.CHINESE);
input.values = inputList;
String json = m.writeValueAsString(input);
-
+
ObjectListBean output = m.readValue(json, ObjectListBean.class);
List<Object> outputList = output.values;
assertEquals(2, outputList.size());
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForMaps.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForMaps.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForMaps.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForMaps.java
index c36ebd2..c1fca0a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForMaps.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForMaps.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import java.util.*;
@@ -118,8 +118,6 @@
TypeFactory.defaultInstance().constructType(Object.class), subtypes, forSerialization, !forSerialization);
}
- // // For #234:
-
public void testList() throws Exception
{
final ObjectMapper mapper = new ObjectMapper();
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForObject.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForObject.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForObject.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForObject.java
index 081af8a..5f0a23a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForObject.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForObject.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import java.util.*;
@@ -136,7 +136,7 @@
fail("Should have failed");
} catch (JsonMappingException e) {
// let's use whatever is currently thrown exception... may change tho
- verifyException(e, "can not construct");
+ verifyException(e, "cannot construct");
}
// and then that we will succeed with default type info
@@ -337,7 +337,7 @@
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "*CLASS*");
String json = mapper.writeValueAsString(new BeanHolder(new StringBean("punny")));
- assertEquals("{\"bean\":{\"*CLASS*\":\"com.fasterxml.jackson.databind.jsontype.TestDefaultForObject$StringBean\",\"name\":\"punny\"}}", json);
+ assertEquals("{\"bean\":{\"*CLASS*\":\"com.fasterxml.jackson.databind.jsontype.deftyping.TestDefaultForObject$StringBean\",\"name\":\"punny\"}}", json);
}
public void testNoGoWithExternalProperty() throws Exception
@@ -348,7 +348,7 @@
JsonTypeInfo.As.EXTERNAL_PROPERTY);
fail("Should not have passed");
} catch (IllegalArgumentException e) {
- verifyException(e, "Can not use includeAs of EXTERNAL_PROPERTY");
+ verifyException(e, "Cannot use includeAs of EXTERNAL_PROPERTY");
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForScalars.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForScalars.java
similarity index 73%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForScalars.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForScalars.java
index c2a14fb..7bb7b5c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForScalars.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForScalars.java
@@ -1,10 +1,12 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import java.util.*;
import static org.junit.Assert.*;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
/**
* Unit tests to verify that Java/JSON scalar values (non-structured values)
@@ -18,9 +20,14 @@
public java.io.Serializable bar = new Integer(13);
}
+ // [databind#1395]: prevent attempts at including type info for primitives
+ static class Data {
+ public long key;
+ }
+
/*
/**********************************************************
- /* Unit tests
+ /* Test methods
/**********************************************************
*/
@@ -92,9 +99,6 @@
assertArrayEquals(input, output);
}
- /**
- * Loosely scalar; for [JACKSON-417]
- */
public void test417() throws Exception
{
ObjectMapper m = new ObjectMapper();
@@ -105,4 +109,31 @@
assertEquals(input.foo, result.foo);
assertEquals(input.bar, result.bar);
}
+
+ // [databind#1395]: prevent attempts at including type info for primitives
+ public void testDefaultTypingWithLong() throws Exception
+ {
+ Data data = new Data();
+ data.key = 1L;
+ Map<String, Object> mapData = new HashMap<String, Object>();
+ mapData.put("longInMap", 2L);
+ mapData.put("longAsField", data);
+
+ // Configure Jackson to preserve types
+ ObjectMapper mapper = new ObjectMapper();
+ StdTypeResolverBuilder resolver = new StdTypeResolverBuilder();
+ resolver.init(JsonTypeInfo.Id.CLASS, null);
+ resolver.inclusion(JsonTypeInfo.As.PROPERTY);
+ resolver.typeProperty("__t");
+ mapper.setDefaultTyping(resolver);
+ mapper.enable(SerializationFeature.INDENT_OUTPUT);
+
+ // Serialize
+ String json = mapper.writeValueAsString(mapData);
+
+ // Deserialize
+ Map<?,?> result = mapper.readValue(json, Map.class);
+ assertNotNull(result);
+ assertEquals(2, result.size());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForTreeNodes.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForTreeNodes.java
similarity index 95%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForTreeNodes.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForTreeNodes.java
index 83876db..39a2cee 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultForTreeNodes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForTreeNodes.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultWithCreators.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultWithCreators.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultWithCreators.java
rename to src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultWithCreators.java
index 785a3b5..b75b478 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultWithCreators.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultWithCreators.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.jsontype;
+package com.fasterxml.jackson.databind.jsontype.deftyping;
import org.junit.Assert;
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java
index 8913f7a..7537129 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java
@@ -294,10 +294,10 @@
ObjectMapper mapper = new ObjectMapper();
mapper.registerSubtypes(ValueBean.class);
// This may look odd, but one implementation nastiness is the fact
- // that we can not properly serialize type id before the object,
+ // that we cannot properly serialize type id before the object,
// because call is made after property name (for object) has already
// been written out. So we'll write it after...
- // Deserializer will work either way as it can not rely on ordering
+ // Deserializer will work either way as it cannot rely on ordering
// anyway.
assertEquals("{\"bean\":{\"value\":11},\"extType\":\"vbean\"}",
mapper.writeValueAsString(new ExternalBean(11)));
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithEnum1328Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithEnum1328Test.java
new file mode 100644
index 0000000..0ce3a65
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithEnum1328Test.java
@@ -0,0 +1,91 @@
+package com.fasterxml.jackson.databind.jsontype.ext;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
+import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
+
+public class ExternalTypeIdWithEnum1328Test extends BaseMapTest
+{
+ public interface Animal { }
+
+ public static class Dog implements Animal {
+ public String dogStuff;
+ }
+
+ public enum AnimalType {
+ Dog;
+ }
+
+ public static class AnimalAndType {
+ public AnimalType type;
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
+ include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+ property = "type")
+ @JsonTypeIdResolver(AnimalResolver.class)
+ private Animal animal;
+
+ public AnimalAndType() { }
+
+ // problem is this annotation
+ @java.beans.ConstructorProperties({"type", "animal"})
+ public AnimalAndType(final AnimalType type, final Animal animal) {
+ this.type = type;
+ this.animal = animal;
+ }
+ }
+
+ static class AnimalResolver implements TypeIdResolver {
+ @Override
+ public void init(JavaType bt) { }
+
+ @Override
+ public String idFromValue(Object value) {
+ return null;
+ }
+
+ @Override
+ public String idFromValueAndType(Object value, Class<?> suggestedType) {
+ return null;
+ }
+
+ @Override
+ public String idFromBaseType() {
+ throw new UnsupportedOperationException("Missing action type information - Can not construct");
+ }
+
+ @Override
+ public JavaType typeFromId(DatabindContext context, String id) throws IOException {
+ if (AnimalType.Dog.toString().equals(id)) {
+ return context.constructType(Dog.class);
+ }
+ throw new IllegalArgumentException("What is a " + id);
+ }
+
+ @Override
+ public String getDescForKnownTypeIds() {
+ return null;
+ }
+
+ @Override
+ public JsonTypeInfo.Id getMechanism() {
+ return JsonTypeInfo.Id.CUSTOM;
+ }
+ }
+
+ public void testExample() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+
+ String json = mapper.writerWithDefaultPrettyPrinter()
+ .writeValueAsString(Arrays.asList(new AnimalAndType(AnimalType.Dog, new Dog())));
+ List<AnimalAndType> list = mapper.readerFor(new TypeReference<List<AnimalAndType>>() { })
+ .readValue(json);
+ assertNotNull(list);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/MultipleExternalIds291Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/MultipleExternalIds291Test.java
new file mode 100644
index 0000000..b015dd8
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/MultipleExternalIds291Test.java
@@ -0,0 +1,111 @@
+package com.fasterxml.jackson.databind.jsontype.ext;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.databind.*;
+
+public class MultipleExternalIds291Test extends BaseMapTest
+{
+ // For [Issue#291]
+ interface F1 {}
+
+ static class A implements F1 {
+ public String a;
+ }
+
+ static class B implements F1 {
+ public String b;
+ }
+
+ static interface F2 {}
+
+ static class C implements F2 {
+ public String c;
+ }
+
+ static class D implements F2{
+ public String d;
+ }
+
+ static class Container {
+ @JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = A.class, name = "1"),
+ @JsonSubTypes.Type(value = B.class, name = "2")})
+ public F1 field1;
+
+ @JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = C.class, name = "1"),
+ @JsonSubTypes.Type(value = D.class, name = "2")})
+ public F2 field2;
+ }
+
+ static class ContainerWithExtra extends Container {
+ public String type;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ final ObjectMapper MAPPER = objectMapper();
+
+ // [databind#291]
+ public void testMultipleValuesSingleExtId() throws Exception
+ {
+ // first with ext-id before values
+ _testMultipleValuesSingleExtId(
+"{'type' : '1',\n"
++"'field1' : { 'a' : 'AAA' },\n"
++"'field2' : { 'c' : 'CCC' }\n"
++"}"
+);
+
+ // then after
+ _testMultipleValuesSingleExtId(
+"{\n"
++"'field1' : { 'a' : 'AAA' },\n"
++"'field2' : { 'c' : 'CCC' },\n"
++"'type' : '1'\n"
++"}"
+);
+ // and then in-between
+ _testMultipleValuesSingleExtId(
+"{\n"
++"'field1' : { 'a' : 'AAA' },\n"
++"'type' : '1',\n"
++"'field2' : { 'c' : 'CCC' }\n"
++"}"
+);
+ }
+
+ public void _testMultipleValuesSingleExtId(String json) throws Exception
+ {
+ json = aposToQuotes(json);
+
+ // First, with base class, no type id field separately
+ {
+ Container c = MAPPER.readValue(json, Container.class);
+ assertNotNull(c);
+ assertTrue(c.field1 instanceof A);
+ assertEquals("AAA", ((A) c.field1).a);
+ assertTrue(c.field2 instanceof C);
+ assertEquals("CCC", ((C) c.field2).c);
+ }
+
+ // then with sub-class that does have similarly named property
+ {
+ ContainerWithExtra c = MAPPER.readValue(json, ContainerWithExtra.class);
+ assertNotNull(c);
+ assertEquals("1", c.type);
+ assertTrue(c.field1 instanceof A);
+ assertEquals("AAA", ((A) c.field1).a);
+ assertTrue(c.field2 instanceof C);
+ assertEquals("CCC", ((C) c.field2).c);
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java
new file mode 100644
index 0000000..6e24114
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java
@@ -0,0 +1,247 @@
+package com.fasterxml.jackson.databind.jsontype.ext;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+// for [databind#2404]
+public class TestPropertyCreatorSubtypesExternalPropertyMissingProperty
+{
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ /**
+ * Base class - external property for Fruit subclasses.
+ */
+ static class Box {
+ private String type;
+ @JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "type")
+ @JsonSubTypes({
+ @Type(value = Apple.class, name = "apple"),
+ @Type(value = Orange.class, name = "orange")
+ })
+ private Fruit fruit;
+
+ private Box(String type, Fruit fruit) {
+ this.type = type;
+ this.fruit = fruit;
+ }
+
+ @JsonCreator
+ public static Box getBox(@JsonProperty("type") String type, @JsonProperty("fruit") Fruit fruit) {
+ return new Box(type, fruit);
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Fruit getFruit() {
+ return fruit;
+ }
+ }
+
+ static abstract class Fruit {
+ private String name;
+
+ protected Fruit(String n) {
+ name = n;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ static class Apple extends Fruit {
+ private int seedCount;
+
+ private Apple(String name, int b) {
+ super(name);
+ seedCount = b;
+ }
+
+ public int getSeedCount() {
+ return seedCount;
+ }
+
+ @JsonCreator
+ public static Apple getApple(@JsonProperty("name") String name, @JsonProperty("seedCount") int seedCount) {
+ return new Apple(name, seedCount);
+ }
+ }
+
+ static class Orange extends Fruit {
+ private String color;
+ private Orange(String name, String c) {
+ super(name);
+ color = c;
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+ @JsonCreator
+ public static Orange getOrange(@JsonProperty("name") String name, @JsonProperty("color") String color) {
+ return new Orange(name, color);
+ }
+ }
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ /*
+ /**********************************************************
+ /* Mock data
+ /**********************************************************
+ */
+
+ private static final Orange orange = new Orange("Orange", "orange");
+ private static final Box orangeBox = new Box("orange", orange);
+ private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}";
+ private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}";
+ private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}";
+ private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}";
+
+ private static final Apple apple = new Apple("Apple", 16);
+ private static Box appleBox = new Box("apple", apple);
+ private static final String appleBoxJson = "{\"type\":\"apple\",\"fruit\":{\"name\":\"Apple\",\"seedCount\":16}}";
+ private static final String appleBoxNullJson = "{\"type\":\"apple\",\"fruit\":null}";
+ private static final String appleBoxEmptyJson = "{\"type\":\"apple\",\"fruit\":{}}";
+ private static final String appleBoxMissingJson = "{\"type\":\"apple\"}";
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ /**
+ * Deserialization tests for external type id property present
+ */
+ @Test
+ public void testDeserializationPresent() throws Exception {
+ MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkOrangeBox();
+ checkAppleBox();
+
+ MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkOrangeBox();
+ checkAppleBox();
+ }
+
+ /**
+ * Deserialization tests for external type id property null
+ */
+ @Test
+ public void testDeserializationNull() throws Exception {
+ MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkOrangeBoxNull(orangeBoxNullJson);
+ checkAppleBoxNull(appleBoxNullJson);
+
+ MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkOrangeBoxNull(orangeBoxNullJson);
+ checkAppleBoxNull(appleBoxNullJson);
+ }
+
+ /**
+ * Deserialization tests for external type id property empty
+ */
+ @Test
+ public void testDeserializationEmpty() throws Exception {
+ MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkOrangeBoxEmpty(orangeBoxEmptyJson);
+ checkAppleBoxEmpty(appleBoxEmptyJson);
+
+ MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkOrangeBoxEmpty(orangeBoxEmptyJson);
+ checkAppleBoxEmpty(appleBoxEmptyJson);
+ }
+
+ /**
+ * Deserialization tests for external type id property missing
+ */
+ @Test
+ public void testDeserializationMissing() throws Exception {
+ MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkOrangeBoxNull(orangeBoxMissingJson);
+ checkAppleBoxNull(appleBoxMissingJson);
+
+ MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+ checkBoxJsonMappingException(orangeBoxMissingJson);
+ checkBoxJsonMappingException(appleBoxMissingJson);
+ }
+
+ private void checkOrangeBox() throws Exception {
+ Box deserOrangeBox = MAPPER.readValue(orangeBoxJson, Box.class);
+ assertEquals(orangeBox.getType(), deserOrangeBox.getType());
+
+ Fruit deserOrange = deserOrangeBox.getFruit();
+ assertSame(Orange.class, deserOrange.getClass());
+ assertEquals(orange.getName(), deserOrange.getName());
+ assertEquals(orange.getColor(), ((Orange) deserOrange).getColor());
+ }
+
+ private void checkAppleBox() throws Exception {
+ Box deserAppleBox = MAPPER.readValue(appleBoxJson, Box.class);
+ assertEquals(appleBox.getType(), deserAppleBox.getType());
+
+ Fruit deserApple = deserAppleBox.fruit;
+ assertSame(Apple.class, deserApple.getClass());
+ assertEquals(apple.getName(), deserApple.getName());
+ assertEquals(apple.getSeedCount(), ((Apple) deserApple).getSeedCount());
+ }
+
+ private void checkOrangeBoxEmpty(String json) throws Exception {
+ Box deserOrangeBox = MAPPER.readValue(json, Box.class);
+ assertEquals(orangeBox.getType(), deserOrangeBox.getType());
+
+ Fruit deserOrange = deserOrangeBox.getFruit();
+ assertSame(Orange.class, deserOrange.getClass());
+ assertNull(deserOrange.getName());
+ assertNull(((Orange) deserOrange).getColor());
+ }
+
+ private void checkAppleBoxEmpty(String json) throws Exception {
+ Box deserAppleBox = MAPPER.readValue(json, Box.class);
+ assertEquals(appleBox.getType(), deserAppleBox.getType());
+
+ Fruit deserApple = deserAppleBox.fruit;
+ assertSame(Apple.class, deserApple.getClass());
+ assertNull(deserApple.getName());
+ assertEquals(0, ((Apple) deserApple).getSeedCount());
+ }
+
+ private void checkOrangeBoxNull(String json) throws Exception {
+ Box deserOrangeBox = MAPPER.readValue(json, Box.class);
+ assertEquals(orangeBox.getType(), deserOrangeBox.getType());
+ assertNull(deserOrangeBox.getFruit());
+ }
+
+ private void checkAppleBoxNull(String json) throws Exception {
+ Box deserAppleBox = MAPPER.readValue(json, Box.class);
+ assertEquals(appleBox.getType(), deserAppleBox.getType());
+ assertNull(deserAppleBox.getFruit());
+ }
+
+ private void checkBoxJsonMappingException(String json) throws Exception {
+ thrown.expect(JsonMappingException.class);
+ thrown.expectMessage("Missing property 'fruit' for external type id 'type'");
+ MAPPER.readValue(json, Box.class);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/AccessFixTest.java b/src/test/java/com/fasterxml/jackson/databind/misc/AccessFixTest.java
index 64c4ca5..c53b9d3 100644
--- a/src/test/java/com/fasterxml/jackson/databind/misc/AccessFixTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/misc/AccessFixTest.java
@@ -14,7 +14,7 @@
@Override
public void checkPermission(Permission perm) throws SecurityException {
if ("suppressAccessChecks".equals(perm.getName())) {
- throw new SecurityException("Can not force permission: "+perm);
+ throw new SecurityException("Cannot force permission: "+perm);
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/BeanPropertyMapTest.java b/src/test/java/com/fasterxml/jackson/databind/misc/BeanPropertyMapTest.java
index eafa286..2240d5a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/misc/BeanPropertyMapTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/misc/BeanPropertyMapTest.java
@@ -31,7 +31,8 @@
PropertyMetadata md = PropertyMetadata.STD_REQUIRED;
props.add(new ObjectIdValueProperty(new MyObjectIdReader("pk"), md));
props.add(new ObjectIdValueProperty(new MyObjectIdReader("firstName"), md));
- BeanPropertyMap propMap = new BeanPropertyMap(false, props);
+ BeanPropertyMap propMap = new BeanPropertyMap(false, props,
+ new HashMap<String,List<PropertyName>>());
propMap = propMap.withProperty(new ObjectIdValueProperty(new MyObjectIdReader("@id"), md));
assertNotNull(propMap);
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitive1854Test.java b/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitive1854Test.java
new file mode 100644
index 0000000..10f5449
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitive1854Test.java
@@ -0,0 +1,67 @@
+package com.fasterxml.jackson.databind.misc;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.*;
+
+public class CaseInsensitive1854Test extends BaseMapTest
+{
+ static class Obj1854 {
+ private final int id;
+
+// 17-Dec-2017, tatu: One of following would work around the bug [databind#1854]
+// @JsonProperty("Items")
+// @JsonIgnore
+ private final List<ChildObj> items;
+
+ public Obj1854(int id, List<ChildObj> items) {
+ this.id = id;
+ this.items = items;
+ }
+
+ @JsonCreator
+ public static Obj1854 fromJson(@JsonProperty("ID") int id,
+ @JsonProperty("Items") List<ChildObj> items) {
+ return new Obj1854(id, items);
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public List<ChildObj> getItems() {
+ return items;
+ }
+
+ }
+
+ static class ChildObj {
+ private final String childId;
+
+ private ChildObj(String id) {
+ this.childId = id;
+ }
+
+ @JsonCreator
+ public static ChildObj fromJson(@JsonProperty("ChildID") String cid) {
+ return new ChildObj(cid);
+ }
+
+ public String getId() {
+ return childId;
+ }
+ }
+
+ public void testIssue1854() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
+ final String DOC = aposToQuotes("{'ID': 1, 'Items': [ { 'ChildID': 10 } ]}");
+ Obj1854 result = mapper.readValue(DOC, Obj1854.class);
+ assertNotNull(result);
+ assertEquals(1, result.getId());
+ assertNotNull(result.getItems());
+ assertEquals(1, result.getItems().size());
+ }
+}
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 66b9554..9ae53ca 100644
--- a/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitiveDeserTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/misc/CaseInsensitiveDeserTest.java
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.databind.misc;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
@@ -20,6 +21,18 @@
public String name, value;
}
+ // [databind#1232]: allow per-property case-insensitivity
+ static class Role {
+ public String ID;
+ public String Name;
+ }
+
+ static class CaseInsensitiveRoleWrapper
+ {
+ @JsonFormat(with={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
+ public Role role;
+ }
+
// [databind#1438]
static class InsensitiveCreator
{
@@ -37,6 +50,7 @@
/********************************************************
*/
+ private final ObjectMapper MAPPER = new ObjectMapper();
private final ObjectMapper INSENSITIVE_MAPPER = new ObjectMapper();
{
INSENSITIVE_MAPPER.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
@@ -78,6 +92,16 @@
assertEquals("Signature not valid!", response.debugMessage);
}
+ // [databind#1232]: allow per-property case-insensitivity
+ public void testCaseInsensitiveWithFormat() throws Exception {
+ CaseInsensitiveRoleWrapper w = MAPPER.readValue
+ (aposToQuotes("{'role':{'id':'12','name':'Foo'}}"),
+ CaseInsensitiveRoleWrapper.class);
+ assertNotNull(w);
+ assertEquals("12", w.role.ID);
+ assertEquals("Foo", w.role.Name);
+ }
+
// [databind#1438]
public void testCreatorWithInsensitive() throws Exception
{
@@ -85,4 +109,19 @@
InsensitiveCreator bean = INSENSITIVE_MAPPER.readValue(json, InsensitiveCreator.class);
assertEquals(3, bean.v);
}
+
+ // And allow config overrides too
+ public void testCaseInsensitiveWithClassFormat() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(Role.class)
+ .setFormat(JsonFormat.Value.empty()
+ .withFeature(JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
+ Role role = mapper.readValue
+ (aposToQuotes("{'id':'12','name':'Foo'}"),
+ Role.class);
+ assertNotNull(role);
+ assertEquals("12", role.ID);
+ assertEquals("Foo", role.Name);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/RaceCondition738Test.java b/src/test/java/com/fasterxml/jackson/databind/misc/RaceCondition738Test.java
index 9168b25..bb5bb34 100644
--- a/src/test/java/com/fasterxml/jackson/databind/misc/RaceCondition738Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/misc/RaceCondition738Test.java
@@ -56,14 +56,14 @@
*/
public void testRepeatedly() throws Exception {
- final int COUNT = 2000;
+ final int COUNT = 3000;
for (int i = 0; i < COUNT; i++) {
runOnce(i, COUNT);
}
}
void runOnce(int round, int max) throws Exception {
- final ObjectMapper mapper = getObjectMapper();
+ final ObjectMapper mapper = newObjectMapper();
Callable<String> writeJson = new Callable<String>() {
@Override
public String call() throws Exception {
@@ -92,8 +92,4 @@
}
}
}
-
- private static ObjectMapper getObjectMapper() {
- return new ObjectMapper();
- }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/ThreadSafety1759Test.java b/src/test/java/com/fasterxml/jackson/databind/misc/ThreadSafety1759Test.java
new file mode 100644
index 0000000..236629f
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/misc/ThreadSafety1759Test.java
@@ -0,0 +1,71 @@
+package com.fasterxml.jackson.databind.misc;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.fasterxml.jackson.databind.*;
+
+public class ThreadSafety1759Test extends BaseMapTest
+{
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testCalendarForDeser() throws Exception
+ {
+ final ObjectMapper mapper = newObjectMapper();
+
+ final int numThreads = 4;
+ final int COUNT = 3000;
+ final AtomicInteger counter = new AtomicInteger();
+
+ // IMPORTANT! Use different timestamp for every thread
+ List<Callable<Throwable>> calls = new ArrayList<Callable<Throwable>>();
+ for (int thread = 1; thread <= numThreads; ++thread) {
+ final String json = quote(String.format("2017-01-%02dT16:30:49Z", thread));
+ final long timestamp = mapper.readValue(json, Date.class).getTime();
+
+ calls.add(createCallable(thread, mapper, json, timestamp, COUNT, counter));
+ }
+
+ ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+ List<Future<Throwable>> results = new ArrayList<>();
+ for (Callable<Throwable> c : calls) {
+ results.add(executor.submit(c));
+ }
+ executor.shutdown();
+ for (Future<Throwable> f : results) {
+ Throwable t = f.get(5, TimeUnit.SECONDS);
+ if (t != null) {
+ fail("Exception during processing: "+t.getMessage());
+ }
+ }
+ assertEquals(numThreads * COUNT, counter.get());
+ }
+
+ private Callable<Throwable> createCallable(final int threadId,
+ final ObjectMapper mapper,
+ final String json, final long timestamp,
+ final int count, final AtomicInteger counter)
+ {
+ return new Callable<Throwable>() {
+ @Override
+ public Throwable call() throws IOException {
+ for (int i = 0; i < count; ++i) {
+ Date dt = mapper.readValue(json, Date.class);
+ if (dt.getTime() != timestamp) {
+ return new IllegalArgumentException("Wrong timestamp (thread id "+threadId+", input: "+json+": should get "+timestamp+", got "+dt.getTime());
+ }
+ counter.addAndGet(1);
+ }
+ return null;
+ }
+ };
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/mixins/MapperMixinsCopy1998Test.java b/src/test/java/com/fasterxml/jackson/databind/mixins/MapperMixinsCopy1998Test.java
new file mode 100644
index 0000000..32897e6
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/mixins/MapperMixinsCopy1998Test.java
@@ -0,0 +1,127 @@
+package com.fasterxml.jackson.databind.mixins;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+
+public class MapperMixinsCopy1998Test extends BaseMapTest
+{
+ final static String FULLMODEL="{\"format\":\"1.0\",\"child\":{\"type\":\"CHILD_B\",\"name\":\"testB\"},\"notVisible\":\"should not be present\"}";
+ final static String EXPECTED="{\"format\":\"1.0\",\"child\":{\"name\":\"testB\"}}";
+
+ static class MyModelView { }
+
+ interface MixinConfig {
+ interface MyModelRoot {
+ @JsonView(MyModelView.class)
+ public String getFormat();
+
+ @JsonView(MyModelView.class)
+ public MyModelChildBase getChild();
+ }
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.EXISTING_PROPERTY)
+ interface MyModelChildBase {
+ @JsonView(MyModelView.class)
+ public String getName();
+ }
+
+ }
+
+ @JsonPropertyOrder({ "format", "child" })
+ static class MyModelRoot {
+ @JsonProperty
+ private String format = "1.0";
+
+ public String getFormat() {
+ return format;
+ }
+ @JsonProperty
+ private MyModelChildBase child;
+
+ public MyModelChildBase getChild() {
+ return child;
+ }
+
+ public void setChild(MyModelChildBase child) {
+ this.child = child;
+ }
+
+ @JsonProperty
+ private String notVisible = "should not be present";
+
+ public String getNotVisible() {
+ return notVisible;
+ }
+ }
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = MyChildA.class, name = "CHILD_A"),
+ @JsonSubTypes.Type(value = MyChildB.class, name = "CHILD_B")
+ })
+ abstract static class MyModelChildBase {
+ @JsonProperty
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ @JsonIgnore
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ static class MyChildA extends MyModelChildBase {
+ public MyChildA(String name) {
+ setName(name);
+ }
+ }
+
+ static class MyChildB extends MyModelChildBase {
+ public MyChildB(String name) {
+ setName(name);
+ }
+ }
+
+ public void testB_KO() throws Exception
+ {
+ final ObjectMapper DEFAULT = defaultMapper();
+ MyModelRoot myModelInstance = new MyModelRoot();
+ myModelInstance.setChild(new MyChildB("testB"));
+
+ ObjectMapper myObjectMapper = DEFAULT.copy();
+
+ String postResult = getString(myModelInstance, myObjectMapper);
+ assertEquals(FULLMODEL, postResult);
+// System.out.println("postResult: "+postResult);
+
+ myObjectMapper = DEFAULT.copy();
+// myObjectMapper = defaultMapper();
+ myObjectMapper.addMixIn(MyModelRoot.class, MixinConfig.MyModelRoot.class)
+ .addMixIn(MyModelChildBase.class, MixinConfig.MyModelChildBase.class)
+ .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
+ .setConfig(myObjectMapper.getSerializationConfig().withView(MyModelView.class));
+
+ String result = getString(myModelInstance, myObjectMapper);
+ assertEquals(EXPECTED, result);
+
+ }
+
+ private String getString(MyModelRoot myModelInstance, ObjectMapper myObjectMapper) throws IOException {
+ return myObjectMapper.writerFor(MyModelRoot.class).writeValueAsString(myModelInstance);
+ }
+
+ private ObjectMapper defaultMapper()
+ {
+ return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
+ .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
+ .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ ;
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForClass.java b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForClass.java
index d36132f..50e1d1c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForClass.java
+++ b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForClass.java
@@ -10,12 +10,6 @@
public class TestMixinDeserForClass
extends BaseMapTest
{
- /*
- /**********************************************************
- /* Helper bean classes
- /**********************************************************
- */
-
static class BaseClass
{
/* property that is always found; but has lower priority than
@@ -35,6 +29,21 @@
@JsonAutoDetect(setterVisibility=Visibility.NONE, fieldVisibility=Visibility.NONE)
interface MixIn { }
+ // [databind#1990]
+ public interface HashCodeMixIn {
+ @Override
+ @JsonProperty
+ public int hashCode();
+ }
+
+ public class Bean1990WithoutHashCode {
+ }
+
+ public class Bean1990WithHashCode {
+ @Override
+ public int hashCode() { return 13; }
+ }
+
/*
/**********************************************************
/* Unit tests
@@ -48,18 +57,16 @@
LeafClass result = m.readValue("{\"a\":\"value\"}", LeafClass.class);
assertEquals("XXXvalue", result.a);
- /* Then with leaf-level mix-in; without (method) auto-detect, should
- * use field
- */
+ // Then with leaf-level mix-in; without (method) auto-detect,
+ // should use field
m = new ObjectMapper();
m.addMixIn(LeafClass.class, MixIn.class);
result = m.readValue("{\"a\":\"value\"}", LeafClass.class);
assertEquals("value", result.a);
}
- /* and then a test for mid-level mixin; should have no effect
- * when deserializing leaf (but will if deserializing base class)
- */
+ // and then a test for mid-level mixin; should have no effect
+ // when deserializing leaf (but will if deserializing base class)
public void testClassMixInsMidLevel() throws IOException
{
ObjectMapper m = new ObjectMapper();
@@ -95,4 +102,22 @@
assertEquals("XXX", result.a);
}
}
+
+ // [databind#1990]: can apply mix-in to `Object#hashCode()` to force serialization
+ public void testHashCodeViaObject() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper()
+ .addMixIn(Object.class, HashCodeMixIn.class);
+
+ // First, with something that overrides hashCode()
+ assertEquals( "{\"hashCode\":13}",
+ mapper.writeValueAsString(new Bean1990WithHashCode()));
+
+ // and then special case of accessing Object#hashCode()
+ String prefix = "{\"hashCode\":";
+ String json = mapper.writeValueAsString(new Bean1990WithoutHashCode());
+ if (!json.startsWith(prefix)) {
+ fail("Should start with ["+prefix+"], does not: ["+json+"]");
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForCreators.java b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForCreators.java
index 5118711..22c2510 100644
--- a/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForCreators.java
+++ b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinDeserForCreators.java
@@ -68,7 +68,7 @@
private StringWrapper(String s, boolean foo) { _value = s; }
@SuppressWarnings("unused")
- private static StringWrapper create(String str) {
+ private static StringWrapper create(String str) {
return new StringWrapper(str, false);
}
}
@@ -77,6 +77,32 @@
@JsonCreator static StringWrapper create(String str) { return null; }
}
+ // [databind#2020]
+ static class Pair2020 {
+ final int x, y;
+
+ private Pair2020(int x0, int y0) {
+ x = x0;
+ y = y0;
+ }
+
+ static Pair2020 with(Object x0, Object y0) {
+ return new Pair2020(((Number) x0).intValue(),
+ ((Number) y0).intValue());
+ }
+ }
+
+ @JsonIgnoreProperties("size")
+ static class MyPairMixIn8 { // with and without <Long, String>
+ @JsonCreator
+ static TestMixinDeserForCreators.Pair2020 with(@JsonProperty("value0") Object value0,
+ @JsonProperty("value1") Object value1)
+ {
+ // body does not matter, only signature
+ return null;
+ }
+ }
+
/*
/**********************************************************
/* Unit tests
@@ -107,11 +133,23 @@
assertEquals("stringX", result._a);
}
- public void testFactoryMixIn() throws IOException
+ public void testFactoryDelegateMixIn() throws IOException
{
ObjectMapper m = new ObjectMapper();
m.addMixIn(StringWrapper.class, StringWrapperMixIn.class);
StringWrapper result = m.readValue("\"a\"", StringWrapper.class);
assertEquals("a", result._value);
}
+
+ // [databind#2020]
+ public void testFactoryPropertyMixin() throws Exception
+ {
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.addMixIn(Pair2020.class, MyPairMixIn8.class);
+
+ String doc = aposToQuotes( "{'value0' : 456, 'value1' : 789}");
+ Pair2020 pair2 = objectMapper.readValue(doc, Pair2020.class);
+ assertEquals(456, pair2.x);
+ assertEquals(789, pair2.y);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestMixinMerging.java b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinMerging.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/introspect/TestMixinMerging.java
rename to src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinMerging.java
index 2595500..76230de 100644
--- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestMixinMerging.java
+++ b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinMerging.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.introspect;
+package com.fasterxml.jackson.databind.mixins;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinSerForMethods.java b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinSerForMethods.java
index 3ab6ce1..a57cffe 100644
--- a/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinSerForMethods.java
+++ b/src/test/java/com/fasterxml/jackson/databind/mixins/TestMixinSerForMethods.java
@@ -62,13 +62,6 @@
@JsonIgnore
public String takeB() { return null; }
}
-
- interface ObjectMixIn
- {
- // and then ditto for hashCode..
- @Override
- @JsonProperty public int hashCode();
- }
static class EmptyBean { }
@@ -154,40 +147,6 @@
assertEquals(Integer.valueOf(42), result.get("x"));
}
- /**
- * Unit test for verifying that it is actually possible to attach
- * mix-in annotations to basic <code>Object.class</code>. This
- * will essentially apply to any and all Objects.
- */
- public void testObjectMixin() throws IOException
- {
- ObjectMapper mapper = new ObjectMapper();
- mapper.addMixIn(Object.class, ObjectMixIn.class);
-
- // First, with our bean...
- Map<String,Object> result = writeAndMap(mapper, new BaseClass("a", "b"));
-
- assertEquals(2, result.size());
- assertEquals("b", result.get("b"));
- Object ob = result.get("hashCode");
- assertNotNull(ob);
- assertEquals(Integer.class, ob.getClass());
-
- /* 15-Oct-2010, tatu: Actually, we now block serialization (attempts) of plain Objects, by default
- * (since generally that makes no sense -- may need to revisit). As such, need to comment out
- * this part of test
- */
- /* Hmmh. For plain Object.class... I suppose getClass() does
- * get serialized (and can't really be blocked either).
- * Fine.
- */
- result = writeAndMap(mapper, new BaseClass("a", "b"));
- assertEquals(2, result.size());
- ob = result.get("hashCode");
- assertNotNull(ob);
- assertEquals(Integer.class, ob.getClass());
- }
-
// [databind#688]
public void testCustomResolver() throws IOException
{
@@ -195,8 +154,8 @@
mapper.setMixInResolver(new ClassIntrospector.MixInResolver() {
@Override
public Class<?> findMixInClassFor(Class<?> target) {
- if (target == BaseClass.class) {
- return ObjectMixIn.class;
+ if (target == EmptyBean.class) {
+ return MixInForSimple.class;
}
return null;
}
@@ -206,10 +165,8 @@
return this;
}
});
- Map<String,Object> result = writeAndMap(mapper, new BaseClass("c", "d"));
- assertEquals(2, result.size());
- assertNotNull(result.get("hashCode"));
- assertTrue(result.containsKey("b"));
- assertFalse(result.containsKey("a"));
+ Map<String,Object> result = writeAndMap(mapper, new SimpleBean());
+ assertEquals(1, result.size());
+ assertEquals(Integer.valueOf(42), result.get("x"));
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleArgCheckTest.java b/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleArgCheckTest.java
new file mode 100644
index 0000000..8de27cd
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleArgCheckTest.java
@@ -0,0 +1,154 @@
+package com.fasterxml.jackson.databind.module;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.NamedType;
+
+public class SimpleModuleArgCheckTest extends BaseMapTest
+{
+ /*
+ /**********************************************************
+ /* Unit tests for invalid deserializers
+ /**********************************************************
+ */
+
+ public void testInvalidForDeserializers() throws Exception
+ {
+ SimpleModule mod = new SimpleModule("test", Version.unknownVersion(),
+ (Map<Class<?>,JsonDeserializer<?>>) null);
+
+ try {
+ mod.addDeserializer(String.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as deserializer");
+ }
+
+ try {
+ mod.addKeyDeserializer(String.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as key deserializer");
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests for invalid serializers
+ /**********************************************************
+ */
+
+ public void testInvalidForSerializers() throws Exception
+ {
+ SimpleModule mod = new SimpleModule("test", Version.unknownVersion(),
+ (List<JsonSerializer<?>>) null);
+
+ try {
+ mod.addSerializer(String.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as serializer");
+ }
+
+ try {
+ mod.addSerializer((JsonSerializer<?>) null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as serializer");
+ }
+
+ try {
+ mod.addKeySerializer(String.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as key serializer");
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests for invalid misc other
+ /**********************************************************
+ */
+
+ public void testInvalidAbstractTypeMapping() throws Exception
+ {
+ // just for funsies let's use more esoteric constructor
+ Map<Class<?>,JsonDeserializer<?>> desers = Collections.emptyMap();
+ List<JsonSerializer<?>> sers = Collections.emptyList();
+ SimpleModule mod = new SimpleModule("test", Version.unknownVersion(),
+ desers, sers);
+
+ try {
+ mod.addAbstractTypeMapping(null, String.class);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as abstract type to map");
+ }
+ try {
+ mod.addAbstractTypeMapping(String.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as concrete type to map to");
+ }
+ }
+
+ public void testInvalidSubtypeMappings() throws Exception
+ {
+ SimpleModule mod = new SimpleModule("test", Version.unknownVersion(),
+ null, null);
+ try {
+ mod.registerSubtypes(String.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as subtype to register");
+ }
+
+ try {
+ mod.registerSubtypes(new NamedType(Integer.class), (NamedType) null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as subtype to register");
+ }
+ }
+
+ public void testInvalidValueInstantiator() throws Exception
+ {
+ SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
+
+ try {
+ mod.addValueInstantiator(null, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as class to register value instantiator for");
+ }
+ try {
+ mod.addValueInstantiator(CharSequence.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as value instantiator");
+ }
+ }
+
+ public void testInvalidMixIn() throws Exception
+ {
+ SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
+
+ try {
+ mod.setMixInAnnotation(null, String.class);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as target type");
+ }
+ try {
+ mod.setMixInAnnotation(String.class, null);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot pass `null` as mixin class");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestSimpleModule.java b/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleTest.java
similarity index 85%
rename from src/test/java/com/fasterxml/jackson/databind/module/TestSimpleModule.java
rename to src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleTest.java
index 375b41c..da9e7dc 100644
--- a/src/test/java/com/fasterxml/jackson/databind/module/TestSimpleModule.java
+++ b/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleTest.java
@@ -11,18 +11,13 @@
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
+
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
@SuppressWarnings("serial")
-public class TestSimpleModule extends BaseMapTest
+public class SimpleModuleTest extends BaseMapTest
{
- /*
- /**********************************************************
- /* Helper classes; simple beans and their handlers
- /**********************************************************
- */
-
/**
* Trivial bean that requires custom serializer and deserializer
*/
@@ -151,7 +146,18 @@
}
}
- protected static class ContextVerifierModule extends Module
+ /**
+ * Test module that is different from MySimpleModule. Used to test registration
+ * of multiple modules.
+ */
+ protected static class AnotherSimpleModule extends SimpleModule
+ {
+ public AnotherSimpleModule(String name, Version version) {
+ super(name, version);
+ }
+ }
+
+ protected static class ContextVerifierModule extends com.fasterxml.jackson.databind.Module
{
@Override
public String getModuleName() { return "x"; }
@@ -210,7 +216,8 @@
mapper.readValue("{\"str\":\"ab\",\"num\":2}", CustomBean.class);
fail("Should have caused an exception");
} catch (IOException e) {
- verifyException(e, "No suitable constructor found");
+ verifyException(e, "Cannot construct");
+ verifyException(e, "no creators");
}
}
@@ -234,17 +241,19 @@
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
mod.addSerializer(new SimpleEnumSerializer());
- mapper.registerModule(mod);
+ // for fun, call "multi-module" registration
+ mapper.registerModules(mod);
assertEquals(quote("b"), mapper.writeValueAsString(SimpleEnum.B));
}
- // for [JACKSON-550]
public void testSimpleInterfaceSerializer() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
mod.addSerializer(new BaseSerializer());
- mapper.registerModule(mod);
+ // and another variant here too
+ List<SimpleModule> mods = Arrays.asList(mod);
+ mapper.registerModules(mods);
assertEquals(quote("Base:1"), mapper.writeValueAsString(new Impl1()));
assertEquals(quote("Base:2"), mapper.writeValueAsString(new Impl2()));
}
@@ -275,15 +284,17 @@
SimpleEnum result = mapper.readValue(quote("a"), SimpleEnum.class);
assertSame(SimpleEnum.A, result);
}
-
- // Simple verification of [JACKSON-455]
+
public void testMultipleModules() throws Exception
{
MySimpleModule mod1 = new MySimpleModule("test1", Version.unknownVersion());
SimpleModule mod2 = new SimpleModule("test2", Version.unknownVersion());
mod1.addSerializer(SimpleEnum.class, new SimpleEnumSerializer());
mod1.addDeserializer(CustomBean.class, new CustomBeanDeserializer());
- mod2.addDeserializer(SimpleEnum.class, new SimpleEnumDeserializer());
+
+ Map<Class<?>,JsonDeserializer<?>> desers = new HashMap<>();
+ desers.put(SimpleEnum.class, new SimpleEnumDeserializer());
+ mod2.setDeserializers(new SimpleDeserializers(desers));
mod2.addSerializer(CustomBean.class, new CustomBeanSerializer());
ObjectMapper mapper = new ObjectMapper();
@@ -302,13 +313,32 @@
assertSame(SimpleEnum.A, result);
}
+ public void testGetRegisteredModules()
+ {
+ MySimpleModule mod1 = new MySimpleModule("test1", Version.unknownVersion());
+ AnotherSimpleModule mod2 = new AnotherSimpleModule("test2", Version.unknownVersion());
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ mapper.registerModule(mod1);
+ mapper.registerModule(mod2);
+
+ Set<Object> registeredModuleIds = mapper.getRegisteredModuleIds();
+ assertEquals(2, registeredModuleIds.size());
+ assertTrue(registeredModuleIds.contains(mod1.getTypeId()));
+ assertTrue(registeredModuleIds.contains(mod2.getTypeId()));
+
+ // 01-Jul-2019, [databind#2374]: verify empty list is fine
+ mapper = new ObjectMapper();
+ assertEquals(0, mapper.getRegisteredModuleIds().size());
+ }
+
/*
/**********************************************************
/* Unit tests; other
/**********************************************************
*/
-
- // [JACKSON-644]: ability to register mix-ins
+
public void testMixIns() throws Exception
{
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
@@ -322,7 +352,6 @@
assertEquals(Integer.valueOf(2), props.get("b"));
}
- // [JACKSON-686]
public void testAccessToMapper() throws Exception
{
ContextVerifierModule module = new ContextVerifierModule();
@@ -339,4 +368,10 @@
Class<?> found = mapper.findMixInClassFor(Object.class);
assertEquals(String.class, found);
}
+
+ public void testAutoDiscovery() throws Exception
+ {
+ List<?> mods = ObjectMapper.findModules();
+ assertEquals(0, mods.size());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestAbstractTypes.java b/src/test/java/com/fasterxml/jackson/databind/module/TestAbstractTypes.java
index 76402b0..f007b91 100644
--- a/src/test/java/com/fasterxml/jackson/databind/module/TestAbstractTypes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/module/TestAbstractTypes.java
@@ -2,6 +2,8 @@
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -38,10 +40,48 @@
public int getValue() { return 3; }
}
+ // [databind#2019]: mappings from multiple modules
+ public interface Datatype1 {
+ String getValue();
+ }
+
+ public interface Datatype2 {
+ String getValue();
+ }
+
+ static class SimpleDatatype1 implements Datatype1 {
+
+ private final String value;
+
+ @JsonCreator
+ public SimpleDatatype1(@JsonProperty("value") String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+ }
+
+ static class SimpleDatatype2 implements Datatype2 {
+ private final String value;
+
+ @JsonCreator
+ public SimpleDatatype2(@JsonProperty("value") String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+ }
+
/*
- /**********************************************************
+ /**********************************************************************
/* Test methods
- /**********************************************************
+ /**********************************************************************
*/
public void testCollectionDefaulting() throws Exception
@@ -115,4 +155,23 @@
Abstract a = mapper.readValue("{}", Abstract.class);
assertNotNull(a);
}
+
+ // [databind#2019]: mappings from multiple modules
+ public static void testAbstractMappingsFromTwoModules() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper();
+ SimpleModule module1 = new SimpleModule("module1");
+ module1.addAbstractTypeMapping(Datatype1.class, SimpleDatatype1.class);
+
+ SimpleModule module2 = new SimpleModule("module2");
+ module2.addAbstractTypeMapping(Datatype2.class, SimpleDatatype2.class);
+ mapper.registerModules(module1, module2);
+
+ final String JSON_EXAMPLE = "{\"value\": \"aaa\"}";
+ Datatype1 value1 = mapper.readValue(JSON_EXAMPLE, Datatype1.class);
+ assertNotNull(value1);
+
+ Datatype2 value2 = mapper.readValue(JSON_EXAMPLE, Datatype2.class);
+ assertNotNull(value2);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestCustomEnumKeyDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/module/TestCustomEnumKeyDeserializer.java
index f99e320..bc5a53c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/module/TestCustomEnumKeyDeserializer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/module/TestCustomEnumKeyDeserializer.java
@@ -4,8 +4,6 @@
import java.io.IOException;
import java.util.*;
-import org.junit.Test;
-
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -169,7 +167,6 @@
*/
// Test passing with the fix
- @Test
public void testWithEnumKeys() throws Exception {
ObjectMapper plainObjectMapper = new ObjectMapper();
JsonNode tree = plainObjectMapper.readTree(aposToQuotes("{'red' : [ 'a', 'b']}"));
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java b/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java
index 171b867..9adca9c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java
+++ b/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java
@@ -5,7 +5,7 @@
public class TestDuplicateRegistration extends BaseMapTest
{
- static class MyModule extends Module {
+ static class MyModule extends com.fasterxml.jackson.databind.Module {
public int regCount;
public MyModule() {
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestKeyDeserializers.java b/src/test/java/com/fasterxml/jackson/databind/module/TestKeyDeserializers.java
index a3f2227..a9bd0d8 100644
--- a/src/test/java/com/fasterxml/jackson/databind/module/TestKeyDeserializers.java
+++ b/src/test/java/com/fasterxml/jackson/databind/module/TestKeyDeserializers.java
@@ -4,10 +4,7 @@
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.BaseMapTest;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.KeyDeserializer;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.*;
public class TestKeyDeserializers extends BaseMapTest
{
@@ -25,7 +22,6 @@
public Foo(String v) { value = v; }
}
-
/*
/**********************************************************
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestTypeModifiers.java b/src/test/java/com/fasterxml/jackson/databind/module/TestTypeModifiers.java
index 27ac41e..e1c4856 100644
--- a/src/test/java/com/fasterxml/jackson/databind/module/TestTypeModifiers.java
+++ b/src/test/java/com/fasterxml/jackson/databind/module/TestTypeModifiers.java
@@ -230,7 +230,21 @@
assertNotNull(param);
assertSame(Integer.class, param.getRawClass());
}
-
+
+ // [databind#2395] Can trigger problem this way too
+ // NOTE: oddly enough, seems to ONLY fail
+ public void testTypeResolutionForRecursive() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new SimpleModule() {
+ @Override
+ public void setupModule(SetupContext context) {
+ context.addTypeModifier(new MyTypeModifier());
+ }
+ });
+ assertNotNull(mapper.readTree("{}"));
+ }
+
public void testCollectionLikeTypeConstruction() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java
new file mode 100644
index 0000000..9a1a59f
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/node/ArrayNodeTest.java
@@ -0,0 +1,341 @@
+package com.fasterxml.jackson.databind.node;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.fasterxml.jackson.databind.node.TreeTraversingParser;
+
+/**
+ * Additional tests for {@link ArrayNode} container class.
+ */
+public class ArrayNodeTest
+ extends BaseMapTest
+{
+ public void testDirectCreation() throws IOException
+ {
+ ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
+ assertStandardEquals(n);
+ assertFalse(n.elements().hasNext());
+ assertFalse(n.fieldNames().hasNext());
+ TextNode text = TextNode.valueOf("x");
+ n.add(text);
+ assertEquals(1, n.size());
+ assertFalse(0 == n.hashCode());
+ assertTrue(n.elements().hasNext());
+ // no field names for arrays
+ assertFalse(n.fieldNames().hasNext());
+ assertNull(n.get("x")); // not used with arrays
+ assertTrue(n.path("x").isMissingNode());
+ assertSame(text, n.get(0));
+
+ // single element, so:
+ assertFalse(n.has("field"));
+ assertFalse(n.hasNonNull("field"));
+ assertTrue(n.has(0));
+ assertTrue(n.hasNonNull(0));
+ assertFalse(n.has(1));
+ assertFalse(n.hasNonNull(1));
+
+ // add null node too
+ n.add((JsonNode) null);
+ assertEquals(2, n.size());
+ assertTrue(n.get(1).isNull());
+ assertTrue(n.has(1));
+ assertFalse(n.hasNonNull(1));
+ // change to text
+ n.set(1, text);
+ assertSame(text, n.get(1));
+ n.set(0, null);
+ assertTrue(n.get(0).isNull());
+
+ // and finally, clear it all
+ ArrayNode n2 = new ArrayNode(JsonNodeFactory.instance);
+ n2.add("foobar");
+ assertFalse(n.equals(n2));
+ n.addAll(n2);
+ assertEquals(3, n.size());
+
+ assertFalse(n.get(0).isTextual());
+ assertNotNull(n.remove(0));
+ assertEquals(2, n.size());
+ assertTrue(n.get(0).isTextual());
+ assertNull(n.remove(-1));
+ assertNull(n.remove(100));
+ assertEquals(2, n.size());
+
+ ArrayList<JsonNode> nodes = new ArrayList<JsonNode>();
+ nodes.add(text);
+ n.addAll(nodes);
+ assertEquals(3, n.size());
+ assertNull(n.get(10000));
+ assertNull(n.remove(-4));
+
+ TextNode text2 = TextNode.valueOf("b");
+ n.insert(0, text2);
+ assertEquals(4, n.size());
+ assertSame(text2, n.get(0));
+
+ assertNotNull(n.addArray());
+ assertEquals(5, n.size());
+ n.addPOJO("foo");
+ assertEquals(6, n.size());
+
+ n.removeAll();
+ assertEquals(0, n.size());
+ }
+
+ public void testDirectCreation2() throws IOException
+ {
+ JsonNodeFactory f = objectMapper().getNodeFactory();
+ ArrayList<JsonNode> list = new ArrayList<>();
+ list.add(f.booleanNode(true));
+ list.add(f.textNode("foo"));
+ ArrayNode n = new ArrayNode(f, list);
+ assertEquals(2, n.size());
+ assertTrue(n.get(0).isBoolean());
+ assertTrue(n.get(1).isTextual());
+
+ // also, should fail with invalid set attempt
+ try {
+ n.set(2, f.nullNode());
+ fail("Should not pass");
+ } catch (IndexOutOfBoundsException e) {
+ verifyException(e, "illegal index");
+ }
+ n.insert(1, (String) null);
+ assertEquals(3, n.size());
+ assertTrue(n.get(0).isBoolean());
+ assertTrue(n.get(1).isNull());
+ assertTrue(n.get(2).isTextual());
+
+ n.removeAll();
+ n.insert(0, (JsonNode) null);
+ assertEquals(1, n.size());
+ assertTrue(n.get(0).isNull());
+ }
+
+ public void testArrayViaMapper() throws Exception
+ {
+ final String JSON = "[[[-0.027512,51.503221],[-0.008497,51.503221],[-0.008497,51.509744],[-0.027512,51.509744]]]";
+
+ JsonNode n = objectMapper().readTree(JSON);
+ assertNotNull(n);
+ assertTrue(n.isArray());
+ ArrayNode an = (ArrayNode) n;
+ assertEquals(1, an.size());
+ ArrayNode an2 = (ArrayNode) n.get(0);
+ assertTrue(an2.isArray());
+ assertEquals(4, an2.size());
+ }
+
+ public void testAdds()
+ {
+ ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
+ assertNotNull(n.addArray());
+ assertNotNull(n.addObject());
+ n.addPOJO("foobar");
+ n.add(1);
+ n.add(1L);
+ n.add(0.5);
+ n.add(0.5f);
+ n.add(new BigDecimal("0.2"));
+ n.add(BigInteger.TEN);
+ assertEquals(9, n.size());
+
+ assertNotNull(n.insertArray(0));
+ assertNotNull(n.insertObject(0));
+ n.insertPOJO(2, "xxx");
+ assertEquals(12, n.size());
+
+ n.insert(0, BigInteger.ONE);
+ n.insert(0, new BigDecimal("0.1"));
+ assertEquals(14, n.size());
+ }
+
+ public void testNullAdds()
+ {
+ JsonNodeFactory f = objectMapper().getNodeFactory();
+ ArrayNode array = f.arrayNode(14);
+
+ array.add((BigDecimal) null);
+ array.add((BigInteger) null);
+ array.add((Boolean) null);
+ array.add((byte[]) null);
+ array.add((Double) null);
+ array.add((Float) null);
+ array.add((Integer) null);
+ array.add((JsonNode) null);
+ array.add((Long) null);
+ array.add((String) null);
+
+ assertEquals(10, array.size());
+
+ for (JsonNode node : array) {
+ assertTrue(node.isNull());
+ }
+ }
+
+ public void testNullInserts()
+ {
+ JsonNodeFactory f = objectMapper().getNodeFactory();
+ ArrayNode array = f.arrayNode(3);
+
+ array.insert(0, (BigDecimal) null);
+ array.insert(0, (BigInteger) null);
+ array.insert(0, (Boolean) null);
+ // Offsets out of the range are fine; negative become 0;
+ // super big just add at the end
+ array.insert(-56, (byte[]) null);
+ array.insert(0, (Double) null);
+ array.insert(200, (Float) null);
+ array.insert(0, (Integer) null);
+ array.insert(1, (JsonNode) null);
+ array.insert(array.size(), (Long) null);
+ array.insert(1, (String) null);
+
+ assertEquals(10, array.size());
+
+ for (JsonNode node : array) {
+ assertTrue(node.isNull());
+ }
+ }
+
+ public void testNullChecking()
+ {
+ ArrayNode a1 = JsonNodeFactory.instance.arrayNode();
+ ArrayNode a2 = JsonNodeFactory.instance.arrayNode();
+ // used to throw NPE before fix:
+ a1.addAll(a2);
+ assertEquals(0, a1.size());
+ assertEquals(0, a2.size());
+
+ a2.addAll(a1);
+ assertEquals(0, a1.size());
+ assertEquals(0, a2.size());
+ }
+
+ public void testNullChecking2()
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode src = mapper.createArrayNode();
+ ArrayNode dest = mapper.createArrayNode();
+ src.add("element");
+ dest.addAll(src);
+ }
+
+ public void testParser() throws Exception
+ {
+ ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
+ n.add(123);
+ TreeTraversingParser p = new TreeTraversingParser(n, null);
+ p.setCodec(null);
+ assertNull(p.getCodec());
+ assertNotNull(p.getParsingContext());
+ assertNotNull(p.getTokenLocation());
+ assertNotNull(p.getCurrentLocation());
+ assertNull(p.getEmbeddedObject());
+ assertNull(p.currentNode());
+
+ //assertNull(p.getNumberType());
+
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ p.skipChildren();
+ assertToken(JsonToken.END_ARRAY, p.getCurrentToken());
+ p.close();
+
+ p = new TreeTraversingParser(n, null);
+ p.nextToken();
+ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals(JsonParser.NumberType.INT, p.getNumberType());
+ p.close();
+ }
+
+ public void testArrayNodeEquality()
+ {
+ ArrayNode n1 = new ArrayNode(null);
+ ArrayNode n2 = new ArrayNode(null);
+
+ assertTrue(n1.equals(n2));
+ assertTrue(n2.equals(n1));
+
+ n1.add(TextNode.valueOf("Test"));
+
+ assertFalse(n1.equals(n2));
+ assertFalse(n2.equals(n1));
+
+ n2.add(TextNode.valueOf("Test"));
+
+ assertTrue(n1.equals(n2));
+ assertTrue(n2.equals(n1));
+ }
+
+ public void testSimpleArray() throws Exception
+ {
+ ArrayNode result = objectMapper().createArrayNode();
+
+ assertTrue(result.isArray());
+ assertType(result, ArrayNode.class);
+
+ assertFalse(result.isObject());
+ assertFalse(result.isNumber());
+ assertFalse(result.isNull());
+ assertFalse(result.isTextual());
+
+ // and let's add stuff...
+ result.add(false);
+ result.insertNull(0);
+
+ // should be equal to itself no matter what
+ assertEquals(result, result);
+ assertFalse(result.equals(null)); // but not to null
+
+ // plus see that we can access stuff
+ assertEquals(NullNode.instance, result.path(0));
+ assertEquals(NullNode.instance, result.get(0));
+ assertEquals(BooleanNode.FALSE, result.path(1));
+ assertEquals(BooleanNode.FALSE, result.get(1));
+ assertEquals(2, result.size());
+
+ assertNull(result.get(-1));
+ assertNull(result.get(2));
+ JsonNode missing = result.path(2);
+ assertTrue(missing.isMissingNode());
+ assertTrue(result.path(-100).isMissingNode());
+
+ // then construct and compare
+ ArrayNode array2 = objectMapper().createArrayNode();
+ array2.addNull();
+ array2.add(false);
+ assertEquals(result, array2);
+
+ // plus remove entries
+ JsonNode rm1 = array2.remove(0);
+ assertEquals(NullNode.instance, rm1);
+ assertEquals(1, array2.size());
+ assertEquals(BooleanNode.FALSE, array2.get(0));
+ assertFalse(result.equals(array2));
+
+ JsonNode rm2 = array2.remove(0);
+ assertEquals(BooleanNode.FALSE, rm2);
+ assertEquals(0, array2.size());
+ }
+
+ public void testSimpleMismatch() throws Exception
+ {
+ ObjectMapper mapper = objectMapper();
+ try {
+ mapper.readValue(" 123 ", ArrayNode.class);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "out of VALUE_NUMBER_INT token");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeFactoryTest.java b/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeFactoryTest.java
new file mode 100644
index 0000000..3a646b4
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeFactoryTest.java
@@ -0,0 +1,37 @@
+package com.fasterxml.jackson.databind.node;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.databind.*;
+
+public class JsonNodeFactoryTest extends NodeTestBase
+{
+ private final ObjectMapper MAPPER = objectMapper();
+
+ public void testSimpleCreation()
+ {
+ JsonNodeFactory f = MAPPER.getNodeFactory();
+ JsonNode n;
+
+ n = f.numberNode((byte) 4);
+ assertTrue(n.isInt());
+ assertEquals(4, n.intValue());
+
+ assertTrue(f.numberNode((Byte) null).isNull());
+
+ assertTrue(f.numberNode((Short) null).isNull());
+
+ assertTrue(f.numberNode((Integer) null).isNull());
+
+ assertTrue(f.numberNode((Long) null).isNull());
+
+ assertTrue(f.numberNode((Float) null).isNull());
+
+ assertTrue(f.numberNode((Double) null).isNull());
+
+ assertTrue(f.numberNode((BigDecimal) null).isNull());
+
+ assertTrue(f.numberNode((BigInteger) null).isNull());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestNumberNodes.java b/src/test/java/com/fasterxml/jackson/databind/node/NumberNodesTest.java
similarity index 75%
rename from src/test/java/com/fasterxml/jackson/databind/node/TestNumberNodes.java
rename to src/test/java/com/fasterxml/jackson/databind/node/NumberNodesTest.java
index 62610f0..f12cb94 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestNumberNodes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/NumberNodesTest.java
@@ -12,8 +12,10 @@
* Basic tests for {@link JsonNode} implementations that
* contain numeric values.
*/
-public class TestNumberNodes extends NodeTestBase
+public class NumberNodesTest extends NodeTestBase
{
+ private final ObjectMapper MAPPER = objectMapper();
+
public void testShort()
{
ShortNode n = ShortNode.valueOf((short) 1);
@@ -37,8 +39,33 @@
assertTrue(ShortNode.valueOf(Short.MAX_VALUE).canConvertToLong());
assertTrue(ShortNode.valueOf(Short.MIN_VALUE).canConvertToLong());
}
-
- public void testInt()
+
+ public void testIntViaMapper() throws Exception
+ {
+ int value = -90184;
+ JsonNode result = MAPPER.readTree(String.valueOf(value));
+ assertTrue(result.isNumber());
+ assertTrue(result.isIntegralNumber());
+ assertTrue(result.isInt());
+ assertType(result, IntNode.class);
+ assertFalse(result.isLong());
+ assertFalse(result.isFloatingPointNumber());
+ assertFalse(result.isDouble());
+ assertFalse(result.isNull());
+ assertFalse(result.isTextual());
+ assertFalse(result.isMissingNode());
+
+ assertEquals(value, result.numberValue().intValue());
+ assertEquals(value, result.intValue());
+ assertEquals(String.valueOf(value), result.asText());
+ assertEquals((double) value, result.doubleValue());
+ assertEquals((long) value, result.longValue());
+
+ // also, equality should work ok
+ assertEquals(result, IntNode.valueOf(value));
+ }
+
+ public void testInt()
{
IntNode n = IntNode.valueOf(1);
assertStandardEquals(n);
@@ -62,6 +89,7 @@
assertTrue(IntNode.valueOf(0).canConvertToLong());
assertTrue(IntNode.valueOf(Integer.MAX_VALUE).canConvertToLong());
assertTrue(IntNode.valueOf(Integer.MIN_VALUE).canConvertToLong());
+
}
public void testLong()
@@ -92,6 +120,31 @@
assertTrue(LongNode.valueOf(Long.MIN_VALUE).canConvertToLong());
}
+ public void testLongViaMapper() throws Exception
+ {
+ // need to use something being 32-bit value space
+ long value = 12345678L << 32;
+ JsonNode result = MAPPER.readTree(String.valueOf(value));
+ assertTrue(result.isNumber());
+ assertTrue(result.isIntegralNumber());
+ assertTrue(result.isLong());
+ assertType(result, LongNode.class);
+ assertFalse(result.isInt());
+ assertFalse(result.isFloatingPointNumber());
+ assertFalse(result.isDouble());
+ assertFalse(result.isNull());
+ assertFalse(result.isTextual());
+ assertFalse(result.isMissingNode());
+
+ assertEquals(value, result.numberValue().longValue());
+ assertEquals(value, result.longValue());
+ assertEquals(String.valueOf(value), result.asText());
+ assertEquals((double) value, result.doubleValue());
+
+ // also, equality should work ok
+ assertEquals(result, LongNode.valueOf(value));
+ }
+
public void testDouble() throws Exception
{
DoubleNode n = DoubleNode.valueOf(0.25);
@@ -105,7 +158,6 @@
assertEquals(BigInteger.ZERO, n.bigIntegerValue());
assertEquals("0.25", n.asText());
- // 1.6:
assertNodeNumbers(DoubleNode.valueOf(4.5), 4, 4.5);
assertTrue(DoubleNode.valueOf(0).canConvertToInt());
@@ -125,6 +177,31 @@
assertEquals("-0.0", String.valueOf(n.doubleValue()));
}
+ public void testDoubleViaMapper() throws Exception
+ {
+ double value = 3.04;
+ JsonNode result = MAPPER.readTree(String.valueOf(value));
+ assertTrue(result.isNumber());
+ assertFalse(result.isNull());
+ assertType(result, DoubleNode.class);
+ assertTrue(result.isFloatingPointNumber());
+ assertTrue(result.isDouble());
+ assertFalse(result.isInt());
+ assertFalse(result.isLong());
+ assertFalse(result.isIntegralNumber());
+ assertFalse(result.isTextual());
+ assertFalse(result.isMissingNode());
+
+ assertEquals(value, result.doubleValue());
+ assertEquals(value, result.numberValue().doubleValue());
+ assertEquals((int) value, result.intValue());
+ assertEquals((long) value, result.longValue());
+ assertEquals(String.valueOf(value), result.asText());
+
+ // also, equality should work ok
+ assertEquals(result, DoubleNode.valueOf(value));
+ }
+
// @since 2.2
public void testFloat()
{
@@ -173,6 +250,7 @@
assertEquals(JsonParser.NumberType.BIG_DECIMAL, n.numberType());
assertTrue(n.isNumber());
assertFalse(n.isIntegralNumber());
+ assertFalse(n.isArray());
assertTrue(n.isBigDecimal());
assertEquals(BigDecimal.ONE, n.numberValue());
assertEquals(1, n.intValue());
@@ -180,7 +258,6 @@
assertEquals(BigDecimal.ONE, n.decimalValue());
assertEquals("1", n.asText());
- // 1.6:
assertNodeNumbers(n, 1, 1.0);
assertTrue(DecimalNode.valueOf(BigDecimal.ZERO).canConvertToInt());
@@ -192,8 +269,31 @@
assertTrue(DecimalNode.valueOf(BigDecimal.ZERO).canConvertToLong());
assertTrue(DecimalNode.valueOf(BigDecimal.valueOf(Long.MAX_VALUE)).canConvertToLong());
assertTrue(DecimalNode.valueOf(BigDecimal.valueOf(Long.MIN_VALUE)).canConvertToLong());
- }
+ // no "natural" way to get it, must construct
+ BigDecimal value = new BigDecimal("0.1");
+ JsonNode result = DecimalNode.valueOf(value);
+
+ assertFalse(result.isObject());
+ assertTrue(result.isNumber());
+ assertFalse(result.isIntegralNumber());
+ assertFalse(result.isLong());
+ assertType(result, DecimalNode.class);
+ assertFalse(result.isInt());
+ assertTrue(result.isFloatingPointNumber());
+ assertTrue(result.isBigDecimal());
+ assertFalse(result.isDouble());
+ assertFalse(result.isNull());
+ assertFalse(result.isTextual());
+ assertFalse(result.isMissingNode());
+
+ assertEquals(value, result.numberValue());
+ assertEquals(value.toString(), result.asText());
+
+ // also, equality should work ok
+ assertEquals(result, DecimalNode.valueOf(value));
+ }
+
public void testDecimalNodeEqualsHashCode()
{
// We want DecimalNodes with equivalent _numeric_ values to be equal;
@@ -290,9 +390,8 @@
assertFalse(n.isInt());
assertTrue(n.isLong());
- /* 19-May-2015, tatu: Actually, no, coercion should not happen by default.
- * But it should be possible to change it if necessary.
- */
+ // 19-May-2015, tatu: Actually, no, coercion should not happen by default.
+ // But it should be possible to change it if necessary.
// but "too small" number will be 'int'...
n = f.numberNode(123L);
assertTrue(n.isLong());
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestObjectNode.java b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
similarity index 90%
rename from src/test/java/com/fasterxml/jackson/databind/node/TestObjectNode.java
rename to src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
index 719c109..b617d68 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestObjectNode.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.databind.node;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -8,11 +9,12 @@
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
/**
* Additional tests for {@link ObjectNode} container class.
*/
-public class TestObjectNode
+public class ObjectNodeTest
extends BaseMapTest
{
@JsonDeserialize(as = DataImpl.class)
@@ -80,7 +82,6 @@
assertTrue(root.isObject());
assertEquals(2, root.size());
- // Related to [JACKSON-50]:
Iterator<JsonNode> it = root.iterator();
assertNotNull(it);
assertTrue(it.hasNext());
@@ -115,7 +116,7 @@
assertEquals(IntNode.valueOf(1), root.get("key"));
assertNull(root.get("b"));
}
- // for [Issue#346]
+ // for [databind#346]
public void testEmptyNodeAsValue() throws Exception
{
Data w = MAPPER.readValue("{}", Data.class);
@@ -178,6 +179,24 @@
assertEquals(0, n.size());
}
+ public void testBigNumbers()
+ {
+ ObjectNode n = new ObjectNode(JsonNodeFactory.instance);
+ assertStandardEquals(n);
+ BigInteger I = BigInteger.valueOf(3);
+ BigDecimal DEC = new BigDecimal("0.1");
+
+ n.put("a", DEC);
+ n.put("b", I);
+
+ assertEquals(2, n.size());
+
+ assertTrue(n.path("a").isBigDecimal());
+ assertEquals(DEC, n.get("a").decimalValue());
+ assertTrue(n.path("b").isBigInteger());
+ assertEquals(I, n.get("b").bigIntegerValue());
+ }
+
/**
* Verify null handling
*/
@@ -205,6 +224,13 @@
n = o1.get("d");
assertNotNull(n);
assertSame(n, NullNode.instance);
+
+ o1.put("3", (BigInteger) null);
+ n = o1.get("3");
+ assertNotNull(3);
+ assertSame(n, NullNode.instance);
+
+ assertEquals(4, o1.size());
}
/**
@@ -340,7 +366,7 @@
assertEquals(1, root3.path("a").intValue());
}
- // [Issue#237] (databind): support DeserializationFeature#FAIL_ON_READING_DUP_TREE_KEY
+ // [databind#237] (databind): support DeserializationFeature#FAIL_ON_READING_DUP_TREE_KEY
public void testFailOnDupKeys() throws Exception
{
final String DUP_JSON = "{ \"a\":1, \"a\":2 }";
@@ -413,4 +439,15 @@
// System.out.println("Deserialized to MyValue: "+de2);
assertNotNull(de2);
}
+
+ public void testSimpleMismatch() throws Exception
+ {
+ ObjectMapper mapper = objectMapper();
+ try {
+ mapper.readValue("[ 1, 2, 3 ]", ObjectNode.class);
+ fail("Should not pass");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "out of START_ARRAY token");
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/POJONodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/POJONodeTest.java
new file mode 100644
index 0000000..638cb43
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/node/POJONodeTest.java
@@ -0,0 +1,55 @@
+package com.fasterxml.jackson.databind.node;
+
+import java.io.IOException;
+import java.util.*;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class POJONodeTest extends NodeTestBase
+{
+ @JsonSerialize(using = CustomSer.class)
+ public static class Data {
+ public String aStr;
+ }
+
+ @SuppressWarnings("serial")
+ public static class CustomSer extends StdSerializer<Data> {
+ public CustomSer() {
+ super(Data.class);
+ }
+
+ @Override
+ public void serialize(Data value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ String attrStr = (String) provider.getAttribute("myAttr");
+ gen.writeStartObject();
+ gen.writeStringField("aStr", "The value is: " + (attrStr == null ? "NULL" : attrStr));
+ gen.writeEndObject();
+ }
+ }
+
+ final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testPOJONodeCustomSer() throws Exception
+ {
+ Data data = new Data();
+ data.aStr = "Hello";
+
+ Map<String, Object> mapTest = new HashMap<>();
+ mapTest.put("data", data);
+
+ ObjectNode treeTest = MAPPER.createObjectNode();
+ treeTest.putPOJO("data", data);
+
+ final String EXP = "{\"data\":{\"aStr\":\"The value is: Hello!\"}}";
+
+ String mapOut = MAPPER.writer().withAttribute("myAttr", "Hello!").writeValueAsString(mapTest);
+ assertEquals(EXP, mapOut);
+
+ String treeOut = MAPPER.writer().withAttribute("myAttr", "Hello!").writeValueAsString(treeTest);
+ assertEquals(EXP, treeOut);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestArrayNode.java b/src/test/java/com/fasterxml/jackson/databind/node/TestArrayNode.java
deleted file mode 100644
index 901910e..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestArrayNode.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package com.fasterxml.jackson.databind.node;
-
-import java.io.*;
-import java.util.*;
-
-
-import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-import com.fasterxml.jackson.databind.node.TextNode;
-import com.fasterxml.jackson.databind.node.TreeTraversingParser;
-
-/**
- * Additional tests for {@link ArrayNode} container class.
- */
-public class TestArrayNode
- extends BaseMapTest
-{
- public void testBasics() throws IOException
- {
- ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
- assertStandardEquals(n);
- assertFalse(n.elements().hasNext());
- assertFalse(n.fieldNames().hasNext());
- TextNode text = TextNode.valueOf("x");
- n.add(text);
- assertEquals(1, n.size());
- assertFalse(0 == n.hashCode());
- assertTrue(n.elements().hasNext());
- // no field names for arrays
- assertFalse(n.fieldNames().hasNext());
- assertNull(n.get("x")); // not used with arrays
- assertTrue(n.path("x").isMissingNode());
- assertSame(text, n.get(0));
-
- // single element, so:
- assertFalse(n.has("field"));
- assertFalse(n.hasNonNull("field"));
- assertTrue(n.has(0));
- assertTrue(n.hasNonNull(0));
- assertFalse(n.has(1));
- assertFalse(n.hasNonNull(1));
-
- // add null node too
- n.add((JsonNode) null);
- assertEquals(2, n.size());
- assertTrue(n.get(1).isNull());
- assertTrue(n.has(1));
- assertFalse(n.hasNonNull(1));
- // change to text
- n.set(1, text);
- assertSame(text, n.get(1));
- n.set(0, null);
- assertTrue(n.get(0).isNull());
-
- // and finally, clear it all
- ArrayNode n2 = new ArrayNode(JsonNodeFactory.instance);
- n2.add("foobar");
- assertFalse(n.equals(n2));
- n.addAll(n2);
- assertEquals(3, n.size());
-
- assertFalse(n.get(0).isTextual());
- assertNotNull(n.remove(0));
- assertEquals(2, n.size());
- assertTrue(n.get(0).isTextual());
-
- ArrayList<JsonNode> nodes = new ArrayList<JsonNode>();
- nodes.add(text);
- n.addAll(nodes);
- assertEquals(3, n.size());
- assertNull(n.get(10000));
- assertNull(n.remove(-4));
-
- TextNode text2 = TextNode.valueOf("b");
- n.insert(0, text2);
- assertEquals(4, n.size());
- assertSame(text2, n.get(0));
-
- assertNotNull(n.addArray());
- assertEquals(5, n.size());
- n.addPOJO("foo");
- assertEquals(6, n.size());
-
- // Try serializing it for fun, too...
- JsonGenerator jg = new MappingJsonFactory().createGenerator(new StringWriter());
- n.serialize(jg, null);
-
- n.removeAll();
- assertEquals(0, n.size());
- jg.close();
- }
-
- public void testAdds()
- {
- ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
- assertNotNull(n.addArray());
- assertNotNull(n.addObject());
- n.addPOJO("foobar");
- n.add(1);
- n.add(1L);
- n.add(0.5);
- n.add(0.5f);
- assertEquals(7, n.size());
-
- assertNotNull(n.insertArray(0));
- assertNotNull(n.insertObject(0));
- n.insertPOJO(2, "xxx");
- assertEquals(10, n.size());
- }
-
- /**
- * Test to verify [JACKSON-227]
- */
- public void testNullChecking()
- {
- ArrayNode a1 = JsonNodeFactory.instance.arrayNode();
- ArrayNode a2 = JsonNodeFactory.instance.arrayNode();
- // used to throw NPE before fix:
- a1.addAll(a2);
- assertEquals(0, a1.size());
- assertEquals(0, a2.size());
-
- a2.addAll(a1);
- assertEquals(0, a1.size());
- assertEquals(0, a2.size());
- }
-
- /**
- * Another test to verify [JACKSON-227]...
- */
- public void testNullChecking2()
- {
- ObjectMapper mapper = new ObjectMapper();
- ArrayNode src = mapper.createArrayNode();
- ArrayNode dest = mapper.createArrayNode();
- src.add("element");
- dest.addAll(src);
- }
-
- public void testParser() throws Exception
- {
- ArrayNode n = new ArrayNode(JsonNodeFactory.instance);
- n.add(123);
- TreeTraversingParser p = new TreeTraversingParser(n, null);
- p.setCodec(null);
- assertNull(p.getCodec());
- assertNotNull(p.getParsingContext());
- assertNotNull(p.getTokenLocation());
- assertNotNull(p.getCurrentLocation());
- assertNull(p.getEmbeddedObject());
- assertNull(p.currentNode());
-
- //assertNull(p.getNumberType());
-
- assertToken(JsonToken.START_ARRAY, p.nextToken());
- p.skipChildren();
- assertToken(JsonToken.END_ARRAY, p.getCurrentToken());
- p.close();
-
- p = new TreeTraversingParser(n, null);
- p.nextToken();
- assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
- assertEquals(JsonParser.NumberType.INT, p.getNumberType());
- p.close();
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestConversions.java b/src/test/java/com/fasterxml/jackson/databind/node/TestConversions.java
index 354793a..e22082d 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestConversions.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestConversions.java
@@ -10,6 +10,7 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -174,10 +175,22 @@
try {
data = n.getBinaryValue(variant);
} catch (Exception e) {
- throw new IOException("Failed (variant "+variant+", data length "+len+"): "+e.getMessage());
+ fail("Failed (variant "+variant+", data length "+len+"): "+e.getMessage());
}
assertNotNull(data);
assertArrayEquals(data, input);
+
+ // 15-Aug-2018, tatu: [databind#2096] requires another test
+ JsonParser p = new TreeTraversingParser(n);
+ assertEquals(JsonToken.VALUE_STRING, p.nextToken());
+ try {
+ data = p.getBinaryValue(variant);
+ } catch (Exception e) {
+ fail("Failed (variant "+variant+", data length "+len+"): "+e.getMessage());
+ }
+ assertNotNull(data);
+ assertArrayEquals(data, input);
+ p.close();
}
}
}
@@ -243,11 +256,13 @@
}
@Override
- public void serializeWithType(JsonGenerator jgen,
- SerializerProvider provider, TypeSerializer typeSer) throws IOException {
- typeSer.writeTypePrefixForObject(this, jgen);
- serialize(jgen, provider);
- typeSer.writeTypeSuffixForObject(this, jgen);
+ public void serializeWithType(JsonGenerator g,
+ SerializerProvider provider, TypeSerializer typeSer) throws IOException
+ {
+ WritableTypeId typeIdDef = new WritableTypeId(this, JsonToken.START_OBJECT);
+ typeSer.writeTypePrefix(g, typeIdDef);
+ serialize(g, provider);
+ typeSer.writeTypePrefix(g, typeIdDef);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java b/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java
index 0dbb91f..0750fef 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java
@@ -15,36 +15,7 @@
{
private final ObjectMapper MAPPER = objectMapper();
- public void testText()
- {
- assertNull(TextNode.valueOf(null));
- TextNode empty = TextNode.valueOf("");
- assertStandardEquals(empty);
- assertSame(TextNode.EMPTY_STRING_NODE, empty);
-
- // 1.6:
- assertNodeNumbers(TextNode.valueOf("-3"), -3, -3.0);
- assertNodeNumbers(TextNode.valueOf("17.75"), 17, 17.75);
-
- // [JACKSON-587]
- long value = 127353264013893L;
- TextNode n = TextNode.valueOf(String.valueOf(value));
- assertEquals(value, n.asLong());
-
- // and then with non-numeric input
- n = TextNode.valueOf("foobar");
- assertNodeNumbersForNonNumeric(n);
-
- assertEquals("foobar", n.asText("barf"));
- assertEquals("", empty.asText("xyz"));
-
- assertTrue(TextNode.valueOf("true").asBoolean(true));
- assertTrue(TextNode.valueOf("true").asBoolean(false));
- assertFalse(TextNode.valueOf("false").asBoolean(true));
- assertFalse(TextNode.valueOf("false").asBoolean(false));
- }
-
- public void testBoolean()
+ public void testBoolean() throws Exception
{
BooleanNode f = BooleanNode.getFalse();
assertNotNull(f);
@@ -67,11 +38,23 @@
assertEquals("true", t.asText());
assertEquals(JsonToken.VALUE_TRUE, t.asToken());
- // 1.6:
assertNodeNumbers(f, 0, 0.0);
assertNodeNumbers(t, 1, 1.0);
- }
+
+ JsonNode result = objectMapper().readTree("true\n");
+ assertFalse(result.isNull());
+ assertFalse(result.isNumber());
+ assertFalse(result.isTextual());
+ assertTrue(result.isBoolean());
+ assertType(result, BooleanNode.class);
+ assertTrue(result.booleanValue());
+ assertEquals("true", result.asText());
+ assertFalse(result.isMissingNode());
+ // also, equality should work ok
+ assertEquals(result, BooleanNode.valueOf(true));
+ assertEquals(result, BooleanNode.getTrue());
+ }
public void testBinary() throws Exception
{
@@ -147,6 +130,10 @@
assertTrue(root1.equals(root1));
assertTrue(root2.equals(root2));
+ assertTrue(nestedArray1.equals(nestedArray1));
+ assertFalse(nestedArray1.equals(nestedArray2));
+ assertFalse(nestedArray2.equals(nestedArray1));
+
// but. Custom comparator can make all the difference
Comparator<JsonNode> cmp = new Comparator<JsonNode>() {
@@ -159,11 +146,15 @@
return 0;
}
if ((o1 instanceof NumericNode) && (o2 instanceof NumericNode)) {
- double d1 = ((NumericNode) o1).asDouble();
- double d2 = ((NumericNode) o2).asDouble();
+ int d1 = ((NumericNode) o1).asInt();
+ int d2 = ((NumericNode) o2).asInt();
if (d1 == d2) { // strictly equals because it's integral value
return 0;
}
+ if (d1 < d2) {
+ return -1;
+ }
+ return 1;
}
return 0;
}
@@ -172,6 +163,14 @@
assertTrue(root2.equals(cmp, root1));
assertTrue(root1.equals(cmp, root1));
assertTrue(root2.equals(cmp, root2));
+
+ ArrayNode array3 = MAPPER.createArrayNode();
+ array3.add(123);
+
+ assertFalse(root2.equals(cmp, nestedArray1));
+ assertTrue(nestedArray1.equals(cmp, nestedArray1));
+ assertFalse(nestedArray1.equals(cmp, root2));
+ assertFalse(nestedArray1.equals(cmp, array3));
}
// [databind#793]
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java b/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java
index c09d619..45f0bac 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java
@@ -1,6 +1,10 @@
package com.fasterxml.jackson.databind.node;
+import java.io.StringReader;
+import java.util.Iterator;
+
import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.JsonNode;
public class TestMissingNode extends NodeTestBase
{
@@ -9,17 +13,12 @@
MissingNode n = MissingNode.getInstance();
assertTrue(n.isMissingNode());
assertEquals(JsonToken.NOT_AVAILABLE, n.asToken());
- // as per [JACKSON-775]
assertEquals("", n.asText());
assertStandardEquals(n);
assertEquals("", n.toString());
- /* As of 2.0, MissingNode is considered non-numeric, meaning
- * that default values are served.
- */
assertNodeNumbersForNonNumeric(n);
- // [JACKSON-823]
assertTrue(n.asBoolean(true));
assertEquals(4, n.asInt(4));
assertEquals(5L, n.asLong(5));
@@ -27,4 +26,69 @@
assertEquals("foo", n.asText("foo"));
}
+
+ /**
+ * Let's also verify behavior of "MissingNode" -- one needs to be able
+ * to traverse such bogus nodes with appropriate methods.
+ */
+ @SuppressWarnings("unused")
+ public void testMissingViaMapper() throws Exception
+ {
+ String JSON = "[ { }, [ ] ]";
+ JsonNode result = objectMapper().readTree(new StringReader(JSON));
+
+ assertTrue(result.isContainerNode());
+ assertTrue(result.isArray());
+ assertEquals(2, result.size());
+
+ int count = 0;
+ for (JsonNode node : result) {
+ ++count;
+ }
+ assertEquals(2, count);
+
+ Iterator<JsonNode> it = result.iterator();
+
+ JsonNode onode = it.next();
+ assertTrue(onode.isContainerNode());
+ assertTrue(onode.isObject());
+ assertEquals(0, onode.size());
+ assertFalse(onode.isMissingNode()); // real node
+ assertNull(onode.textValue());
+
+ // how about dereferencing?
+ assertNull(onode.get(0));
+ JsonNode dummyNode = onode.path(0);
+ assertNotNull(dummyNode);
+ assertTrue(dummyNode.isMissingNode());
+ assertNull(dummyNode.get(3));
+ assertNull(dummyNode.get("whatever"));
+ JsonNode dummyNode2 = dummyNode.path(98);
+ assertNotNull(dummyNode2);
+ assertTrue(dummyNode2.isMissingNode());
+ JsonNode dummyNode3 = dummyNode.path("field");
+ assertNotNull(dummyNode3);
+ assertTrue(dummyNode3.isMissingNode());
+
+ // and same for the array node
+
+ JsonNode anode = it.next();
+ assertTrue(anode.isContainerNode());
+ assertTrue(anode.isArray());
+ assertFalse(anode.isMissingNode()); // real node
+ assertEquals(0, anode.size());
+
+ assertNull(anode.get(0));
+ dummyNode = anode.path(0);
+ assertNotNull(dummyNode);
+ assertTrue(dummyNode.isMissingNode());
+ assertNull(dummyNode.get(0));
+ assertNull(dummyNode.get("myfield"));
+ dummyNode2 = dummyNode.path(98);
+ assertNotNull(dummyNode2);
+ assertTrue(dummyNode2.isMissingNode());
+ dummyNode3 = dummyNode.path("f");
+ assertNotNull(dummyNode3);
+ assertTrue(dummyNode3.isMissingNode());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestNullNode.java b/src/test/java/com/fasterxml/jackson/databind/node/TestNullNode.java
index c807b03..99f6eab 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestNullNode.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestNullNode.java
@@ -1,10 +1,22 @@
package com.fasterxml.jackson.databind.node;
+import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
public class TestNullNode extends NodeTestBase
{
+ final static class CovarianceBean {
+ ObjectNode _object;
+ ArrayNode _array;
+
+ public void setObject(ObjectNode n) { _object = n; }
+ public void setArray(ArrayNode n) { _array = n; }
+ }
+
public void testBasicsWithNullNode() throws Exception
{
// Let's use something that doesn't add much beyond JsonNode base
@@ -42,4 +54,47 @@
// 2.4
assertEquals("foo", n.asText("foo"));
}
+
+ public void testNullHandling() throws Exception
+ {
+ // First, a stand-alone null
+ JsonNode n = objectReader().readTree("null");
+ assertNotNull(n);
+ assertTrue(n.isNull());
+ assertFalse(n.isNumber());
+ assertFalse(n.isTextual());
+ assertEquals("null", n.asText());
+ assertEquals(n, NullNode.instance);
+
+ n = objectMapper().readTree("null");
+ assertNotNull(n);
+ assertTrue(n.isNull());
+
+ // Then object property
+ ObjectNode root = (ObjectNode) objectReader().readTree("{\"x\":null}");
+ assertEquals(1, root.size());
+ n = root.get("x");
+ assertNotNull(n);
+ assertTrue(n.isNull());
+ }
+
+ public void testNullSerialization() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ StringWriter sw = new StringWriter();
+ mapper.writeValue(sw, NullNode.instance);
+ assertEquals("null", sw.toString());
+ }
+
+ public void testNullHandlingCovariance() throws Exception
+ {
+ String JSON = "{\"object\" : null, \"array\" : null }";
+ CovarianceBean bean = objectMapper().readValue(JSON, CovarianceBean.class);
+
+ ObjectNode on = bean._object;
+ assertNull(on);
+
+ ArrayNode an = bean._array;
+ assertNull(an);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeDeserialization.java
index 5435b6d..5bf8d85 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeDeserialization.java
@@ -3,7 +3,6 @@
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import java.io.IOException;
/**
* This unit test suite tries to verify that JsonNode-based trees
@@ -20,58 +19,12 @@
public void setNode(JsonNode n) { _node = n; }
}
- final static class CovarianceBean {
- ObjectNode _object;
- ArrayNode _array;
-
- public void setObject(ObjectNode n) { _object = n; }
- public void setArray(ArrayNode n) { _array = n; }
- }
-
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
- /**
- * This test checks that is possible to mix "regular" Java objects
- * and JsonNode.
- */
- public void testMixed() throws IOException
- {
- ObjectMapper om = new ObjectMapper();
- String JSON = "{\"node\" : { \"a\" : 3 }, \"x\" : 9 }";
- Bean bean = om.readValue(JSON, Bean.class);
-
- assertEquals(9, bean._x);
- JsonNode n = bean._node;
- assertNotNull(n);
- assertEquals(1, n.size());
- ObjectNode on = (ObjectNode) n;
- assertEquals(3, on.get("a").intValue());
- }
-
- /// Verifying [JACKSON-143]
- public void testArrayNodeEquality()
- {
- ArrayNode n1 = new ArrayNode(null);
- ArrayNode n2 = new ArrayNode(null);
-
- assertTrue(n1.equals(n2));
- assertTrue(n2.equals(n1));
-
- n1.add(TextNode.valueOf("Test"));
-
- assertFalse(n1.equals(n2));
- assertFalse(n2.equals(n1));
-
- n2.add(TextNode.valueOf("Test"));
-
- assertTrue(n1.equals(n2));
- assertTrue(n2.equals(n1));
- }
-
public void testObjectNodeEquality()
{
ObjectNode n1 = new ObjectNode(null);
@@ -104,36 +57,4 @@
String value = out.path("field").asText();
assertNotNull(value);
}
-
- // [databind#186]
- public void testNullHandling() throws Exception
- {
- // First, a stand-alone null
- JsonNode n = objectReader().readTree("null");
- assertNotNull(n);
- assertTrue(n.isNull());
-
- n = objectMapper().readTree("null");
- assertNotNull(n);
- assertTrue(n.isNull());
-
- // Then object property
- ObjectNode root = (ObjectNode) objectReader().readTree("{\"x\":null}");
- assertEquals(1, root.size());
- n = root.get("x");
- assertNotNull(n);
- assertTrue(n.isNull());
- }
-
- public void testNullHandlingCovariance() throws Exception
- {
- String JSON = "{\"object\" : null, \"array\" : null }";
- CovarianceBean bean = objectMapper().readValue(JSON, CovarianceBean.class);
-
- ObjectNode on = bean._object;
- assertNull(on);
-
- ArrayNode an = bean._array;
- assertNull(an);
- }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeMapperDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeMapperDeserializer.java
deleted file mode 100644
index 37410aa..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeMapperDeserializer.java
+++ /dev/null
@@ -1,425 +0,0 @@
-package com.fasterxml.jackson.databind.node;
-
-import java.io.*;
-import java.math.BigDecimal;
-import java.util.*;
-
-import com.fasterxml.jackson.core.*;
-import com.fasterxml.jackson.databind.*;
-
-/**
- * This unit test suite tries to verify that ObjectMapper
- * can properly parse JSON and bind contents into appropriate
- * JsonNode instances.
- */
-public class TestTreeMapperDeserializer extends BaseMapTest
-{
- public void testSimple()
- throws Exception
- {
- final String JSON = SAMPLE_DOC_JSON_SPEC;
-
- for (int type = 0; type < 2; ++type) {
- JsonNode result;
-
- if (type == 0) {
- result = objectMapper().readTree(new StringReader(JSON));
- } else {
- result = objectMapper().readTree(JSON);
- }
-
- assertType(result, ObjectNode.class);
- assertEquals(1, result.size());
- assertTrue(result.isObject());
-
- ObjectNode main = (ObjectNode) result;
- assertEquals("Image", main.fieldNames().next());
- JsonNode ob = main.elements().next();
- assertType(ob, ObjectNode.class);
- ObjectNode imageMap = (ObjectNode) ob;
-
- assertEquals(5, imageMap.size());
- ob = imageMap.get("Width");
- assertTrue(ob.isIntegralNumber());
- assertFalse(ob.isFloatingPointNumber());
- assertEquals(SAMPLE_SPEC_VALUE_WIDTH, ob.intValue());
- ob = imageMap.get("Height");
- assertTrue(ob.isIntegralNumber());
- assertEquals(SAMPLE_SPEC_VALUE_HEIGHT, ob.intValue());
-
- ob = imageMap.get("Title");
- assertTrue(ob.isTextual());
- assertEquals(SAMPLE_SPEC_VALUE_TITLE, ob.textValue());
-
- ob = imageMap.get("Thumbnail");
- assertType(ob, ObjectNode.class);
- ObjectNode tn = (ObjectNode) ob;
- ob = tn.get("Url");
- assertTrue(ob.isTextual());
- assertEquals(SAMPLE_SPEC_VALUE_TN_URL, ob.textValue());
- ob = tn.get("Height");
- assertTrue(ob.isIntegralNumber());
- assertEquals(SAMPLE_SPEC_VALUE_TN_HEIGHT, ob.intValue());
- ob = tn.get("Width");
- assertTrue(ob.isTextual());
- assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, ob.textValue());
-
- ob = imageMap.get("IDs");
- assertTrue(ob.isArray());
- ArrayNode idList = (ArrayNode) ob;
- assertEquals(4, idList.size());
- assertEquals(4, calcLength(idList.elements()));
- assertEquals(4, calcLength(idList.iterator()));
- {
- int[] values = new int[] {
- SAMPLE_SPEC_VALUE_TN_ID1,
- SAMPLE_SPEC_VALUE_TN_ID2,
- SAMPLE_SPEC_VALUE_TN_ID3,
- SAMPLE_SPEC_VALUE_TN_ID4
- };
- for (int i = 0; i < values.length; ++i) {
- assertEquals(values[i], idList.get(i).intValue());
- }
- int i = 0;
- for (JsonNode n : idList) {
- assertEquals(values[i], n.intValue());
- ++i;
- }
- }
- }
- }
-
- public void testBoolean()
- throws Exception
- {
- JsonNode result = objectMapper().readTree("true\n");
- assertFalse(result.isNull());
- assertFalse(result.isNumber());
- assertFalse(result.isTextual());
- assertTrue(result.isBoolean());
- assertType(result, BooleanNode.class);
- assertTrue(result.booleanValue());
- assertEquals("true", result.asText());
- assertFalse(result.isMissingNode());
-
- // also, equality should work ok
- assertEquals(result, BooleanNode.valueOf(true));
- assertEquals(result, BooleanNode.getTrue());
- }
-
- public void testDouble()
- throws Exception
- {
- double value = 3.04;
- JsonNode result = objectMapper().readTree(String.valueOf(value));
- assertTrue(result.isNumber());
- assertFalse(result.isNull());
- assertType(result, DoubleNode.class);
- assertTrue(result.isFloatingPointNumber());
- assertTrue(result.isDouble());
- assertFalse(result.isInt());
- assertFalse(result.isLong());
- assertFalse(result.isIntegralNumber());
- assertFalse(result.isTextual());
- assertFalse(result.isMissingNode());
-
- assertEquals(value, result.doubleValue());
- assertEquals(value, result.numberValue().doubleValue());
- assertEquals((int) value, result.intValue());
- assertEquals((long) value, result.longValue());
- assertEquals(String.valueOf(value), result.asText());
-
- // also, equality should work ok
- assertEquals(result, DoubleNode.valueOf(value));
- }
-
- public void testInt()
- throws Exception
- {
- int value = -90184;
- JsonNode result = objectMapper().readTree(String.valueOf(value));
- assertTrue(result.isNumber());
- assertTrue(result.isIntegralNumber());
- assertTrue(result.isInt());
- assertType(result, IntNode.class);
- assertFalse(result.isLong());
- assertFalse(result.isFloatingPointNumber());
- assertFalse(result.isDouble());
- assertFalse(result.isNull());
- assertFalse(result.isTextual());
- assertFalse(result.isMissingNode());
-
- assertEquals(value, result.numberValue().intValue());
- assertEquals(value, result.intValue());
- assertEquals(String.valueOf(value), result.asText());
- assertEquals((double) value, result.doubleValue());
- assertEquals((long) value, result.longValue());
-
- // also, equality should work ok
- assertEquals(result, IntNode.valueOf(value));
- }
-
- public void testLong() throws Exception
- {
- // need to use something being 32-bit value space
- long value = 12345678L << 32;
- JsonNode result = objectMapper().readTree(String.valueOf(value));
- assertTrue(result.isNumber());
- assertTrue(result.isIntegralNumber());
- assertTrue(result.isLong());
- assertType(result, LongNode.class);
- assertFalse(result.isInt());
- assertFalse(result.isFloatingPointNumber());
- assertFalse(result.isDouble());
- assertFalse(result.isNull());
- assertFalse(result.isTextual());
- assertFalse(result.isMissingNode());
-
- assertEquals(value, result.numberValue().longValue());
- assertEquals(value, result.longValue());
- assertEquals(String.valueOf(value), result.asText());
- assertEquals((double) value, result.doubleValue());
-
- // also, equality should work ok
- assertEquals(result, LongNode.valueOf(value));
- }
-
- public void testNull() throws Exception
- {
- JsonNode result = objectMapper().readTree(" null ");
- // should not get java null, but NullNode...
- assertNotNull(result);
- assertTrue(result.isNull());
- assertFalse(result.isNumber());
- assertFalse(result.isTextual());
- assertEquals("null", result.asText());
-
- // also, equality should work ok
- assertEquals(result, NullNode.instance);
- }
-
- public void testDecimalNode()
- throws Exception
- {
- // no "natural" way to get it, must construct
- BigDecimal value = new BigDecimal("0.1");
- JsonNode result = DecimalNode.valueOf(value);
-
- assertFalse(result.isArray());
- assertFalse(result.isObject());
- assertTrue(result.isNumber());
- assertFalse(result.isIntegralNumber());
- assertFalse(result.isLong());
- assertType(result, DecimalNode.class);
- assertFalse(result.isInt());
- assertTrue(result.isFloatingPointNumber());
- assertTrue(result.isBigDecimal());
- assertFalse(result.isDouble());
- assertFalse(result.isNull());
- assertFalse(result.isTextual());
- assertFalse(result.isMissingNode());
-
- assertEquals(value, result.numberValue());
- assertEquals(value.toString(), result.asText());
-
- // also, equality should work ok
- assertEquals(result, DecimalNode.valueOf(value));
- }
-
- public void testSimpleArray() throws Exception
- {
- ArrayNode result = objectMapper().createArrayNode();
-
- assertTrue(result.isArray());
- assertType(result, ArrayNode.class);
-
- assertFalse(result.isObject());
- assertFalse(result.isNumber());
- assertFalse(result.isNull());
- assertFalse(result.isTextual());
-
- // and let's add stuff...
- result.add(false);
- result.insertNull(0);
-
- // should be equal to itself no matter what
- assertEquals(result, result);
- assertFalse(result.equals(null)); // but not to null
-
- // plus see that we can access stuff
- assertEquals(NullNode.instance, result.path(0));
- assertEquals(NullNode.instance, result.get(0));
- assertEquals(BooleanNode.FALSE, result.path(1));
- assertEquals(BooleanNode.FALSE, result.get(1));
- assertEquals(2, result.size());
-
- assertNull(result.get(-1));
- assertNull(result.get(2));
- JsonNode missing = result.path(2);
- assertTrue(missing.isMissingNode());
- assertTrue(result.path(-100).isMissingNode());
-
- // then construct and compare
- ArrayNode array2 = objectMapper().createArrayNode();
- array2.addNull();
- array2.add(false);
- assertEquals(result, array2);
-
- // plus remove entries
- JsonNode rm1 = array2.remove(0);
- assertEquals(NullNode.instance, rm1);
- assertEquals(1, array2.size());
- assertEquals(BooleanNode.FALSE, array2.get(0));
- assertFalse(result.equals(array2));
-
- JsonNode rm2 = array2.remove(0);
- assertEquals(BooleanNode.FALSE, rm2);
- assertEquals(0, array2.size());
- }
-
- /**
- * Type mappers should be able to gracefully deal with end of
- * input.
- */
- public void testEOF() throws Exception
- {
- String JSON =
- "{ \"key\": [ { \"a\" : { \"name\": \"foo\", \"type\": 1\n"
- +"}, \"type\": 3, \"url\": \"http://www.google.com\" } ],\n"
- +"\"name\": \"xyz\", \"type\": 1, \"url\" : null }\n "
- ;
- JsonFactory jf = new JsonFactory();
- JsonParser jp = jf.createParser(new StringReader(JSON));
- JsonNode result = objectMapper().readTree(jp);
-
- assertTrue(result.isObject());
- assertEquals(4, result.size());
-
- assertNull(objectMapper().readTree(jp));
- jp.close();
- }
-
- public void testMultiple() throws Exception
- {
- String JSON = "12 \"string\" [ 1, 2, 3 ]";
- JsonFactory jf = new JsonFactory();
- JsonParser jp = jf.createParser(new StringReader(JSON));
- final ObjectMapper mapper = objectMapper();
- JsonNode result = mapper.readTree(jp);
-
- assertTrue(result.isIntegralNumber());
- assertTrue(result.isInt());
- assertFalse(result.isTextual());
- assertEquals(12, result.intValue());
-
- result = mapper.readTree(jp);
- assertTrue(result.isTextual());
- assertFalse(result.isIntegralNumber());
- assertFalse(result.isInt());
- assertEquals("string", result.textValue());
-
- result = mapper.readTree(jp);
- assertTrue(result.isArray());
- assertEquals(3, result.size());
-
- assertNull(mapper.readTree(jp));
- jp.close();
- }
-
- /**
- * Let's also verify behavior of "MissingNode" -- one needs to be able
- * to traverse such bogus nodes with appropriate methods.
- */
- @SuppressWarnings("unused")
- public void testMissingNode()
- throws Exception
- {
- String JSON = "[ { }, [ ] ]";
- JsonNode result = objectMapper().readTree(new StringReader(JSON));
-
- assertTrue(result.isContainerNode());
- assertTrue(result.isArray());
- assertEquals(2, result.size());
-
- int count = 0;
- for (JsonNode node : result) {
- ++count;
- }
- assertEquals(2, count);
-
- Iterator<JsonNode> it = result.iterator();
-
- JsonNode onode = it.next();
- assertTrue(onode.isContainerNode());
- assertTrue(onode.isObject());
- assertEquals(0, onode.size());
- assertFalse(onode.isMissingNode()); // real node
- assertNull(onode.textValue());
-
- // how about dereferencing?
- assertNull(onode.get(0));
- JsonNode dummyNode = onode.path(0);
- assertNotNull(dummyNode);
- assertTrue(dummyNode.isMissingNode());
- assertNull(dummyNode.get(3));
- assertNull(dummyNode.get("whatever"));
- JsonNode dummyNode2 = dummyNode.path(98);
- assertNotNull(dummyNode2);
- assertTrue(dummyNode2.isMissingNode());
- JsonNode dummyNode3 = dummyNode.path("field");
- assertNotNull(dummyNode3);
- assertTrue(dummyNode3.isMissingNode());
-
- // and same for the array node
-
- JsonNode anode = it.next();
- assertTrue(anode.isContainerNode());
- assertTrue(anode.isArray());
- assertFalse(anode.isMissingNode()); // real node
- assertEquals(0, anode.size());
-
- assertNull(anode.get(0));
- dummyNode = anode.path(0);
- assertNotNull(dummyNode);
- assertTrue(dummyNode.isMissingNode());
- assertNull(dummyNode.get(0));
- assertNull(dummyNode.get("myfield"));
- dummyNode2 = dummyNode.path(98);
- assertNotNull(dummyNode2);
- assertTrue(dummyNode2.isMissingNode());
- dummyNode3 = dummyNode.path("f");
- assertNotNull(dummyNode3);
- assertTrue(dummyNode3.isMissingNode());
- }
-
- public void testArray() throws Exception
- {
- final String JSON = "[[[-0.027512,51.503221],[-0.008497,51.503221],[-0.008497,51.509744],[-0.027512,51.509744]]]";
-
- JsonNode n = objectMapper().readTree(JSON);
- assertNotNull(n);
- assertTrue(n.isArray());
- ArrayNode an = (ArrayNode) n;
- assertEquals(1, an.size());
- ArrayNode an2 = (ArrayNode) n.get(0);
- assertTrue(an2.isArray());
- assertEquals(4, an2.size());
- }
-
- /*
- /**********************************************
- /* Helper methods
- /**********************************************
- */
-
- private int calcLength(Iterator<JsonNode> it)
- {
- int count = 0;
- while (it.hasNext()) {
- it.next();
- ++count;
- }
- return count;
- }
-}
-
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeMapperSerializer.java b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeMapperSerializer.java
index 6114ed6..10f77a8 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeMapperSerializer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeMapperSerializer.java
@@ -11,8 +11,7 @@
* This unit test suite tries to verify that the trees ObjectMapper
* constructs can be serialized properly.
*/
-public class TestTreeMapperSerializer
- extends BaseMapTest
+public class TestTreeMapperSerializer extends NodeTestBase
{
final static String FIELD1 = "first";
final static String FIELD2 = "Second?";
@@ -24,8 +23,7 @@
final static double DOUBLE_VALUE = 9.25;
- public void testFromArray()
- throws Exception
+ public void testFromArray() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
ArrayNode root = mapper.createArrayNode();
@@ -113,29 +111,20 @@
}
String doc = sw.toString();
- JsonParser jp = new JsonFactory().createParser(new StringReader(doc));
+ JsonParser p = new JsonFactory().createParser(new StringReader(doc));
- assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
for (int i = -20; i <= 20; ++i) {
- assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
- assertEquals(i, jp.getIntValue());
- assertEquals(""+i, jp.getText());
+ assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals(i, p.getIntValue());
+ assertEquals(""+i, p.getText());
}
- assertEquals(JsonToken.END_ARRAY, jp.nextToken());
- jp.close();
+ assertEquals(JsonToken.END_ARRAY, p.nextToken());
+ p.close();
}
}
- public void testNull() throws Exception
- {
- ObjectMapper mapper = new ObjectMapper();
- StringWriter sw = new StringWriter();
- mapper.writeValue(sw, NullNode.instance);
- assertEquals("null", sw.toString());
- }
-
- public void testBinary()
- throws Exception
+ public void testBinary() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
final int LENGTH = 13045;
@@ -146,11 +135,11 @@
StringWriter sw = new StringWriter();
mapper.writeValue(sw, BinaryNode.valueOf(data));
- JsonParser jp = new JsonFactory().createParser(sw.toString());
+ JsonParser p = new JsonFactory().createParser(sw.toString());
// note: can't determine it's binary from json alone:
- assertToken(JsonToken.VALUE_STRING, jp.nextToken());
- assertArrayEquals(data, jp.getBinaryValue());
- jp.close();
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertArrayEquals(data, p.getBinaryValue());
+ p.close();
}
/*
@@ -162,63 +151,63 @@
private void verifyFromArray(String input)
throws Exception
{
- JsonParser jp = new JsonFactory().createParser(new StringReader(input));
+ JsonParser p = new JsonFactory().createParser(new StringReader(input));
- assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
- assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
- assertEquals(TEXT1, getAndVerifyText(jp));
+ assertEquals(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals(TEXT1, getAndVerifyText(p));
- assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
- assertEquals(3, jp.getIntValue());
+ assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals(3, p.getIntValue());
- assertEquals(JsonToken.START_OBJECT, jp.nextToken());
- assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals(FIELD1, getAndVerifyText(jp));
+ assertEquals(JsonToken.START_OBJECT, p.nextToken());
+ assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals(FIELD1, getAndVerifyText(p));
- assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
- assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals(FIELD2, getAndVerifyText(jp));
+ assertEquals(JsonToken.VALUE_TRUE, p.nextToken());
+ assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals(FIELD2, getAndVerifyText(p));
- assertEquals(JsonToken.START_ARRAY, jp.nextToken());
- assertEquals(JsonToken.END_ARRAY, jp.nextToken());
- assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+ assertEquals(JsonToken.END_ARRAY, p.nextToken());
+ assertEquals(JsonToken.END_OBJECT, p.nextToken());
- assertEquals(JsonToken.VALUE_FALSE, jp.nextToken());
+ assertEquals(JsonToken.VALUE_FALSE, p.nextToken());
- assertEquals(JsonToken.END_ARRAY, jp.nextToken());
- assertNull(jp.nextToken());
- jp.close();
+ assertEquals(JsonToken.END_ARRAY, p.nextToken());
+ assertNull(p.nextToken());
+ p.close();
}
private void verifyFromMap(String input)
throws Exception
{
- JsonParser jp = new JsonFactory().createParser(new StringReader(input));
- assertEquals(JsonToken.START_OBJECT, jp.nextToken());
- assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals(FIELD4, getAndVerifyText(jp));
- assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
- assertEquals(TEXT2, getAndVerifyText(jp));
+ JsonParser p = new JsonFactory().createParser(input);
+ assertEquals(JsonToken.START_OBJECT, p.nextToken());
+ assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals(FIELD4, getAndVerifyText(p));
+ assertEquals(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals(TEXT2, getAndVerifyText(p));
- assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals(FIELD3, getAndVerifyText(jp));
- assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
- assertEquals(-1, jp.getIntValue());
+ assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals(FIELD3, getAndVerifyText(p));
+ assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals(-1, p.getIntValue());
- assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals(FIELD2, getAndVerifyText(jp));
- assertEquals(JsonToken.START_ARRAY, jp.nextToken());
- assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+ assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals(FIELD2, getAndVerifyText(p));
+ assertEquals(JsonToken.START_ARRAY, p.nextToken());
+ assertEquals(JsonToken.END_ARRAY, p.nextToken());
- assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals(FIELD1, getAndVerifyText(jp));
- assertEquals(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
- assertEquals(DOUBLE_VALUE, jp.getDoubleValue(), 0);
+ assertEquals(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals(FIELD1, getAndVerifyText(p));
+ assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ assertEquals(DOUBLE_VALUE, p.getDoubleValue(), 0);
- assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+ assertEquals(JsonToken.END_OBJECT, p.nextToken());
- assertNull(jp.nextToken());
- jp.close();
+ assertNull(p.nextToken());
+ p.close();
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeTraversingParser.java b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeTraversingParser.java
index f0aa428..0573696 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeTraversingParser.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeTraversingParser.java
@@ -8,6 +8,7 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.node.BinaryNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.POJONode;
@@ -22,7 +23,6 @@
public List<String> kids;
}
- // Helper class for [JACKSON-370]
@JsonIgnoreProperties(ignoreUnknown=true)
public static class Jackson370Bean {
public Inner inner;
@@ -45,65 +45,65 @@
"{ \"a\" : 123, \"list\" : [ 12.25, null, true, { }, [ ] ] }";
ObjectMapper m = new ObjectMapper();
JsonNode tree = m.readTree(JSON);
- JsonParser jp = tree.traverse();
+ JsonParser p = tree.traverse();
- assertNull(jp.getCurrentToken());
- assertNull(jp.getCurrentName());
+ assertNull(p.getCurrentToken());
+ assertNull(p.getCurrentName());
- assertToken(JsonToken.START_OBJECT, jp.nextToken());
- assertNull(jp.getCurrentName());
- assertEquals("Expected START_OBJECT", JsonToken.START_OBJECT.asString(), jp.getText());
+ assertToken(JsonToken.START_OBJECT, p.nextToken());
+ assertNull(p.getCurrentName());
+ assertEquals("Expected START_OBJECT", JsonToken.START_OBJECT.asString(), p.getText());
- assertToken(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals("a", jp.getCurrentName());
- assertEquals("a", jp.getText());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("a", p.getCurrentName());
+ assertEquals("a", p.getText());
- assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
- assertEquals("a", jp.getCurrentName());
- assertEquals(123, jp.getIntValue());
- assertEquals("123", jp.getText());
+ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals("a", p.getCurrentName());
+ assertEquals(123, p.getIntValue());
+ assertEquals("123", p.getText());
- assertToken(JsonToken.FIELD_NAME, jp.nextToken());
- assertEquals("list", jp.getCurrentName());
- assertEquals("list", jp.getText());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("list", p.getCurrentName());
+ assertEquals("list", p.getText());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertEquals("list", jp.getCurrentName());
- assertEquals(JsonToken.START_ARRAY.asString(), jp.getText());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertEquals("list", p.getCurrentName());
+ assertEquals(JsonToken.START_ARRAY.asString(), p.getText());
- assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
- assertNull(jp.getCurrentName());
- assertEquals(12.25, jp.getDoubleValue(), 0);
- assertEquals("12.25", jp.getText());
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ assertNull(p.getCurrentName());
+ assertEquals(12.25, p.getDoubleValue(), 0);
+ assertEquals("12.25", p.getText());
- assertToken(JsonToken.VALUE_NULL, jp.nextToken());
- assertNull(jp.getCurrentName());
- assertEquals(JsonToken.VALUE_NULL.asString(), jp.getText());
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertNull(p.getCurrentName());
+ assertEquals(JsonToken.VALUE_NULL.asString(), p.getText());
- assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
- assertNull(jp.getCurrentName());
- assertTrue(jp.getBooleanValue());
- assertEquals(JsonToken.VALUE_TRUE.asString(), jp.getText());
+ assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+ assertNull(p.getCurrentName());
+ assertTrue(p.getBooleanValue());
+ assertEquals(JsonToken.VALUE_TRUE.asString(), p.getText());
- assertToken(JsonToken.START_OBJECT, jp.nextToken());
- assertNull(jp.getCurrentName());
- assertToken(JsonToken.END_OBJECT, jp.nextToken());
- assertNull(jp.getCurrentName());
+ assertToken(JsonToken.START_OBJECT, p.nextToken());
+ assertNull(p.getCurrentName());
+ assertToken(JsonToken.END_OBJECT, p.nextToken());
+ assertNull(p.getCurrentName());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertNull(jp.getCurrentName());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
- assertNull(jp.getCurrentName());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertNull(p.getCurrentName());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertNull(p.getCurrentName());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
- assertToken(JsonToken.END_OBJECT, jp.nextToken());
- assertNull(jp.getCurrentName());
+ assertToken(JsonToken.END_OBJECT, p.nextToken());
+ assertNull(p.getCurrentName());
- assertNull(jp.nextToken());
+ assertNull(p.nextToken());
- jp.close();
- assertTrue(jp.isClosed());
+ p.close();
+ assertTrue(p.isClosed());
}
public void testArray() throws Exception
@@ -111,25 +111,25 @@
// For convenience, parse tree from JSON first
ObjectMapper m = new ObjectMapper();
- JsonParser jp = m.readTree("[]").traverse();
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
- jp.close();
+ JsonParser p = m.readTree("[]").traverse();
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ p.close();
- jp = m.readTree("[[]]").traverse();
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
- jp.close();
+ p = m.readTree("[[]]").traverse();
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ p.close();
- jp = m.readTree("[[ 12.1 ]]").traverse();
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
- jp.close();
+ p = m.readTree("[[ 12.1 ]]").traverse();
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ p.close();
}
public void testNested() throws Exception
@@ -140,28 +140,28 @@
;
ObjectMapper m = new ObjectMapper();
JsonNode tree = m.readTree(JSON);
- JsonParser jp = tree.traverse();
- assertToken(JsonToken.START_OBJECT, jp.nextToken());
- assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ JsonParser p = tree.traverse();
+ assertToken(JsonToken.START_OBJECT, p.nextToken());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
- assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
- assertToken(JsonToken.START_ARRAY, jp.nextToken());
- assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
- assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
+ assertToken(JsonToken.START_ARRAY, p.nextToken());
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
- assertToken(JsonToken.END_ARRAY, jp.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
+ assertToken(JsonToken.END_ARRAY, p.nextToken());
- assertToken(JsonToken.END_OBJECT, jp.nextToken());
- jp.close();
+ assertToken(JsonToken.END_OBJECT, p.nextToken());
+ p.close();
}
/**
@@ -172,71 +172,71 @@
{
ObjectMapper m = new ObjectMapper();
JsonNode tree = m.readTree(SAMPLE_DOC_JSON_SPEC);
- JsonParser jp = tree.traverse();
- verifyJsonSpecSampleDoc(jp, true);
- jp.close();
+ JsonParser p = tree.traverse();
+ verifyJsonSpecSampleDoc(p, true);
+ p.close();
}
public void testBinaryPojo() throws Exception
{
byte[] inputBinary = new byte[] { 1, 2, 100 };
POJONode n = new POJONode(inputBinary);
- JsonParser jp = n.traverse();
+ JsonParser p = n.traverse();
- assertNull(jp.getCurrentToken());
- assertToken(JsonToken.VALUE_EMBEDDED_OBJECT, jp.nextToken());
- byte[] data = jp.getBinaryValue();
+ assertNull(p.getCurrentToken());
+ assertToken(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
+ byte[] data = p.getBinaryValue();
assertNotNull(data);
assertArrayEquals(inputBinary, data);
- Object pojo = jp.getEmbeddedObject();
+ Object pojo = p.getEmbeddedObject();
assertSame(data, pojo);
- jp.close();
+ p.close();
}
public void testBinaryNode() throws Exception
{
byte[] inputBinary = new byte[] { 0, -5 };
BinaryNode n = new BinaryNode(inputBinary);
- JsonParser jp = n.traverse();
+ JsonParser p = n.traverse();
- assertNull(jp.getCurrentToken());
+ assertNull(p.getCurrentToken());
// exposed as POJO... not as VALUE_STRING
- assertToken(JsonToken.VALUE_EMBEDDED_OBJECT, jp.nextToken());
- byte[] data = jp.getBinaryValue();
+ assertToken(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
+ byte[] data = p.getBinaryValue();
assertNotNull(data);
assertArrayEquals(inputBinary, data);
// but as importantly, can be viewed as base64 encoded thing:
- assertEquals("APs=", jp.getText());
+ assertEquals("APs=", p.getText());
- assertNull(jp.nextToken());
- jp.close();
+ assertNull(p.nextToken());
+ p.close();
}
public void testTextAsBinary() throws Exception
{
TextNode n = new TextNode(" APs=\n");
- JsonParser jp = n.traverse();
- assertNull(jp.getCurrentToken());
- assertToken(JsonToken.VALUE_STRING, jp.nextToken());
- byte[] data = jp.getBinaryValue();
+ JsonParser p = n.traverse();
+ assertNull(p.getCurrentToken());
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ byte[] data = p.getBinaryValue();
assertNotNull(data);
assertArrayEquals(new byte[] { 0, -5 }, data);
- assertNull(jp.nextToken());
- jp.close();
- assertTrue(jp.isClosed());
+ assertNull(p.nextToken());
+ p.close();
+ assertTrue(p.isClosed());
// Also: let's verify we get an exception for garbage...
n = new TextNode("?!??");
- jp = n.traverse();
- assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+ p = n.traverse();
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
try {
- jp.getBinaryValue();
- } catch (JsonParseException e) {
+ p.getBinaryValue();
+ } catch (InvalidFormatException e) {
verifyException(e, "Illegal character");
}
- jp.close();
+ p.close();
}
/**
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeWithType.java b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeWithType.java
index 959133e..8395627 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestTreeWithType.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestTreeWithType.java
@@ -20,7 +20,7 @@
}
}
- // [Issue#353]
+ // [databind#353]
public class SavedCookie {
public String name, value;
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TextNodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/TextNodeTest.java
new file mode 100644
index 0000000..ad04c8a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TextNodeTest.java
@@ -0,0 +1,31 @@
+package com.fasterxml.jackson.databind.node;
+
+public class TextNodeTest extends NodeTestBase
+{
+ public void testText()
+ {
+ assertNull(TextNode.valueOf(null));
+ TextNode empty = TextNode.valueOf("");
+ assertStandardEquals(empty);
+ assertSame(TextNode.EMPTY_STRING_NODE, empty);
+
+ assertNodeNumbers(TextNode.valueOf("-3"), -3, -3.0);
+ assertNodeNumbers(TextNode.valueOf("17.75"), 17, 17.75);
+
+ long value = 127353264013893L;
+ TextNode n = TextNode.valueOf(String.valueOf(value));
+ assertEquals(value, n.asLong());
+
+ // and then with non-numeric input
+ n = TextNode.valueOf("foobar");
+ assertNodeNumbersForNonNumeric(n);
+
+ assertEquals("foobar", n.asText("barf"));
+ assertEquals("", empty.asText("xyz"));
+
+ assertTrue(TextNode.valueOf("true").asBoolean(true));
+ assertTrue(TextNode.valueOf("true").asBoolean(false));
+ assertFalse(TextNode.valueOf("false").asBoolean(true));
+ assertFalse(TextNode.valueOf("false").asBoolean(false));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TreeReadViaMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/node/TreeReadViaMapperTest.java
new file mode 100644
index 0000000..dbeed46
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TreeReadViaMapperTest.java
@@ -0,0 +1,192 @@
+package com.fasterxml.jackson.databind.node;
+
+import java.io.*;
+import java.util.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.node.TestTreeDeserialization.Bean;
+
+/**
+ * This unit test suite tries to verify that ObjectMapper
+ * can properly parse JSON and bind contents into appropriate
+ * JsonNode instances.
+ */
+public class TreeReadViaMapperTest extends BaseMapTest
+{
+ public void testSimple() throws Exception
+ {
+ final String JSON = SAMPLE_DOC_JSON_SPEC;
+
+ for (int type = 0; type < 2; ++type) {
+ JsonNode result;
+
+ if (type == 0) {
+ result = objectMapper().readTree(new StringReader(JSON));
+ } else {
+ result = objectMapper().readTree(JSON);
+ }
+
+ assertType(result, ObjectNode.class);
+ assertEquals(1, result.size());
+ assertTrue(result.isObject());
+
+ ObjectNode main = (ObjectNode) result;
+ assertEquals("Image", main.fieldNames().next());
+ JsonNode ob = main.elements().next();
+ assertType(ob, ObjectNode.class);
+ ObjectNode imageMap = (ObjectNode) ob;
+
+ assertEquals(5, imageMap.size());
+ ob = imageMap.get("Width");
+ assertTrue(ob.isIntegralNumber());
+ assertFalse(ob.isFloatingPointNumber());
+ assertEquals(SAMPLE_SPEC_VALUE_WIDTH, ob.intValue());
+ ob = imageMap.get("Height");
+ assertTrue(ob.isIntegralNumber());
+ assertEquals(SAMPLE_SPEC_VALUE_HEIGHT, ob.intValue());
+
+ ob = imageMap.get("Title");
+ assertTrue(ob.isTextual());
+ assertEquals(SAMPLE_SPEC_VALUE_TITLE, ob.textValue());
+
+ ob = imageMap.get("Thumbnail");
+ assertType(ob, ObjectNode.class);
+ ObjectNode tn = (ObjectNode) ob;
+ ob = tn.get("Url");
+ assertTrue(ob.isTextual());
+ assertEquals(SAMPLE_SPEC_VALUE_TN_URL, ob.textValue());
+ ob = tn.get("Height");
+ assertTrue(ob.isIntegralNumber());
+ assertEquals(SAMPLE_SPEC_VALUE_TN_HEIGHT, ob.intValue());
+ ob = tn.get("Width");
+ assertTrue(ob.isTextual());
+ assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, ob.textValue());
+
+ ob = imageMap.get("IDs");
+ assertTrue(ob.isArray());
+ ArrayNode idList = (ArrayNode) ob;
+ assertEquals(4, idList.size());
+ assertEquals(4, calcLength(idList.elements()));
+ assertEquals(4, calcLength(idList.iterator()));
+ {
+ int[] values = new int[] {
+ SAMPLE_SPEC_VALUE_TN_ID1,
+ SAMPLE_SPEC_VALUE_TN_ID2,
+ SAMPLE_SPEC_VALUE_TN_ID3,
+ SAMPLE_SPEC_VALUE_TN_ID4
+ };
+ for (int i = 0; i < values.length; ++i) {
+ assertEquals(values[i], idList.get(i).intValue());
+ }
+ int i = 0;
+ for (JsonNode n : idList) {
+ assertEquals(values[i], n.intValue());
+ ++i;
+ }
+ }
+ }
+ }
+
+ public void testMixed() throws IOException
+ {
+ ObjectMapper om = new ObjectMapper();
+ String JSON = "{\"node\" : { \"a\" : 3 }, \"x\" : 9 }";
+ Bean bean = om.readValue(JSON, Bean.class);
+
+ assertEquals(9, bean._x);
+ JsonNode n = bean._node;
+ assertNotNull(n);
+ assertEquals(1, n.size());
+ ObjectNode on = (ObjectNode) n;
+ assertEquals(3, on.get("a").intValue());
+ }
+
+ /**
+ * Type mappers should be able to gracefully deal with end of
+ * input.
+ */
+ public void testEOF() throws Exception
+ {
+ String JSON =
+ "{ \"key\": [ { \"a\" : { \"name\": \"foo\", \"type\": 1\n"
+ +"}, \"type\": 3, \"url\": \"http://www.google.com\" } ],\n"
+ +"\"name\": \"xyz\", \"type\": 1, \"url\" : null }\n "
+ ;
+ JsonFactory jf = new JsonFactory();
+ JsonParser p = jf.createParser(new StringReader(JSON));
+ JsonNode result = objectMapper().readTree(p);
+
+ assertTrue(result.isObject());
+ assertEquals(4, result.size());
+
+ assertNull(objectMapper().readTree(p));
+ p.close();
+ }
+
+ public void testMultiple() throws Exception
+ {
+ String JSON = "12 \"string\" [ 1, 2, 3 ]";
+ JsonFactory jf = new JsonFactory();
+ JsonParser p = jf.createParser(new StringReader(JSON));
+ final ObjectMapper mapper = objectMapper();
+ JsonNode result = mapper.readTree(p);
+
+ assertTrue(result.isIntegralNumber());
+ assertTrue(result.isInt());
+ assertFalse(result.isTextual());
+ assertEquals(12, result.intValue());
+
+ result = mapper.readTree(p);
+ assertTrue(result.isTextual());
+ assertFalse(result.isIntegralNumber());
+ assertFalse(result.isInt());
+ assertEquals("string", result.textValue());
+
+ result = mapper.readTree(p);
+ assertTrue(result.isArray());
+ assertEquals(3, result.size());
+
+ assertNull(mapper.readTree(p));
+ p.close();
+ }
+
+ // [databind#1406]
+ public void testNullFromEOFViaMapper() throws Exception
+ {
+ final ObjectMapper mapper = objectMapper();
+
+ assertNull(mapper.readTree(new StringReader("")));
+ assertNull(mapper.readTree(new ByteArrayInputStream(new byte[0])));
+ }
+
+ // [databind#1406]
+ public void testNullFromEOFViaObjectReader() throws Exception
+ {
+ final ObjectMapper mapper = objectMapper();
+
+ assertNull(mapper.readTree(new StringReader("")));
+ assertNull(mapper.readTree(new ByteArrayInputStream(new byte[0])));
+ assertNull(mapper.readerFor(JsonNode.class)
+ .readTree(new StringReader("")));
+ assertNull(mapper.readerFor(JsonNode.class)
+ .readTree(new ByteArrayInputStream(new byte[0])));
+ }
+
+ /*
+ /**********************************************
+ /* Helper methods
+ /**********************************************
+ */
+
+ private int calcLength(Iterator<JsonNode> it)
+ {
+ int count = 0;
+ while (it.hasNext()) {
+ it.next();
+ ++count;
+ }
+ return count;
+ }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/JSOGDeserialize622Test.java b/src/test/java/com/fasterxml/jackson/databind/objectid/JSOGDeserialize622Test.java
index 129872e..1adfd10 100644
--- a/src/test/java/com/fasterxml/jackson/databind/objectid/JSOGDeserialize622Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/objectid/JSOGDeserialize622Test.java
@@ -97,14 +97,14 @@
static class JSOGRefDeserializer extends JsonDeserializer<JSOGRef>
{
@Override
- public JSOGRef deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
- JsonNode node = jp.readValueAsTree();
+ public JSOGRef deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
+ JsonNode node = p.readValueAsTree();
if (node.isTextual()) {
return new JSOGRef(node.asInt());
}
JsonNode n = node.get(REF_KEY);
if (n == null) {
- throw JsonMappingException.from(jp, "Could not find key '"+REF_KEY
+ throw new JsonMappingException(p, "Could not find key '"+REF_KEY
+"' from ("+node.getClass().getName()+"): "+node);
}
return new JSOGRef(n.asInt());
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestObjectId687.java b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java
similarity index 76%
rename from src/test/java/com/fasterxml/jackson/failing/TestObjectId687.java
rename to src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java
index fd8fabc..972a5fa 100644
--- a/src/test/java/com/fasterxml/jackson/failing/TestObjectId687.java
+++ b/src/test/java/com/fasterxml/jackson/databind/objectid/ObjectId687Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.objectid;
import java.io.IOException;
import java.util.*;
@@ -6,8 +6,9 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
-public class TestObjectId687 extends BaseMapTest
+public class ObjectId687Test extends BaseMapTest
{
+ // for [databind#687]
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="label")
static class ReferredWithCreator {
public String label;
@@ -63,7 +64,8 @@
*/
private final ObjectMapper MAPPER = objectMapper();
-
+
+ // for [databind#687]
public void testSerializeDeserializeWithCreator() throws IOException {
ReferredWithCreator base = new ReferredWithCreator("label1");
ReferringToObjWithCreator r = new ReferringToObjWithCreator();
@@ -72,10 +74,15 @@
e.baseRef = base;
e.nextRef = r;
- String jsonStr = MAPPER.writeValueAsString(e);
+ String json = MAPPER.writeValueAsString(e);
- EnclosingForRefsWithCreator deserialized = MAPPER.readValue(jsonStr, EnclosingForRefsWithCreator.class);
- assertNotNull(deserialized);
+ EnclosingForRefsWithCreator result = MAPPER.readValue(json,
+ EnclosingForRefsWithCreator.class);
+ assertNotNull(result);
+ assertEquals(result.label, e.label);
+
+ // also, compare by re-serializing:
+ assertEquals(json, MAPPER.writeValueAsString(result));
}
public void testSerializeDeserializeNoCreator() throws IOException {
@@ -86,9 +93,14 @@
e.baseRef = base;
e.nextRef = r;
- String jsonStr = MAPPER.writeValueAsString(e);
+ String json = MAPPER.writeValueAsString(e);
- EnclosingForRefWithNoCreator deserialized = MAPPER.readValue(jsonStr, EnclosingForRefWithNoCreator.class);
- assertNotNull(deserialized);
+ EnclosingForRefWithNoCreator result = MAPPER.readValue(json,
+ EnclosingForRefWithNoCreator.class);
+ assertNotNull(result);
+ assertEquals(result.label, e.label);
+
+ // also, compare by re-serializing:
+ assertEquals(json, MAPPER.writeValueAsString(result));
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/PolymorphicWithObjectId1551Test.java b/src/test/java/com/fasterxml/jackson/databind/objectid/PolymorphicWithObjectId1551Test.java
new file mode 100644
index 0000000..a244ca4
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/objectid/PolymorphicWithObjectId1551Test.java
@@ -0,0 +1,91 @@
+package com.fasterxml.jackson.databind.objectid;
+
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
+import com.fasterxml.jackson.annotation.JsonIdentityReference;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.ObjectIdGenerators;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+
+public class PolymorphicWithObjectId1551Test extends BaseMapTest
+{
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY,
+ property = "@class")
+ static abstract class Vehicle {
+ public String vehicleId;
+ }
+
+ static class Car extends Vehicle {
+ public int numberOfDoors;
+ }
+
+ static class VehicleOwnerViaProp {
+ @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "vehicleId")
+ @JsonIdentityReference(alwaysAsId = false)
+ public Vehicle ownedVehicle;
+ }
+
+ static class VehicleOwnerBroken {
+ @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "bogus")
+ @JsonIdentityReference(alwaysAsId = false)
+ public Vehicle ownedVehicle;
+ }
+
+ public void testWithAbstractUsingProp() throws Exception
+ {
+ Car c = new Car();
+ c.vehicleId = "123";
+ c.numberOfDoors = 2;
+ // both owners own the same vehicle (car sharing ;-))
+ VehicleOwnerViaProp v1 = new VehicleOwnerViaProp();
+ v1.ownedVehicle = c;
+ VehicleOwnerViaProp v2 = new VehicleOwnerViaProp();
+ v2.ownedVehicle = c;
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ String serialized = objectMapper.writer()
+ .writeValueAsString(new VehicleOwnerViaProp[] { v1, v2 });
+ // 02-May-2017, tatu: Not possible to support as of Jackson 2.8 at least, so:
+
+ VehicleOwnerViaProp[] deserialized = objectMapper.readValue(serialized, VehicleOwnerViaProp[].class);
+ assertEquals(2, deserialized.length);
+ assertSame(deserialized[0].ownedVehicle, deserialized[1].ownedVehicle);
+ }
+
+ public void testFailingAbstractUsingProp() throws Exception
+ {
+ Car c = new Car();
+ c.vehicleId = "123";
+ c.numberOfDoors = 2;
+ // both owners own the same vehicle (car sharing ;-))
+ VehicleOwnerBroken v1 = new VehicleOwnerBroken();
+ v1.ownedVehicle = c;
+ VehicleOwnerBroken v2 = new VehicleOwnerBroken();
+ v2.ownedVehicle = c;
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ try {
+ objectMapper.writer()
+ .writeValueAsString(new VehicleOwnerBroken[] { v1, v2 });
+ } catch (InvalidDefinitionException e) {
+ // on serialization, reported for different type
+ assertEquals(Car.class, e.getType().getRawClass());
+ verifyException(e, "Invalid Object Id definition");
+ verifyException(e, "cannot find property with name 'bogus'");
+ }
+
+ // and same for deser
+ final String JSON = aposToQuotes(
+"[{'ownedVehicle':{'@class':'com.fasterxml.jackson.failing.PolymorphicWithObjectId1551Test$Car','vehicleId':'123',"
++"'numberOfDoors':2}},{'ownedVehicle':'123'}]"
+ );
+ try {
+ objectMapper.readValue(JSON, VehicleOwnerBroken[].class);
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ assertEquals(Vehicle.class, e.getType().getRawClass());
+ verifyException(e, "Invalid Object Id definition");
+ verifyException(e, "cannot find property with name 'bogus'");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdSerialization.java b/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdSerialization.java
index 6d98d90..537d09f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdSerialization.java
@@ -331,7 +331,7 @@
MAPPER.writeValueAsString(new Broken());
fail("Should have thrown an exception");
} catch (JsonMappingException e) {
- verifyException(e, "can not find property with name 'id'");
+ verifyException(e, "cannot find property with name 'id'");
}
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithPolymorphic.java b/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithPolymorphic.java
index 2ecca8e..486abf3 100644
--- a/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithPolymorphic.java
+++ b/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithPolymorphic.java
@@ -2,11 +2,9 @@
import java.util.*;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
@@ -134,11 +132,6 @@
public void testIssue811() throws Exception
{
ObjectMapper om = new ObjectMapper();
- om.disable(MapperFeature.AUTO_DETECT_CREATORS);
- om.disable(MapperFeature.AUTO_DETECT_GETTERS);
- om.disable(MapperFeature.AUTO_DETECT_IS_GETTERS);
- om.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
-
om.enable(SerializationFeature.WRITE_ENUMS_USING_INDEX);
om.enable(SerializationFeature.INDENT_OUTPUT);
om.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, "@class");
diff --git a/src/test/java/com/fasterxml/jackson/databind/seq/ObjectReaderTest.java b/src/test/java/com/fasterxml/jackson/databind/seq/ObjectReaderTest.java
deleted file mode 100644
index f64d50b..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/seq/ObjectReaderTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package com.fasterxml.jackson.databind.seq;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import com.fasterxml.jackson.core.FormatSchema;
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonPointer;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.cfg.ContextAttributes;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-
-public class ObjectReaderTest extends BaseMapTest
-{
- final ObjectMapper MAPPER = new ObjectMapper();
-
- static class POJO {
- public Map<String, Object> name;
- }
-
- public void testSimpleViaParser() throws Exception
- {
- final String JSON = "[1]";
- JsonParser p = MAPPER.getFactory().createParser(JSON);
- Object ob = MAPPER.readerFor(Object.class)
- .readValue(p);
- p.close();
- assertTrue(ob instanceof List<?>);
- }
-
- public void testParserFeatures() throws Exception
- {
- final String JSON = "[ /* foo */ 7 ]";
- // default won't accept comments, let's change that:
- ObjectReader reader = MAPPER.readerFor(int[].class)
- .with(JsonParser.Feature.ALLOW_COMMENTS);
-
- int[] value = reader.readValue(JSON);
- assertNotNull(value);
- assertEquals(1, value.length);
- assertEquals(7, value[0]);
-
- // but also can go back
- try {
- reader.without(JsonParser.Feature.ALLOW_COMMENTS).readValue(JSON);
- fail("Should not have passed");
- } catch (JsonProcessingException e) {
- verifyException(e, "foo");
- }
- }
-
- public void testNoPointerLoading() throws Exception {
- final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
-
- JsonNode tree = MAPPER.readTree(source);
- JsonNode node = tree.at("/foo/bar/caller");
- POJO pojo = MAPPER.treeToValue(node, POJO.class);
- assertTrue(pojo.name.containsKey("value"));
- assertEquals(1234, pojo.name.get("value"));
- }
-
- public void testPointerLoading() throws Exception {
- final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
-
- ObjectReader reader = MAPPER.readerFor(POJO.class).at("/foo/bar/caller");
-
- POJO pojo = reader.readValue(source);
- assertTrue(pojo.name.containsKey("value"));
- assertEquals(1234, pojo.name.get("value"));
- }
-
- public void testPointerLoadingAsJsonNode() throws Exception {
- final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
-
- ObjectReader reader = MAPPER.readerFor(POJO.class).at(JsonPointer.compile("/foo/bar/caller"));
-
- JsonNode node = reader.readTree(source);
- assertTrue(node.has("name"));
- assertEquals("{\"value\":1234}", node.get("name").toString());
- }
-
- public void testPointerLoadingMappingIteratorOne() throws Exception {
- final String source = "{\"foo\":{\"bar\":{\"caller\":{\"name\":{\"value\":1234}}}}}";
-
- ObjectReader reader = MAPPER.readerFor(POJO.class).at("/foo/bar/caller");
-
- MappingIterator<POJO> itr = reader.readValues(source);
-
- POJO pojo = itr.next();
-
- assertTrue(pojo.name.containsKey("value"));
- assertEquals(1234, pojo.name.get("value"));
- assertFalse(itr.hasNext());
- itr.close();
- }
-
- public void testPointerLoadingMappingIteratorMany() throws Exception {
- final String source = "{\"foo\":{\"bar\":{\"caller\":[{\"name\":{\"value\":1234}}, {\"name\":{\"value\":5678}}]}}}";
-
- ObjectReader reader = MAPPER.readerFor(POJO.class).at("/foo/bar/caller");
-
- MappingIterator<POJO> itr = reader.readValues(source);
-
- POJO pojo = itr.next();
-
- assertTrue(pojo.name.containsKey("value"));
- assertEquals(1234, pojo.name.get("value"));
- assertTrue(itr.hasNext());
-
- pojo = itr.next();
-
- assertNotNull(pojo.name);
- assertTrue(pojo.name.containsKey("value"));
- assertEquals(5678, pojo.name.get("value"));
- assertFalse(itr.hasNext());
- itr.close();
- }
-
- public void testNodeHandling() throws Exception
- {
- JsonNodeFactory nodes = new JsonNodeFactory(true);
- ObjectReader r = MAPPER.reader().with(nodes);
- assertTrue(r.createArrayNode().isArray());
- assertTrue(r.createObjectNode().isObject());
- }
-
- public void testSettings() throws Exception
- {
- ObjectReader r = MAPPER.reader();
- assertSame(MAPPER.getFactory(), r.getFactory());
-
- JsonFactory f = new JsonFactory();
- r = r.with(f);
- assertSame(f, r.getFactory());
-
- assertNotNull(r.getTypeFactory());
- assertNull(r.getInjectableValues());
-
- r = r.withAttributes(Collections.emptyMap());
- ContextAttributes attrs = r.getAttributes();
- assertNotNull(attrs);
- assertNull(attrs.getAttribute("abc"));
-
- r = r.forType(MAPPER.constructType(String.class));
- r = r.withRootName(PropertyName.construct("foo"));
-
- r = r.withoutFeatures(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,
- DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
- assertFalse(r.isEnabled(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES));
- assertFalse(r.isEnabled(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE));
- r = r.withFeatures(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,
- DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
- assertTrue(r.isEnabled(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES));
- assertTrue(r.isEnabled(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE));
- }
-
- public void testNoPrefetch() throws Exception
- {
- ObjectReader r = MAPPER.reader()
- .without(DeserializationFeature.EAGER_DESERIALIZER_FETCH);
- Number n = r.forType(Integer.class).readValue("123 ");
- assertEquals(Integer.valueOf(123), n);
- }
-
- /*
- /**********************************************************
- /* Test methods, failures
- /**********************************************************
- */
-
- public void testMissingType() throws Exception
- {
- ObjectReader r = MAPPER.reader();
- try {
- r.readValue("1");
- fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "No value type configured");
- }
- }
-
- public void testSchema() throws Exception
- {
- ObjectReader r = MAPPER.readerFor(String.class);
-
- // Ok to try to set `null` schema, always:
- r = r.with((FormatSchema) null);
-
- try {
- // but not schema that doesn't match format (no schema exists for json)
- r = r.with(new BogusSchema())
- .readValue(quote("foo"));
-
- fail("Should not pass");
- } catch (IllegalArgumentException e) {
- verifyException(e, "Can not use FormatSchema");
- }
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/seq/SequenceWriterTest.java b/src/test/java/com/fasterxml/jackson/databind/seq/SequenceWriterTest.java
index 676383f..c103a34 100644
--- a/src/test/java/com/fasterxml/jackson/databind/seq/SequenceWriterTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/seq/SequenceWriterTest.java
@@ -1,12 +1,17 @@
package com.fasterxml.jackson.databind.seq;
+import java.io.Closeable;
+import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
+
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.io.SerializedString;
+
import com.fasterxml.jackson.databind.*;
public class SequenceWriterTest extends BaseMapTest
@@ -43,6 +48,40 @@
public ImplB(int v) { b = v; }
}
+ static class BareBase {
+ public int a = 1;
+ }
+
+ @JsonPropertyOrder({ "a", "b" })
+ static class BareBaseExt extends BareBase {
+ public int b = 2;
+ }
+
+ static class BareBaseCloseable extends BareBase
+ implements Closeable
+ {
+ public int c = 3;
+
+ boolean closed = false;
+
+ @Override
+ public void close() throws IOException {
+ closed = true;
+ }
+ }
+
+ static class CloseableValue implements Closeable
+ {
+ public int x;
+
+ public boolean closed;
+
+ @Override
+ public void close() throws IOException {
+ closed = true;
+ }
+ }
+
/*
/**********************************************************
/* Test methods, simple writes
@@ -57,6 +96,7 @@
{
StringWriter strw = new StringWriter();
SequenceWriter w = WRITER
+ .forType(Bean.class)
.writeValues(strw);
w.write(new Bean(13))
.write(new Bean(-6))
@@ -136,7 +176,6 @@
strw.toString());
}
- @SuppressWarnings("resource")
public void testPolymorphicArrayWithType() throws Exception
{
StringWriter strw = new StringWriter();
@@ -145,9 +184,53 @@
.writeValuesAsArray(strw);
w.write(new ImplA(-1))
.write(new ImplB(3))
- .write(new ImplA(7))
- .close();
+ .write(new ImplA(7));
+ w.flush();
+ w.close();
assertEquals(aposToQuotes("[{'type':'A','value':-1},{'type':'B','b':3},{'type':'A','value':7}]"),
strw.toString());
}
+
+ @SuppressWarnings("resource")
+ public void testSimpleCloseable() throws Exception
+ {
+ ObjectWriter w = MAPPER.writer()
+ .with(SerializationFeature.CLOSE_CLOSEABLE);
+ CloseableValue input = new CloseableValue();
+ assertFalse(input.closed);
+ StringWriter out = new StringWriter();
+ SequenceWriter seq = w.writeValues(out);
+ input = new CloseableValue();
+ assertFalse(input.closed);
+ seq.write(input);
+ assertTrue(input.closed);
+ seq.close();
+ input.close();
+ assertEquals(aposToQuotes("{'x':0,'closed':false}"), out.toString());
+ }
+
+ public void testWithExplicitType() throws Exception
+ {
+ ObjectWriter w = MAPPER.writer()
+ // just for fun (and higher coverage):
+ .without(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)
+ .with(SerializationFeature.CLOSE_CLOSEABLE);
+ StringWriter out = new StringWriter();
+ SequenceWriter seq = w.writeValues(out);
+ // first full, as-is
+ seq.write(new BareBaseExt());
+ // but then just base type (no 'b' field)
+ seq.write(new BareBaseExt(), MAPPER.constructType(BareBase.class));
+
+ // one more. And note! Check for Closeable is for _value_, not type
+ // so it's fine to expect closing here
+ BareBaseCloseable cl = new BareBaseCloseable();
+ seq.write(cl, MAPPER.constructType(BareBase.class));
+ assertTrue(cl.closed);
+ cl.close();
+
+ seq.close();
+ seq.flush();
+ assertEquals(aposToQuotes("{'a':1,'b':2} {'a':1} {'a':1}"), out.toString());
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestInnerClassReaderFor.java b/src/test/java/com/fasterxml/jackson/databind/seq/TestInnerClassReaderFor.java
similarity index 95%
rename from src/test/java/com/fasterxml/jackson/failing/TestInnerClassReaderFor.java
rename to src/test/java/com/fasterxml/jackson/databind/seq/TestInnerClassReaderFor.java
index 7fef656..6cf85e4 100644
--- a/src/test/java/com/fasterxml/jackson/failing/TestInnerClassReaderFor.java
+++ b/src/test/java/com/fasterxml/jackson/databind/seq/TestInnerClassReaderFor.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.seq;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BaseMapTest;
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java
index 871f7b8..23746d4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java
@@ -35,6 +35,18 @@
}
}
+ // For [databind#1376]: allow disabling any-getter
+ static class NotEvenAnyBean extends AnyOnlyBean
+ {
+ @JsonAnyGetter(enabled=false)
+ @Override
+ public Map<String,Integer> any() {
+ throw new RuntimeException("Should not get called!)");
+ }
+
+ public int getValue() { return 42; }
+ }
+
static class MapAsAny
{
protected Map<String,Object> stuff = new LinkedHashMap<String,Object>();
@@ -121,13 +133,13 @@
/*
/**********************************************************
- /* Test cases
+ /* Test methods
/**********************************************************
*/
private final ObjectMapper MAPPER = new ObjectMapper();
- public void testSimpleJsonValue() throws Exception
+ public void testSimpleAnyBean() throws Exception
{
String json = MAPPER.writeValueAsString(new Bean());
Map<?,?> map = MAPPER.readValue(json, Map.class);
@@ -153,6 +165,12 @@
assertEquals("{\"a\":3}", json);
}
+ public void testAnyDisabling() throws Exception
+ {
+ String json = MAPPER.writeValueAsString(new NotEvenAnyBean());
+ assertEquals(aposToQuotes("{'value':42}"), json);
+ }
+
// Trying to repro [databind#577]
public void testAnyWithNull() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifier1612Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifier1612Test.java
new file mode 100644
index 0000000..2be2594
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifier1612Test.java
@@ -0,0 +1,66 @@
+package com.fasterxml.jackson.databind.ser;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerBuilder;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+
+public class BeanSerializerModifier1612Test extends BaseMapTest
+{
+ @JsonPropertyOrder({ "a", "b", "c" })
+ static class Bean1612 {
+ public Integer a;
+ public Integer b;
+ public Double c;
+
+ public Bean1612(Integer a, Integer b, Double c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+ }
+
+ static class Modifier1612 extends BeanSerializerModifier {
+ @Override
+ public BeanSerializerBuilder updateBuilder(SerializationConfig config, BeanDescription beanDesc,
+ BeanSerializerBuilder builder) {
+ List<BeanPropertyWriter> filtered = new ArrayList<BeanPropertyWriter>(2);
+ List<BeanPropertyWriter> properties = builder.getProperties();
+ //Make the filtered properties list bigger
+ builder.setFilteredProperties(new BeanPropertyWriter[] {properties.get(0), properties.get(1), properties.get(2)});
+
+ //The props will be shorter
+ filtered.add(properties.get(1));
+ filtered.add(properties.get(2));
+ builder.setProperties(filtered);
+ return builder;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Construction and setter methods
+ /**********************************************************
+ */
+
+ public void testIssue1612() throws Exception
+ {
+ SimpleModule mod = new SimpleModule();
+ mod.setSerializerModifier(new Modifier1612());
+ ObjectMapper objectMapper = new ObjectMapper()
+ .registerModule(mod);
+ try {
+ objectMapper.writeValueAsString(new Bean1612(0, 1, 2d));
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Failed to construct BeanSerializer");
+ verifyException(e, Bean1612.class.getName());
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java b/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifierTest.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifierTest.java
index 915a2ab..6f0e571 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifierTest.java
@@ -23,7 +23,7 @@
* construction of {@link BeanSerializer} instances.
*/
@SuppressWarnings("serial")
-public class TestBeanSerializer extends BaseMapTest
+public class BeanSerializerModifierTest extends BaseMapTest
{
static class SerializerModifierModule extends SimpleModule
{
@@ -142,8 +142,6 @@
}
}
- // for [JACKSON-670]
-
static class EmptyBean {
@JsonIgnore
public String name = "foo";
@@ -163,7 +161,8 @@
beanProperties.add(new BeanPropertyWriter(prop, f, null,
strType,
null, null, strType,
- false, null));
+ false, null,
+ null));
} catch (NoSuchFieldException e) {
throw new IllegalStateException(e.getMessage());
}
@@ -187,7 +186,7 @@
return new BogusBeanSerializer(42);
}
}
- // [Issue#120], arrays, collections, maps
+ // [databind#120], arrays, collections, maps
static class ArraySerializerModifier extends BeanSerializerModifier {
@Override
@@ -248,9 +247,7 @@
};
}
}
-
- enum EnumABC { A, B, C };
-
+
/*
/********************************************************
/* Unit tests: success
@@ -304,7 +301,6 @@
assertEquals("{\"bogus\":\"foo\"}", json);
}
- // [Issue#539]
public void testEmptyBean539() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
@@ -320,7 +316,7 @@
assertEquals("42", json);
}
- // [Issue#121]
+ // [databind#121]
public void testModifyArraySerializer() throws Exception
{
@@ -351,7 +347,7 @@
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule("test")
.setSerializerModifier(new EnumSerializerModifier()));
- assertEquals("123", mapper.writeValueAsString(EnumABC.C));
+ assertEquals("123", mapper.writeValueAsString(ABC.C));
}
public void testModifyKeySerializer() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonValue.java b/src/test/java/com/fasterxml/jackson/databind/ser/JsonValueTest.java
similarity index 81%
rename from src/test/java/com/fasterxml/jackson/databind/ser/TestJsonValue.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/JsonValueTest.java
index 4b5b4d6..ec1adb5 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonValue.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/JsonValueTest.java
@@ -16,7 +16,7 @@
* annotation with bean serialization.
*/
@SuppressWarnings("serial")
-public class TestJsonValue
+public class JsonValueTest
extends BaseMapTest
{
static class ValueClass<T>
@@ -26,12 +26,16 @@
public ValueClass(T v) { _value = v; }
@JsonValue T value() { return _value; }
-
- // shouldn't need this, but may be useful for troubleshooting:
- @Override
- public String toString() { return "???"; }
}
+ static class FieldValueClass<T>
+ {
+ @JsonValue(true)
+ final T _value;
+
+ public FieldValueClass(T v) { _value = v; }
+ }
+
/**
* Another test class to check that it is also possible to
* force specific serializer to use with @JsonValue annotated
@@ -54,8 +58,7 @@
{
public ToStringValueClass2(String value) { super(value); }
- /* Simple as well, but let's ensure that other getters won't matter...
- */
+ // Simple as well, but let's ensure that other getters won't matter...
@JsonProperty int getFoobar() { return 4; }
@@ -87,6 +90,15 @@
}
}
+ static class MapFieldBean
+ {
+ @JsonValue
+ Map<String,String> stuff = new HashMap<>();
+ {
+ stuff.put("b", "2");
+ }
+ }
+
static class MapAsNumber extends HashMap<String,String>
{
@JsonValue
@@ -99,6 +111,17 @@
public int value() { return 13; }
}
+ // Just to ensure it's possible to disable annotation (usually
+ // via mix-ins, but here directly)
+ @JsonPropertyOrder({ "x", "y" })
+ static class DisabledJsonValue {
+ @JsonValue(false)
+ public int x = 1;
+
+ @JsonValue(false)
+ public int getY() { return 2; }
+ }
+
static class IntExtBean {
public List<Internal> values = new ArrayList<Internal>();
@@ -178,10 +201,16 @@
private final ObjectMapper MAPPER = new ObjectMapper();
- public void testSimpleJsonValue() throws Exception
+ public void testSimpleMethodJsonValue() throws Exception
{
- String result = MAPPER.writeValueAsString(new ValueClass<String>("abc"));
- assertEquals("\"abc\"", result);
+ assertEquals("\"abc\"", MAPPER.writeValueAsString(new ValueClass<String>("abc")));
+ assertEquals("null", MAPPER.writeValueAsString(new ValueClass<String>(null)));
+ }
+
+ public void testSimpleFieldJsonValue() throws Exception
+ {
+ assertEquals("\"abc\"", MAPPER.writeValueAsString(new FieldValueClass<String>("abc")));
+ assertEquals("null", MAPPER.writeValueAsString(new FieldValueClass<String>(null)));
}
public void testJsonValueWithUseSerializer() throws Exception
@@ -199,6 +228,12 @@
assertEquals("\"xyz\"", result);
}
+ public void testDisabling() throws Exception
+ {
+ assertEquals(aposToQuotes("{'x':1,'y':2}"),
+ MAPPER.writeValueAsString(new DisabledJsonValue()));
+ }
+
public void testValueWithStaticType() throws Exception
{
// Ok; first, with dynamic type:
@@ -211,12 +246,15 @@
}
public void testMapWithJsonValue() throws Exception {
+ // First via method
assertEquals("{\"a\":\"1\"}", MAPPER.writeValueAsString(new MapBean()));
+
+ // then field
+ assertEquals("{\"b\":\"2\"}", MAPPER.writeValueAsString(new MapFieldBean()));
}
public void testWithMap() throws Exception {
assertEquals("42", MAPPER.writeValueAsString(new MapAsNumber()));
-
}
public void testWithList() throws Exception {
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/NumberSerTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/NumberSerTest.java
deleted file mode 100644
index 7d573d6..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/ser/NumberSerTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.fasterxml.jackson.databind.ser;
-
-import java.math.BigInteger;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.BaseMapTest;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/**
- * Unit tests for verifying serialization of simple basic non-structured
- * types; primitives (and/or their wrappers), Strings.
- */
-public class NumberSerTest extends BaseMapTest
-{
- private final ObjectMapper MAPPER = objectMapper();
-
- static class IntWrapper {
- public int i;
-
- public IntWrapper(int value) { i = value; }
- }
-
- static class IntAsString {
- @JsonFormat(shape=JsonFormat.Shape.STRING)
- @JsonProperty("value")
- public int foo = 3;
- }
-
- static class LongAsString {
- @JsonFormat(shape=JsonFormat.Shape.STRING)
- public long value = 4;
- }
-
- static class DoubleAsString {
- @JsonFormat(shape=JsonFormat.Shape.STRING)
- public double value = -0.5;
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- public void testDouble() throws Exception
- {
- double[] values = new double[] {
- 0.0, 1.0, 0.1, -37.01, 999.99, 0.3, 33.3, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
- };
- for (double d : values) {
- String expected = String.valueOf(d);
- if (Double.isNaN(d) || Double.isInfinite(d)) {
- expected = "\""+d+"\"";
- }
- assertEquals(expected, MAPPER.writeValueAsString(Double.valueOf(d)));
- }
- }
-
- public void testBigInteger() throws Exception
- {
- BigInteger[] values = new BigInteger[] {
- BigInteger.ONE, BigInteger.TEN, BigInteger.ZERO,
- BigInteger.valueOf(1234567890L),
- new BigInteger("123456789012345678901234568"),
- new BigInteger("-1250000124326904597090347547457")
- };
-
- for (BigInteger value : values) {
- String expected = value.toString();
- assertEquals(expected, MAPPER.writeValueAsString(value));
- }
- }
-
- public void testNumbersAsString() throws Exception
- {
- assertEquals(aposToQuotes("{'value':'3'}"), MAPPER.writeValueAsString(new IntAsString()));
- assertEquals(aposToQuotes("{'value':'4'}"), MAPPER.writeValueAsString(new LongAsString()));
- assertEquals(aposToQuotes("{'value':'-0.5'}"), MAPPER.writeValueAsString(new DoubleAsString()));
- }
-
- public void testConfigOverridesForNumbers() throws Exception
- {
- ObjectMapper mapper = new ObjectMapper();
- mapper.configOverride(Integer.TYPE) // for `int`
- .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
- assertEquals(aposToQuotes("{'i':'3'}"),
- mapper.writeValueAsString(new IntWrapper(3)));
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/SerializationFeaturesTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/SerializationFeaturesTest.java
index e408561..edaefcf 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/SerializationFeaturesTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/SerializationFeaturesTest.java
@@ -3,13 +3,9 @@
import java.io.*;
import java.util.*;
-import com.fasterxml.jackson.annotation.*;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
/**
* Unit tests for checking handling of some of {@link MapperFeature}s
@@ -18,52 +14,6 @@
public class SerializationFeaturesTest
extends BaseMapTest
{
- /**
- * Class with one explicitly defined getter, one name-based
- * auto-detectable getter.
- */
- static class GetterClass
- {
- @JsonProperty("x") public int getX() { return -2; }
- public int getY() { return 1; }
- }
-
- /**
- * Another test-class that explicitly disables auto-detection
- */
- @JsonAutoDetect(getterVisibility=Visibility.NONE)
- static class DisabledGetterClass
- {
- @JsonProperty("x") public int getX() { return -2; }
- public int getY() { return 1; }
- }
-
- /**
- * Another test-class that explicitly enables auto-detection
- */
- @JsonAutoDetect(isGetterVisibility=Visibility.NONE)
- static class EnabledGetterClass
- {
- @JsonProperty("x") public int getX() { return -2; }
- public int getY() { return 1; }
-
- // not auto-detected, since "is getter" auto-detect disabled
- public boolean isOk() { return true; }
- }
-
- /**
- * One more: only detect "isXxx", not "getXXX"
- */
- @JsonAutoDetect(getterVisibility=Visibility.NONE)
- static class EnabledIsGetterClass
- {
- // Won't be auto-detected any more
- public int getY() { return 1; }
-
- // but this will be
- public boolean isOk() { return true; }
- }
-
static class CloseableBean implements Closeable
{
public int a = 3;
@@ -83,82 +33,12 @@
public StringListBean(Collection<String> v) { values = v; }
}
- static class TCls {
- @JsonProperty("groupname")
- private String groupname;
-
- public void setName(String str) {
- this.groupname = str;
- }
- public String getName() {
- return groupname;
- }
- }
-
/*
/**********************************************************
/* Test methods
/**********************************************************
*/
- public void testGlobalAutoDetection() throws IOException
- {
- // First: auto-detection enabled (default):
- ObjectMapper m = new ObjectMapper();
- Map<String,Object> result = writeAndMap(m, new GetterClass());
- assertEquals(2, result.size());
- assertEquals(Integer.valueOf(-2), result.get("x"));
- assertEquals(Integer.valueOf(1), result.get("y"));
-
- // Then auto-detection disabled. But note: we MUST create a new
- // mapper, since old version of serializer may be cached by now
- m = new ObjectMapper();
- m.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
- result = writeAndMap(m, new GetterClass());
- assertEquals(1, result.size());
- assertTrue(result.containsKey("x"));
- }
-
- public void testPerClassAutoDetection() throws IOException
- {
- // First: class-level auto-detection disabling
- ObjectMapper m = new ObjectMapper();
- Map<String,Object> result = writeAndMap(m, new DisabledGetterClass());
- assertEquals(1, result.size());
- assertTrue(result.containsKey("x"));
-
- // And then class-level auto-detection enabling, should override defaults
- m.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
- result = writeAndMap(m, new EnabledGetterClass());
- assertEquals(2, result.size());
- assertTrue(result.containsKey("x"));
- assertTrue(result.containsKey("y"));
- }
-
- public void testPerClassAutoDetectionForIsGetter() throws IOException
- {
- ObjectMapper m = new ObjectMapper();
- // class level should override
- m.configure(MapperFeature.AUTO_DETECT_GETTERS, true);
- m.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
- Map<String,Object> result = writeAndMap(m, new EnabledIsGetterClass());
- assertEquals(1, result.size());
- assertTrue(result.containsKey("ok"));
- assertEquals(Boolean.TRUE, result.get("ok"));
- }
-
- // Simple test verifying that chainable methods work ok...
- public void testConfigChainability()
- {
- ObjectMapper m = new ObjectMapper();
- assertTrue(m.isEnabled(MapperFeature.AUTO_DETECT_SETTERS));
- assertTrue(m.isEnabled(MapperFeature.AUTO_DETECT_GETTERS));
- m.configure(MapperFeature.AUTO_DETECT_SETTERS, false)
- .configure(MapperFeature.AUTO_DETECT_GETTERS, false);
- assertFalse(m.isEnabled(MapperFeature.AUTO_DETECT_SETTERS));
- assertFalse(m.isEnabled(MapperFeature.AUTO_DETECT_GETTERS));
- }
-
// Test for [JACKSON-282]
@SuppressWarnings("resource")
public void testCloseCloseable() throws IOException
@@ -258,7 +138,7 @@
HashSet<Long> longs = new HashSet<Long>();
longs.add(42L);
assertEquals("42", writer.writeValueAsString(longs));
- // [Issue#180]
+ // [databind#180]
final String EXP_STRINGS = "{\"values\":\"foo\"}";
assertEquals(EXP_STRINGS, writer.writeValueAsString(new StringListBean(Collections.singletonList("foo"))));
@@ -268,29 +148,24 @@
// arrays:
assertEquals("true", writer.writeValueAsString(new boolean[] { true }));
+ assertEquals("[true,false]", writer.writeValueAsString(new boolean[] { true, false }));
assertEquals("true", writer.writeValueAsString(new Boolean[] { Boolean.TRUE }));
+
+ assertEquals("3", writer.writeValueAsString(new short[] { 3 }));
+ assertEquals("[3,2]", writer.writeValueAsString(new short[] { 3, 2 }));
+
assertEquals("3", writer.writeValueAsString(new int[] { 3 }));
+ assertEquals("[3,2]", writer.writeValueAsString(new int[] { 3, 2 }));
+
+ assertEquals("1", writer.writeValueAsString(new long[] { 1L }));
+ assertEquals("[-1,4]", writer.writeValueAsString(new long[] { -1L, 4L }));
+
+ assertEquals("0.5", writer.writeValueAsString(new double[] { 0.5 }));
+ assertEquals("[0.5,2.5]", writer.writeValueAsString(new double[] { 0.5, 2.5 }));
+
+ assertEquals("0.5", writer.writeValueAsString(new float[] { 0.5f }));
+ assertEquals("[0.5,2.5]", writer.writeValueAsString(new float[] { 0.5f, 2.5f }));
+
assertEquals(quote("foo"), writer.writeValueAsString(new String[] { "foo" }));
}
-
- public void testVisibilityFeatures() throws Exception
- {
- ObjectMapper om = new ObjectMapper();
- // Only use explicitly specified values to be serialized/deserialized (i.e., JSONProperty).
- om.configure(MapperFeature.AUTO_DETECT_FIELDS, false);
- om.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
- om.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
- om.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
- om.configure(MapperFeature.USE_GETTERS_AS_SETTERS, false);
- om.configure(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS, true);
- om.configure(MapperFeature.INFER_PROPERTY_MUTATORS, false);
- om.configure(MapperFeature.USE_ANNOTATIONS, true);
-
- JavaType javaType = om.getTypeFactory().constructType(TCls.class);
- BeanDescription desc = (BeanDescription) om.getSerializationConfig().introspect(javaType);
- List<BeanPropertyDefinition> props = desc.findProperties();
- if (props.size() != 1) {
- fail("Should find 1 property, not "+props.size()+"; properties = "+props);
- }
- }
}
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 ee47809..1aa8bd5 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestArraySerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestArraySerialization.java
@@ -73,8 +73,10 @@
public void testStringArray() throws Exception
{
- String json = MAPPER.writeValueAsString(new String[] { "a", "\"foo\"", null });
- assertEquals("[\"a\",\"\\\"foo\\\"\",null]", json);
+ assertEquals("[\"a\",\"\\\"foo\\\"\",null]",
+ MAPPER.writeValueAsString(new String[] { "a", "\"foo\"", null }));
+ assertEquals("[]",
+ MAPPER.writeValueAsString(new String[] { }));
}
public void testDoubleArray() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestCustomSerializers.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestCustomSerializers.java
index 12e2219..1feeba6 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestCustomSerializers.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestCustomSerializers.java
@@ -22,19 +22,14 @@
/**
* Tests for verifying various issues with custom serializers.
*/
+@SuppressWarnings("serial")
public class TestCustomSerializers extends BaseMapTest
{
- /*
- /**********************************************************
- /* Helper beans
- /**********************************************************
- */
-
static class ElementSerializer extends JsonSerializer<Element>
{
@Override
- public void serialize(Element value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
- jgen.writeString("element");
+ public void serialize(Element value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
+ gen.writeString("element");
}
}
@@ -49,7 +44,6 @@
/**
* Trivial simple custom escape definition set.
*/
- @SuppressWarnings("serial")
static class CustomEscapes extends CharacterEscapes
{
private final int[] _asciiEscapes;
@@ -110,8 +104,7 @@
prop = o;
}
}
-
- @SuppressWarnings("serial")
+
static class ParentClassSerializer
extends StdScalarSerializer<Object>
{
@@ -127,7 +120,32 @@
gen.writeString(desc+"/"+value);
}
}
-
+
+ static class UCStringSerializer extends StdScalarSerializer<String>
+ {
+ public UCStringSerializer() { super(String.class); }
+
+ @Override
+ public void serialize(String value, JsonGenerator gen,
+ SerializerProvider provider) throws IOException {
+ gen.writeString(value.toUpperCase());
+ }
+ }
+
+ // IMPORTANT: must associate serializer via property annotations
+ protected static class StringListWrapper
+ {
+ @JsonSerialize(contentUsing=UCStringSerializer.class)
+ public List<String> list;
+
+ public StringListWrapper(String... values) {
+ list = new ArrayList<>();
+ for (String value : values) {
+ list.add(value);
+ }
+ }
+ }
+
/*
/**********************************************************
/* Unit tests
@@ -156,12 +174,13 @@
module.addSerializer(Collection.class, new JsonSerializer<Collection>() {
@Override
- public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider)
- throws IOException, JsonProcessingException {
+ public void serialize(Collection value, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
if (value.size() != 0) {
- collectionSerializer.serialize(value, jgen, provider);
+ collectionSerializer.serialize(value, gen, provider);
} else {
- jgen.writeNull();
+ gen.writeNull();
}
}
});
@@ -169,7 +188,7 @@
assertEquals("null", mapper.writeValueAsString(new ArrayList<Object>()));
}
- // [Issue#87]: delegating serializer
+ // [databind#87]: delegating serializer
public void testDelegating() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
@@ -189,7 +208,7 @@
assertEquals("{\"x\":3,\"y\":7}", mapper.writeValueAsString(new Immutable()));
}
- // [Issue#215]: Allow registering CharacterEscapes via ObjectWriter
+ // [databind#215]: Allow registering CharacterEscapes via ObjectWriter
public void testCustomEscapes() throws Exception
{
assertEquals(quote("foo\\u0062\\Ar"),
@@ -207,4 +226,29 @@
assertEquals(aposToQuotes("{'prop':'Issue631Bean/42'}"),
MAPPER.writeValueAsString(new Issue631Bean(42)));
}
+
+ public void testWithCustomElements() throws Exception
+ {
+ // First variant that uses per-property override
+ StringListWrapper wr = new StringListWrapper("a", null, "b");
+ assertEquals(aposToQuotes("{'list':['A',null,'B']}"),
+ MAPPER.writeValueAsString(wr));
+
+ // and then per-type registration
+
+ SimpleModule module = new SimpleModule("test", Version.unknownVersion());
+ module.addSerializer(String.class, new UCStringSerializer());
+ ObjectMapper mapper = new ObjectMapper()
+ .registerModule(module);
+
+ assertEquals(quote("FOOBAR"), mapper.writeValueAsString("foobar"));
+ assertEquals(aposToQuotes("['FOO',null]"),
+ mapper.writeValueAsString(new String[] { "foo", null }));
+
+ List<String> list = Arrays.asList("foo", null);
+ assertEquals(aposToQuotes("['FOO',null]"), mapper.writeValueAsString(list));
+
+ Set<String> set = new LinkedHashSet<String>(Arrays.asList("foo", null));
+ assertEquals(aposToQuotes("['FOO',null]"), mapper.writeValueAsString(set));
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestIterable.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestIterable.java
index 48c2e30..2d49d3a 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestIterable.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestIterable.java
@@ -1,7 +1,6 @@
package com.fasterxml.jackson.databind.ser;
import java.io.IOException;
-import java.io.StringWriter;
import java.util.*;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -27,7 +26,8 @@
return _ints.iterator();
}
}
- // [JACKSON-689]
+
+ @JsonSerialize(typing=JsonSerialize.Typing.STATIC)
static class BeanWithIterable {
private final ArrayList<String> values = new ArrayList<String>();
{
@@ -37,6 +37,15 @@
public Iterable<String> getValues() { return values; }
}
+ static class BeanWithIterator {
+ private final ArrayList<String> values = new ArrayList<String>();
+ {
+ values.add("itValue");
+ }
+
+ public Iterator<String> getValues() { return values.iterator(); }
+ }
+
static class IntIterable implements Iterable<Integer>
{
@Override
@@ -96,37 +105,53 @@
/**********************************************************
*/
- private final static ObjectMapper MAPPER = new ObjectMapper();
-
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final ObjectMapper STATIC_MAPPER = new ObjectMapper();
+ {
+ STATIC_MAPPER.enable(MapperFeature.USE_STATIC_TYPING);
+ }
+
public void testIterator() throws IOException
{
- StringWriter sw = new StringWriter();
ArrayList<Integer> l = new ArrayList<Integer>();
l.add(1);
+ l.add(null);
l.add(-9);
l.add(0);
- MAPPER.writeValue(sw, l.iterator());
- assertEquals("[1,-9,0]", sw.toString().trim());
+
+ assertEquals("[1,null,-9,0]", MAPPER.writeValueAsString(l.iterator()));
+ l.clear();
+ assertEquals("[]", MAPPER.writeValueAsString(l.iterator()));
}
public void testIterable() throws IOException
{
- StringWriter sw = new StringWriter();
- MAPPER.writeValue(sw, new IterableWrapper(new int[] { 1, 2, 3 }));
- assertEquals("[1,2,3]", sw.toString().trim());
+ assertEquals("[1,2,3]",
+ MAPPER.writeValueAsString(new IterableWrapper(new int[] { 1, 2, 3 })));
}
- // [JACKSON-689], [JACKSON-876]
public void testWithIterable() throws IOException
{
- // 689:
assertEquals("{\"values\":[\"value\"]}",
- MAPPER.writeValueAsString(new BeanWithIterable()));
- // 876:
+ STATIC_MAPPER.writeValueAsString(new BeanWithIterable()));
assertEquals("[1,2,3]",
- MAPPER.writeValueAsString(new IntIterable()));
+ STATIC_MAPPER.writeValueAsString(new IntIterable()));
}
+ public void testWithIterator() throws IOException
+ {
+ assertEquals("{\"values\":[\"itValue\"]}",
+ STATIC_MAPPER.writeValueAsString(new BeanWithIterator()));
+
+ // [databind#1977]
+ ArrayList<Number> numbersList = new ArrayList<>();
+ numbersList.add(1);
+ numbersList.add(0.25);
+ String json = MAPPER.writeValueAsString(numbersList.iterator());
+ assertEquals("[1,0.25]", json);
+ }
+
// [databind#358]
public void testIterable358() throws Exception {
String json = MAPPER.writeValueAsString(new B());
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize.java
index 01cabf2..483ccb2 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize.java
@@ -148,6 +148,7 @@
{
try {
serializeAsString(MAPPER, new BrokenClass());
+ fail("Should not succeed");
} catch (Exception e) {
verifyException(e, "types not related");
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestKeySerializers.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestKeySerializers.java
index ccd674b..dd97392 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestKeySerializers.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestKeySerializers.java
@@ -21,6 +21,26 @@
}
}
+ public static class NullKeySerializer extends JsonSerializer<Object>
+ {
+ private String _null;
+ public NullKeySerializer(String s) { _null = s; }
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ gen.writeFieldName(_null);
+ }
+ }
+
+ public static class NullValueSerializer extends JsonSerializer<Object>
+ {
+ private String _null;
+ public NullValueSerializer(String s) { _null = s; }
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ gen.writeString(_null);
+ }
+ }
+
public static class NotKarlBean
{
public Map<String,Integer> map = new HashMap<String,Integer>();
@@ -138,7 +158,7 @@
// Test custom key serializer for enum
public void testCustomForEnum() throws IOException
{
- // can not use shared mapper as we are registering a module
+ // cannot use shared mapper as we are registering a module
final ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test");
mod.addKeySerializer(ABC.class, new ABCKeySerializer());
@@ -148,6 +168,19 @@
assertEquals("{\"stuff\":{\"xxxB\":\"bar\"}}", json);
}
+ public void testCustomNullSerializers() throws IOException
+ {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.getSerializerProvider().setNullKeySerializer(new NullKeySerializer("NULL-KEY"));
+ mapper.getSerializerProvider().setNullValueSerializer(new NullValueSerializer("NULL"));
+ Map<String,Integer> input = new HashMap<>();
+ input.put(null, 3);
+ String json = mapper.writeValueAsString(input);
+ assertEquals("{\"NULL-KEY\":3}", json);
+ json = mapper.writeValueAsString(new Object[] { 1, null, true });
+ assertEquals("[1,\"NULL\",true]", json);
+ }
+
public void testCustomEnumInnerMapKey() throws Exception {
Map<Outer, Object> outerMap = new HashMap<Outer, Object>();
Map<ABC, Map<String, String>> map = new EnumMap<ABC, Map<String, String>>(ABC.class);
@@ -190,6 +223,7 @@
}
// [databind#838]
+ @SuppressWarnings("deprecation")
public void testUnWrappedMapWithKeySerializer() throws Exception{
SimpleModule mod = new SimpleModule("test");
mod.addKeySerializer(ABC.class, new ABCKeySerializer());
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java
index a220237..26cabd6 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java
@@ -15,7 +15,7 @@
@SuppressWarnings("serial")
public class TestMapSerialization extends BaseMapTest
{
- @JsonSerialize(using=MapSerializer.class)
+ @JsonSerialize(using=PseudoMapSerializer.class)
static class PseudoMap extends LinkedHashMap<String,String>
{
public PseudoMap(String... values) {
@@ -25,7 +25,7 @@
}
}
- static class MapSerializer extends JsonSerializer<Map<String,String>>
+ static class PseudoMapSerializer extends JsonSerializer<Map<String,String>>
{
@Override
public void serialize(Map<String,String> value,
@@ -36,16 +36,6 @@
}
}
- // For [JACKSON-574]
- static class DefaultKeySerializer extends JsonSerializer<Object>
- {
- @Override
- public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException
- {
- jgen.writeFieldName("DEFAULT:"+value);
- }
- }
-
// [databind#335]
static class MapOrderingBean {
@JsonPropertyOrder(alphabetic=true)
@@ -93,29 +83,6 @@
}
}
- // for [databind#47]
- public static class Wat
- {
- private final String wat;
-
- @JsonCreator
- Wat(String wat) {
- this.wat = wat;
- }
-
- @JsonValue
- public String getWat() {
- return wat;
- }
-
- @Override
- public String toString() {
- return "(String)[Wat: " + wat + "]";
- }
- }
-
- static class WatMap extends HashMap<Wat,Boolean> { }
-
// for [databind#691]
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME)
@JsonTypeName("mymap")
@@ -147,7 +114,7 @@
}
// problems with map entries, values
- public void testMapKeyValueSerialization() throws IOException
+ public void testMapKeySetValuesSerialization() throws IOException
{
Map<String,String> map = new HashMap<String,String>();
map.put("a", "b");
@@ -167,16 +134,6 @@
assertEquals("[\"f\"]", MAPPER.writeValueAsString(map.values()));
}
- // For [JACKSON-574]
- public void testDefaultKeySerializer() throws IOException
- {
- ObjectMapper m = new ObjectMapper();
- m.getSerializerProvider().setDefaultKeySerializer(new DefaultKeySerializer());
- Map<String,String> map = new HashMap<String,String>();
- map.put("a", "b");
- assertEquals("{\"DEFAULT:a\":\"b\"}", m.writeValueAsString(map));
- }
-
// sort Map entries by key
public void testOrderByKey() throws IOException
{
@@ -242,26 +199,6 @@
assertEquals(aposToQuotes("{'value':{'answer':42}}"), json);
}
- // [databind#47]
- public void testMapJsonValueKey47() throws Exception
- {
- WatMap input = new WatMap();
- input.put(new Wat("3"), true);
-
- ObjectMapper mapper = new ObjectMapper();
- String json = mapper.writeValueAsString(input);
- assertEquals(aposToQuotes("{'3':true}"), json);
- }
-
- // [databind#682]
- public void testClassKey() throws IOException
- {
- Map<Class<?>,Integer> map = new LinkedHashMap<Class<?>,Integer>();
- map.put(String.class, 2);
- String json = MAPPER.writeValueAsString(map);
- assertEquals(aposToQuotes("{'java.lang.String':2}"), json);
- }
-
// [databind#691]
public void testNullJsonMapping691() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestRootType.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestRootType.java
index 0795bfe..56af44d 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestRootType.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestRootType.java
@@ -56,7 +56,7 @@
public int a = 3;
}
- // [Issue#412]
+ // [databind#412]
static class TestCommandParent {
public String uuid;
public int type;
@@ -190,14 +190,13 @@
assertEquals("456", mapper.writerFor(Long.TYPE).writeValueAsString(Long.valueOf(456L)));
}
- // [JACKSON-630] also, allow annotation to define root name
public void testRootNameAnnotation() throws Exception
{
String json = WRAP_ROOT_MAPPER.writeValueAsString(new WithRootName());
assertEquals("{\"root\":{\"a\":3}}", json);
}
- // [Issue#412]
+ // [databind#412]
public void testRootNameWithExplicitType() throws Exception
{
TestCommandChild cmd = new TestCommandChild();
@@ -207,6 +206,6 @@
ObjectWriter writer = WRAP_ROOT_MAPPER.writerFor(TestCommandParent.class);
String json = writer.writeValueAsString(cmd);
- assertEquals(json, "{\"TestCommandParent\":{\"uuid\":\"1234\",\"type\":1}}");
+ assertEquals("{\"TestCommandParent\":{\"uuid\":\"1234\",\"type\":1}}", json);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestSimpleAtomicTypes.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestSimpleAtomicTypes.java
deleted file mode 100644
index afce72b..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestSimpleAtomicTypes.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.fasterxml.jackson.databind.ser;
-
-import java.util.concurrent.atomic.*;
-
-import com.fasterxml.jackson.databind.BaseMapTest;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
-/**
- * Unit tests for verifying serialization of simple basic non-structured
- * types; primitives (and/or their wrappers), Strings.
- */
-public class TestSimpleAtomicTypes
- extends BaseMapTest
-{
- static class UCStringWrapper {
- @JsonSerialize(contentUsing=UpperCasingSerializer.class)
- public AtomicReference<String> value;
-
- public UCStringWrapper(String s) { value = new AtomicReference<String>(s); }
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- private final ObjectMapper MAPPER = objectMapper();
-
- public void testAtomicBoolean() throws Exception
- {
- assertEquals("true", MAPPER.writeValueAsString(new AtomicBoolean(true)));
- assertEquals("false", MAPPER.writeValueAsString(new AtomicBoolean(false)));
- }
-
- public void testAtomicInteger() throws Exception
- {
- assertEquals("1", MAPPER.writeValueAsString(new AtomicInteger(1)));
- assertEquals("-9", MAPPER.writeValueAsString(new AtomicInteger(-9)));
- }
-
- public void testAtomicLong() throws Exception
- {
- assertEquals("0", MAPPER.writeValueAsString(new AtomicLong(0)));
- }
-
- public void testAtomicReference() throws Exception
- {
- String[] strs = new String[] { "abc" };
- assertEquals("[\"abc\"]", MAPPER.writeValueAsString(new AtomicReference<String[]>(strs)));
- }
-
- public void testCustomSerializer() throws Exception
- {
- final String VALUE = "fooBAR";
- String json = MAPPER.writeValueAsString(new UCStringWrapper(VALUE));
- assertEquals(json, aposToQuotes("{'value':'FOOBAR'}"));
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropsForSerTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/IgnorePropsForSerTest.java
similarity index 78%
rename from src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropsForSerTest.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/IgnorePropsForSerTest.java
index a8342bd..73c34a4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/IgnorePropsForSerTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/IgnorePropsForSerTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import java.util.*;
@@ -63,6 +63,25 @@
}
}
+ // for [databind#1060]
+ static class IgnoreForListValuesXY {
+ @JsonIgnoreProperties({ "x" })
+ public List<XY> coordinates;
+
+ public IgnoreForListValuesXY() {
+ coordinates = Arrays.asList(new XY());
+ }
+ }
+
+ static class IgnoreForListValuesXYZ {
+ @JsonIgnoreProperties({ "y" })
+ public List<XYZ> coordinates;
+
+ public IgnoreForListValuesXYZ() {
+ coordinates = Arrays.asList(new XYZ());
+ }
+ }
+
/*
/****************************************************************
/* Unit tests
@@ -129,4 +148,17 @@
.setIgnorals(JsonIgnoreProperties.Value.forIgnoredProperties("x"));
assertEquals("{\"y\":3}", mapper.writeValueAsString(new Point(2, 3)));
}
+
+ // for [databind#1060]
+ // Ensure that `@JsonIgnoreProperties` applies to POJOs within lists, too
+ public void testIgnoreForListValues() throws Exception
+ {
+ // should apply to elements
+ assertEquals(aposToQuotes("{'coordinates':[{'y':2}]}"),
+ MAPPER.writeValueAsString(new IgnoreForListValuesXY()));
+
+ // and combine values too
+ assertEquals(aposToQuotes("{'coordinates':[{'z':3}]}"),
+ MAPPER.writeValueAsString(new IgnoreForListValuesXYZ()));
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/JsonInclude1327Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonInclude1327Test.java
similarity index 96%
rename from src/test/java/com/fasterxml/jackson/databind/filter/JsonInclude1327Test.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonInclude1327Test.java
index cf11a4d..bac6df4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/JsonInclude1327Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonInclude1327Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import java.util.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeArrayTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeArrayTest.java
new file mode 100644
index 0000000..4a48cf7
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeArrayTest.java
@@ -0,0 +1,127 @@
+package com.fasterxml.jackson.databind.ser.filter;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public class JsonIncludeArrayTest extends BaseMapTest
+{
+ static class NonEmptyByteArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public byte[] value;
+
+ public NonEmptyByteArray(byte... v) { value = v; }
+ }
+
+ static class NonEmptyShortArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public short[] value;
+
+ public NonEmptyShortArray(short... v) { value = v; }
+ }
+
+ static class NonEmptyCharArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public char[] value;
+
+ public NonEmptyCharArray(char... v) { value = v; }
+ }
+
+ static class NonEmptyIntArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public int[] value;
+
+ public NonEmptyIntArray(int... v) { value = v; }
+ }
+
+ static class NonEmptyLongArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public long[] value;
+
+ public NonEmptyLongArray(long... v) { value = v; }
+ }
+
+ static class NonEmptyBooleanArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public boolean[] value;
+
+ public NonEmptyBooleanArray(boolean... v) { value = v; }
+ }
+
+ static class NonEmptyDoubleArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public double[] value;
+
+ public NonEmptyDoubleArray(double... v) { value = v; }
+ }
+
+ static class NonEmptyFloatArray {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public float[] value;
+
+ public NonEmptyFloatArray(float... v) { value = v; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testByteArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyByteArray()));
+ }
+
+ public void testShortArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyShortArray()));
+ assertEquals("{\"value\":[1]}", MAPPER.writeValueAsString(new NonEmptyShortArray((short) 1)));
+ }
+
+ public void testCharArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyCharArray()));
+ // by default considered to be serialized as String
+ assertEquals("{\"value\":\"ab\"}", MAPPER.writeValueAsString(new NonEmptyCharArray('a', 'b')));
+ // but can force as sparse (real) array too
+ assertEquals("{\"value\":[\"a\",\"b\"]}", MAPPER
+ .writer().with(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS)
+ .writeValueAsString(new NonEmptyCharArray('a', 'b')));
+ }
+
+ public void testIntArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyIntArray()));
+ assertEquals("{\"value\":[2]}", MAPPER.writeValueAsString(new NonEmptyIntArray(2)));
+ }
+
+ public void testLongArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyLongArray()));
+ assertEquals("{\"value\":[3,4]}", MAPPER.writeValueAsString(new NonEmptyLongArray(3, 4)));
+ }
+
+ public void testBooleanArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyBooleanArray()));
+ assertEquals("{\"value\":[true,false]}", MAPPER.writeValueAsString(new NonEmptyBooleanArray(true,false)));
+ }
+
+ public void testDoubleArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyDoubleArray()));
+ assertEquals("{\"value\":[0.25,-1.0]}", MAPPER.writeValueAsString(new NonEmptyDoubleArray(0.25,-1.0)));
+ }
+
+ public void testFloatArray() throws IOException
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyFloatArray()));
+ assertEquals("{\"value\":[0.5]}", MAPPER.writeValueAsString(new NonEmptyFloatArray(0.5f)));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCollectionTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCollectionTest.java
new file mode 100644
index 0000000..699a194
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCollectionTest.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.databind.ser.filter;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JsonIncludeCollectionTest extends BaseMapTest
+{
+ static class NonEmptyEnumSet {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public EnumSet<ABC> v;
+
+ public NonEmptyEnumSet(ABC...values) {
+ if (values.length == 0) {
+ v = EnumSet.noneOf(ABC.class);
+ } else {
+ v = EnumSet.copyOf(Arrays.asList(values));
+ }
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testEnumSet() throws Exception
+ {
+ assertEquals("{}", MAPPER.writeValueAsString(new NonEmptyEnumSet()));
+ assertEquals("{\"v\":[\"B\"]}", MAPPER.writeValueAsString(new NonEmptyEnumSet(ABC.B)));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java
new file mode 100644
index 0000000..0e3b0f0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java
@@ -0,0 +1,102 @@
+package com.fasterxml.jackson.databind.ser.filter;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+
+// Tests for [databind#888]
+public class JsonIncludeCustomTest extends BaseMapTest
+{
+ static class FooFilter {
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) { // do NOT filter out nulls
+ return false;
+ }
+ // in fact, only filter out exact String "foo"
+ return "foo".equals(other);
+ }
+ }
+
+ // for testing prob with `equals(null)` which SHOULD be allowed
+ static class BrokenFilter {
+ @Override
+ public boolean equals(Object other) {
+ /*String str = */ other.toString();
+ return false;
+ }
+ }
+
+ static class FooBean {
+ @JsonInclude(value=JsonInclude.Include.CUSTOM,
+ valueFilter=FooFilter.class)
+ public String value;
+
+ public FooBean(String v) { value = v; }
+ }
+
+ static class FooMapBean {
+ @JsonInclude(content=JsonInclude.Include.CUSTOM,
+ contentFilter=FooFilter.class)
+ public Map<String,String> stuff = new LinkedHashMap<String,String>();
+
+ public FooMapBean add(String key, String value) {
+ stuff.put(key, value);
+ return this;
+ }
+ }
+
+ static class BrokenBean {
+ @JsonInclude(value=JsonInclude.Include.CUSTOM,
+ valueFilter=BrokenFilter.class)
+ public String value;
+
+ public BrokenBean(String v) { value = v; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, success
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testSimpleCustomFilter() throws Exception
+ {
+ assertEquals(aposToQuotes("{'value':'x'}"), MAPPER.writeValueAsString(new FooBean("x")));
+ assertEquals("{}", MAPPER.writeValueAsString(new FooBean("foo")));
+ }
+
+ public void testCustomFilterWithMap() throws Exception
+ {
+ FooMapBean input = new FooMapBean()
+ .add("a", "1")
+ .add("b", "foo")
+ .add("c", "2");
+
+ assertEquals(aposToQuotes("{'stuff':{'a':'1','c':'2'}}"), MAPPER.writeValueAsString(input));
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, fail handling
+ /**********************************************************
+ */
+
+ public void testBrokenFilter() throws Exception
+ {
+ try {
+ String json = MAPPER.writeValueAsString(new BrokenBean("foo"));
+ fail("Should not pass, produced: "+json);
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Problem determining whether filter of type");
+ verifyException(e, "filter out `null`");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeOverrideTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeOverrideTest.java
new file mode 100644
index 0000000..1a8dbc5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeOverrideTest.java
@@ -0,0 +1,191 @@
+package com.fasterxml.jackson.databind.ser.filter;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Unit tests for checking that overridden settings for
+ * {@link com.fasterxml.jackson.databind.annotation.JsonSerialize#include} annotation property work
+ * as expected.
+ */
+public class JsonIncludeOverrideTest
+ extends BaseMapTest
+{
+ @JsonPropertyOrder({"list", "map"})
+ static class EmptyListMapBean
+ {
+ public List<String> list = Collections.emptyList();
+
+ public Map<String,String> map = Collections.emptyMap();
+ }
+
+ @JsonInclude(JsonInclude.Include.ALWAYS)
+ @JsonPropertyOrder({"num", "annotated", "plain"})
+ static class MixedTypeAlwaysBean
+ {
+ @JsonInclude(JsonInclude.Include.USE_DEFAULTS)
+ public Integer num = null;
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public String annotated = null;
+
+ public String plain = null;
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @JsonPropertyOrder({"num", "annotated", "plain"})
+ static class MixedTypeNonNullBean
+ {
+ @JsonInclude(JsonInclude.Include.USE_DEFAULTS)
+ public Integer num = null;
+
+ @JsonInclude(JsonInclude.Include.ALWAYS)
+ public String annotated = null;
+
+ public String plain = null;
+ }
+
+ public void testPropConfigOverridesForInclude() throws IOException
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ // First, with defaults, both included:
+ JsonIncludeOverrideTest.EmptyListMapBean empty = new JsonIncludeOverrideTest.EmptyListMapBean();
+ assertEquals(aposToQuotes("{'list':[],'map':{}}"),
+ mapper.writeValueAsString(empty));
+
+ // and then change inclusion criteria for either
+ mapper = new ObjectMapper();
+ mapper.configOverride(Map.class)
+ .setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, null));
+ assertEquals(aposToQuotes("{'list':[]}"),
+ mapper.writeValueAsString(empty));
+
+ mapper = new ObjectMapper();
+ mapper.configOverride(List.class)
+ .setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, null));
+ assertEquals(aposToQuotes("{'map':{}}"),
+ mapper.writeValueAsString(empty));
+ }
+
+ public void testOverrideForIncludeAsPropertyNonNull() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ // First, with defaults, all but NON_NULL annotated included
+ JsonIncludeOverrideTest.MixedTypeAlwaysBean nullValues = new JsonIncludeOverrideTest.MixedTypeAlwaysBean();
+ assertEquals(aposToQuotes("{'num':null,'plain':null}"),
+ mapper.writeValueAsString(nullValues));
+
+ // and then change inclusion as property criteria for either
+ mapper = new ObjectMapper();
+ mapper.configOverride(String.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.NON_NULL, null));
+ assertEquals("{\"num\":null}",
+ mapper.writeValueAsString(nullValues));
+
+ mapper = new ObjectMapper();
+ mapper.configOverride(Integer.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.NON_NULL, null));
+ assertEquals("{\"plain\":null}",
+ mapper.writeValueAsString(nullValues));
+ }
+
+ public void testOverrideForIncludeAsPropertyAlways() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ // First, with defaults, only ALWAYS annotated included
+ JsonIncludeOverrideTest.MixedTypeNonNullBean nullValues = new JsonIncludeOverrideTest.MixedTypeNonNullBean();
+ assertEquals("{\"annotated\":null}",
+ mapper.writeValueAsString(nullValues));
+
+ // and then change inclusion as property criteria for either
+ mapper = new ObjectMapper();
+ mapper.configOverride(String.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.ALWAYS, null));
+ assertEquals(aposToQuotes("{'annotated':null,'plain':null}"),
+ mapper.writeValueAsString(nullValues));
+
+ mapper = new ObjectMapper();
+ mapper.configOverride(Integer.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.ALWAYS, null));
+ assertEquals(aposToQuotes("{'num':null,'annotated':null}"),
+ mapper.writeValueAsString(nullValues));
+ }
+
+ public void testOverridesForIncludeAndIncludeAsPropertyNonNull() throws Exception
+ {
+ // First, with ALWAYS override on containing bean, all included
+ JsonIncludeOverrideTest.MixedTypeNonNullBean nullValues = new JsonIncludeOverrideTest.MixedTypeNonNullBean();
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(JsonIncludeOverrideTest.MixedTypeNonNullBean.class)
+ .setInclude(JsonInclude.Value
+ .construct(JsonInclude.Include.ALWAYS, null));
+ assertEquals(aposToQuotes("{'num':null,'annotated':null,'plain':null}"),
+ mapper.writeValueAsString(nullValues));
+
+ // and then change inclusion as property criteria for either
+ mapper = new ObjectMapper();
+ mapper.configOverride(JsonIncludeOverrideTest.MixedTypeNonNullBean.class)
+ .setInclude(JsonInclude.Value
+ .construct(JsonInclude.Include.ALWAYS, null));
+ mapper.configOverride(String.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.NON_NULL, null));
+ assertEquals(aposToQuotes("{'num':null,'annotated':null}"),
+ mapper.writeValueAsString(nullValues));
+
+ mapper = new ObjectMapper();
+ mapper.configOverride(JsonIncludeOverrideTest.MixedTypeNonNullBean.class)
+ .setInclude(JsonInclude.Value
+ .construct(JsonInclude.Include.ALWAYS, null));
+ mapper.configOverride(Integer.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.NON_NULL, null));
+ assertEquals(aposToQuotes("{'annotated':null,'plain':null}"),
+ mapper.writeValueAsString(nullValues));
+ }
+
+ public void testOverridesForIncludeAndIncludeAsPropertyAlways() throws Exception
+ {
+ // First, with NON_NULL override on containing bean, empty
+ JsonIncludeOverrideTest.MixedTypeAlwaysBean nullValues = new JsonIncludeOverrideTest.MixedTypeAlwaysBean();
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(JsonIncludeOverrideTest.MixedTypeAlwaysBean.class)
+ .setInclude(JsonInclude.Value
+ .construct(JsonInclude.Include.NON_NULL, null));
+ assertEquals("{}",
+ mapper.writeValueAsString(nullValues));
+
+ // and then change inclusion as property criteria for either
+ mapper = new ObjectMapper();
+ mapper.configOverride(JsonIncludeOverrideTest.MixedTypeAlwaysBean.class)
+ .setInclude(JsonInclude.Value
+ .construct(JsonInclude.Include.NON_NULL, null));
+ mapper.configOverride(String.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.ALWAYS, null));
+ assertEquals("{\"plain\":null}",
+ mapper.writeValueAsString(nullValues));
+
+ mapper = new ObjectMapper();
+ mapper.configOverride(JsonIncludeOverrideTest.MixedTypeAlwaysBean.class)
+ .setInclude(JsonInclude.Value
+ .construct(JsonInclude.Include.NON_NULL, null));
+ mapper.configOverride(Integer.class)
+ .setIncludeAsProperty(JsonInclude.Value
+ .construct(JsonInclude.Include.ALWAYS, null));
+ assertEquals("{\"num\":null}",
+ mapper.writeValueAsString(nullValues));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeTest.java
similarity index 84%
rename from src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeTest.java
index a5aa82e..5d18dff 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import java.io.IOException;
import java.util.*;
@@ -121,12 +121,32 @@
public NonEmptyDouble(double v) { value = v; }
}
- @JsonPropertyOrder({"list", "map"})
- static class EmptyListMapBean
- {
- public List<String> list = Collections.emptyList();
+ static class NonEmpty<T> {
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public T value;
- public Map<String,String> map = Collections.emptyMap();
+ public NonEmpty(T v) { value = v; }
+ }
+
+ static class NonEmptyDate extends NonEmpty<Date> {
+ public NonEmptyDate(Date v) { super(v); }
+ }
+ static class NonEmptyCalendar extends NonEmpty<Calendar> {
+ public NonEmptyCalendar(Calendar v) { super(v); }
+ }
+
+ static class NonDefault<T> {
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ public T value;
+
+ public NonDefault(T v) { value = v; }
+ }
+
+ static class NonDefaultDate extends NonDefault<Date> {
+ public NonDefaultDate(Date v) { super(v); }
+ }
+ static class NonDefaultCalendar extends NonDefault<Calendar> {
+ public NonDefaultCalendar(Calendar v) { super(v); }
}
// [databind#1351]
@@ -176,7 +196,7 @@
/*
/**********************************************************
- /* Unit tests
+ /* Test methods
/**********************************************************
*/
@@ -281,34 +301,11 @@
assertEquals("{\"value\":1.25}", defMapper.writeValueAsString(new NonEmptyDouble(1.25)));
assertEquals("{\"value\":0.0}", defMapper.writeValueAsString(new NonEmptyDouble(0.0)));
-
IntWrapper zero = new IntWrapper(0);
assertEquals("{\"i\":0}", defMapper.writeValueAsString(zero));
assertEquals("{\"i\":0}", inclMapper.writeValueAsString(zero));
}
- public void testPropConfigOverridesForInclude() throws IOException
- {
- // First, with defaults, both included:
- EmptyListMapBean empty = new EmptyListMapBean();
- assertEquals(aposToQuotes("{'list':[],'map':{}}"),
- MAPPER.writeValueAsString(empty));
- ObjectMapper mapper;
-
- // and then change inclusion criteria for either
- mapper = new ObjectMapper();
- mapper.configOverride(Map.class)
- .setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, null));
- assertEquals(aposToQuotes("{'list':[]}"),
- mapper.writeValueAsString(empty));
-
- mapper = new ObjectMapper();
- mapper.configOverride(List.class)
- .setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, null));
- assertEquals(aposToQuotes("{'map':{}}"),
- mapper.writeValueAsString(empty));
- }
-
// [databind#1351], [databind#1417]
public void testIssue1351() throws Exception
{
@@ -320,4 +317,27 @@
assertEquals(aposToQuotes("{}"),
mapper.writeValueAsString(new Issue1351NonBean(0)));
}
+
+ // [databind#1550]
+ public void testInclusionOfDate() throws Exception
+ {
+ final Date input = new Date(0L);
+ assertEquals(aposToQuotes("{'value':0}"),
+ MAPPER.writeValueAsString(new NonEmptyDate(input)));
+ assertEquals("{}",
+ MAPPER.writeValueAsString(new NonDefaultDate(input)));
+
+
+ }
+
+ // [databind#1550]
+ public void testInclusionOfCalendar() throws Exception
+ {
+ final Calendar input = new GregorianCalendar();
+ input.setTimeInMillis(0L);
+ assertEquals(aposToQuotes("{'value':0}"),
+ MAPPER.writeValueAsString(new NonEmptyCalendar(input)));
+ assertEquals("{}",
+ MAPPER.writeValueAsString(new NonDefaultCalendar(input)));
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/MapInclusionTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/MapInclusionTest.java
new file mode 100644
index 0000000..cdd0a94
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/MapInclusionTest.java
@@ -0,0 +1,83 @@
+package com.fasterxml.jackson.databind.ser.filter;
+
+import java.io.IOException;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.*;
+
+public class MapInclusionTest extends BaseMapTest
+{
+ static class NoEmptiesMapContainer {
+ @JsonInclude(value=JsonInclude.Include.NON_EMPTY,
+ content=JsonInclude.Include.NON_EMPTY)
+ public Map<String,String> stuff = new LinkedHashMap<String,String>();
+
+ public NoEmptiesMapContainer add(String key, String value) {
+ stuff.put(key, value);
+ return this;
+ }
+ }
+
+ static class NoNullsMapContainer {
+ @JsonInclude(value=JsonInclude.Include.NON_NULL,
+ content=JsonInclude.Include.NON_NULL)
+ public Map<String,String> stuff = new LinkedHashMap<String,String>();
+
+ public NoNullsMapContainer add(String key, String value) {
+ stuff.put(key, value);
+ return this;
+ }
+ }
+
+ static class NoNullsNotEmptyMapContainer {
+ @JsonInclude(value=JsonInclude.Include.NON_EMPTY,
+ content=JsonInclude.Include.NON_NULL)
+ public Map<String,String> stuff = new LinkedHashMap<String,String>();
+
+ public NoNullsNotEmptyMapContainer add(String key, String value) {
+ stuff.put(key, value);
+ return this;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = objectMapper();
+
+ // [databind#588]
+ public void testNonEmptyValueMapViaProp() throws IOException
+ {
+ String json = MAPPER.writeValueAsString(new NoEmptiesMapContainer()
+ .add("a", null)
+ .add("b", ""));
+ assertEquals(aposToQuotes("{}"), json);
+ }
+
+ public void testNoNullsMap() throws IOException
+ {
+ NoNullsMapContainer input = new NoNullsMapContainer()
+ .add("a", null)
+ .add("b", "");
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("{'stuff':{'b':''}}"), json);
+ }
+
+ public void testNonEmptyNoNullsMap() throws IOException
+ {
+ NoNullsNotEmptyMapContainer input = new NoNullsNotEmptyMapContainer()
+ .add("a", null)
+ .add("b", "");
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("{'stuff':{'b':''}}"), json);
+
+ json = MAPPER.writeValueAsString(new NoNullsNotEmptyMapContainer()
+ .add("a", null)
+ .add("b", null));
+ assertEquals(aposToQuotes("{}"), json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/NullSerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/NullSerializationTest.java
similarity index 91%
rename from src/test/java/com/fasterxml/jackson/databind/filter/NullSerializationTest.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/NullSerializationTest.java
index dc74036..d1cf083 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/NullSerializationTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/NullSerializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import java.io.*;
@@ -118,17 +118,6 @@
assertEquals("{\"a\":\"foobar\"}", m.writeValueAsString(root));
}
- /* 14-Oct-2013, tatu: Support for annotating classes is not
- * implemented yet.
- */
-/*
- public void testNullSerializerViaClass() throws Exception
- {
- assertEquals("[\"foobar\"]",
- MAPPER.writeValueAsString(new NullValuedType[] { new NullValuedType() }));
- }
- */
-
public void testNullSerializerForProperty() throws Exception
{
assertEquals("{\"a\":\"foobar\"}", MAPPER.writeValueAsString(new BeanWithNullProps()));
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/TestAnyGetterFiltering.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestAnyGetterFiltering.java
similarity index 63%
rename from src/test/java/com/fasterxml/jackson/databind/filter/TestAnyGetterFiltering.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/TestAnyGetterFiltering.java
index e579feb..dadc7e2 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/TestAnyGetterFiltering.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestAnyGetterFiltering.java
@@ -1,9 +1,11 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import java.util.*;
import com.fasterxml.jackson.annotation.*;
+
import com.fasterxml.jackson.core.JsonGenerator;
+
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
@@ -51,6 +53,32 @@
}
}
+ // [databind#1655]
+ @JsonFilter("CustomFilter")
+ static class OuterObject {
+ public int getExplicitProperty() {
+ return 42;
+ }
+
+ @JsonAnyGetter
+ public Map<String, Object> getAny() {
+ Map<String, Object> extra = new HashMap<>();
+ extra.put("dynamicProperty", "I will not serialize");
+ return extra;
+ }
+ }
+
+ static class CustomFilter extends SimpleBeanPropertyFilter {
+ @Override
+ public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider provider,
+ PropertyWriter writer) throws Exception
+ {
+ if (pojo instanceof OuterObject) {
+ writer.serializeAsField(pojo, gen, provider);
+ }
+ }
+ }
+
/*
/**********************************************************
/* Test methods
@@ -72,4 +100,15 @@
assertEquals(aposToQuotes("{'a':'1','b':'3'}"),
MAPPER.writeValueAsString(new AnyBeanWithIgnores()));
}
+
+ // [databind#1655]
+ public void testAnyGetterPojo1655() throws Exception
+ {
+ FilterProvider filters = new SimpleFilterProvider().addFilter("CustomFilter", new CustomFilter());
+ String json = MAPPER.writer(filters).writeValueAsString(new OuterObject());
+ Map<?,?> stuff = MAPPER.readValue(json, Map.class);
+ if (stuff.size() != 2) {
+ fail("Should have 2 properties, got: "+stuff);
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestIgnoredTypes.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestIgnoredTypes.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/deser/TestIgnoredTypes.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/TestIgnoredTypes.java
index 01b5c51..3279c02 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/TestIgnoredTypes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestIgnoredTypes.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.deser;
+package com.fasterxml.jackson.databind.ser.filter;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/TestJsonFilter.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestJsonFilter.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/filter/TestJsonFilter.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/TestJsonFilter.java
index b607af7..84b057c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/TestJsonFilter.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestJsonFilter.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import com.fasterxml.jackson.annotation.*;
@@ -146,7 +146,7 @@
MAPPER.writeValueAsString(new Bean());
fail("Should have failed without configured filter");
} catch (JsonMappingException e) { // should be resolved to a MappingException (internally may be something else)
- verifyException(e, "Can not resolve PropertyFilter with id 'RootFilter'");
+ verifyException(e, "Cannot resolve PropertyFilter with id 'RootFilter'");
}
// but when changing behavior, should work difference
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/TestMapFiltering.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestMapFiltering.java
similarity index 76%
rename from src/test/java/com/fasterxml/jackson/databind/filter/TestMapFiltering.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/TestMapFiltering.java
index a7a3729..ab2a9a9 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/TestMapFiltering.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestMapFiltering.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import java.io.IOException;
import java.lang.annotation.*;
@@ -14,6 +14,7 @@
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
+import com.fasterxml.jackson.databind.ser.std.MapProperty;
@SuppressWarnings("serial")
public class TestMapFiltering extends BaseMapTest
@@ -27,7 +28,7 @@
@JsonFilter("filterForMaps")
static class FilteredBean extends LinkedHashMap<String,Integer> { }
-
+
static class MapBean {
@JsonFilter("filterX")
@CustomOffset(1)
@@ -41,22 +42,44 @@
}
}
+ static class MapBeanNoOffset {
+ @JsonFilter("filterX")
+ public Map<String,Integer> values;
+
+ public MapBeanNoOffset() {
+ values = new LinkedHashMap<String,Integer>();
+ values.put("a", 1);
+ values.put("b", 2);
+ values.put("c", 3);
+ }
+ }
+
static class TestMapFilter implements PropertyFilter
{
@Override
- public void serializeAsField(Object value, JsonGenerator jgen,
+ public void serializeAsField(Object bean, JsonGenerator g,
SerializerProvider provider, PropertyWriter writer)
throws Exception
{
String name = writer.getName();
+
+ // sanity checks
+ assertNotNull(writer.getType());
+ assertEquals(name, writer.getFullName().getSimpleName());
+
if (!"a".equals(name)) {
return;
}
CustomOffset n = writer.findAnnotation(CustomOffset.class);
int offset = (n == null) ? 0 : n.value();
- Integer I = offset + ((Integer) value).intValue();
- writer.serializeAsField(I, jgen, provider);
+ // 12-Jun-2017, tatu: With 2.9, `value` is the surrounding POJO, so
+ // need to do casting
+ MapProperty prop = (MapProperty) writer;
+ Integer old = (Integer) prop.getValue();
+ prop.setValue(Integer.valueOf(offset + old.intValue()));
+
+ writer.serializeAsField(bean, g, provider);
}
@Override
@@ -70,15 +93,12 @@
@Deprecated
public void depositSchemaProperty(PropertyWriter writer,
ObjectNode propertiesNode, SerializerProvider provider)
- throws JsonMappingException {
-
- }
+ throws JsonMappingException { }
@Override
public void depositSchemaProperty(PropertyWriter writer,
JsonObjectFormatVisitor objectVisitor,
- SerializerProvider provider) throws JsonMappingException {
- }
+ SerializerProvider provider) throws JsonMappingException { }
}
// [databind#527]
@@ -180,6 +200,10 @@
String json = MAPPER.writer(prov).writeValueAsString(new MapBean());
// a=1 should become a=2
assertEquals(aposToQuotes("{'values':{'a':2}}"), json);
+
+ // and then one without annotation as contrast
+ json = MAPPER.writer(prov).writeValueAsString(new MapBeanNoOffset());
+ assertEquals(aposToQuotes("{'values':{'a':1}}"), json);
}
// [databind#527]
@@ -212,6 +236,7 @@
assertEquals(aposToQuotes("{'a':'foo'}"), json);
}
+ @SuppressWarnings("deprecation")
public void testMapNullSerialization() throws IOException
{
ObjectMapper m = new ObjectMapper();
@@ -220,7 +245,9 @@
// by default, should output null-valued entries:
assertEquals("{\"a\":null}", m.writeValueAsString(map));
// but not if explicitly asked not to (note: config value is dynamic here)
- m.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
+
+ m = new ObjectMapper();
+ m.disable(SerializationFeature.WRITE_NULL_MAP_VALUES);
assertEquals("{}", m.writeValueAsString(map));
}
@@ -240,4 +267,31 @@
.add("b", null)));
assertEquals(aposToQuotes("{}"), json);
}
+
+ public void testMapViaGlobalNonEmpty() throws Exception
+ {
+ // basic Map<String,String> subclass:
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setDefaultPropertyInclusion(JsonInclude.Value.empty()
+ .withContentInclusion(JsonInclude.Include.NON_EMPTY));
+ assertEquals(aposToQuotes("{'a':'b'}"), mapper.writeValueAsString(
+ new StringMap497()
+ .add("x", "")
+ .add("a", "b")
+ ));
+ }
+
+ public void testMapViaTypeOverride() throws Exception
+ {
+ // basic Map<String,String> subclass:
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(Map.class)
+ .setInclude(JsonInclude.Value.empty()
+ .withContentInclusion(JsonInclude.Include.NON_EMPTY));
+ assertEquals(aposToQuotes("{'a':'b'}"), mapper.writeValueAsString(
+ new StringMap497()
+ .add("foo", "")
+ .add("a", "b")
+ ));
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/TestSimpleSerializationIgnore.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestSimpleSerializationIgnore.java
similarity index 98%
rename from src/test/java/com/fasterxml/jackson/databind/filter/TestSimpleSerializationIgnore.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/filter/TestSimpleSerializationIgnore.java
index 5a580f7..92c7054 100644
--- a/src/test/java/com/fasterxml/jackson/databind/filter/TestSimpleSerializationIgnore.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/TestSimpleSerializationIgnore.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.filter;
+package com.fasterxml.jackson.databind.ser.filter;
import java.util.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/jdk/AtomicTypeSerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/AtomicTypeSerializationTest.java
new file mode 100644
index 0000000..19f2890
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/AtomicTypeSerializationTest.java
@@ -0,0 +1,138 @@
+package com.fasterxml.jackson.databind.ser.jdk;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+/**
+ * Unit tests for verifying serialization of {@link java.util.concurrent.atomic.AtomicReference}
+ * and other atomic types, via various settings.
+ */
+public class AtomicTypeSerializationTest
+ extends BaseMapTest
+{
+ static class UCStringWrapper {
+ @JsonSerialize(contentUsing=UpperCasingSerializer.class)
+ public AtomicReference<String> value;
+
+ public UCStringWrapper(String s) { value = new AtomicReference<String>(s); }
+ }
+
+ // [datatypes-java8#17]
+ @JsonPropertyOrder({ "date1", "date2", "date" })
+ static class ContextualOptionals
+ {
+ public AtomicReference<Date> date;
+
+ @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy+MM+dd")
+ public AtomicReference<Date> date1;
+
+ @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy*MM*dd")
+ public AtomicReference<Date> date2;
+ }
+
+ // [databind#1673]
+ static class ContainerA {
+ public AtomicReference<Strategy> strategy =
+ new AtomicReference<>((Strategy) new Foo(42));
+ }
+
+ static class ContainerB {
+ public AtomicReference<List<Strategy>> strategy;
+ {
+ List<Strategy> list = new ArrayList<>();
+ list.add(new Foo(42));
+ strategy = new AtomicReference<>(list);
+ }
+ }
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
+ @JsonSubTypes({ @JsonSubTypes.Type(name = "Foo", value = Foo.class) })
+ interface Strategy { }
+
+ static class Foo implements Strategy {
+ public int foo;
+
+ @JsonCreator
+ Foo(@JsonProperty("foo") int foo) {
+ this.foo = foo;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = objectMapper();
+
+ public void testAtomicBoolean() throws Exception
+ {
+ assertEquals("true", MAPPER.writeValueAsString(new AtomicBoolean(true)));
+ assertEquals("false", MAPPER.writeValueAsString(new AtomicBoolean(false)));
+ }
+
+ public void testAtomicInteger() throws Exception
+ {
+ assertEquals("1", MAPPER.writeValueAsString(new AtomicInteger(1)));
+ assertEquals("-9", MAPPER.writeValueAsString(new AtomicInteger(-9)));
+ }
+
+ public void testAtomicLong() throws Exception
+ {
+ assertEquals("0", MAPPER.writeValueAsString(new AtomicLong(0)));
+ }
+
+ public void testAtomicReference() throws Exception
+ {
+ String[] strs = new String[] { "abc" };
+ assertEquals("[\"abc\"]", MAPPER.writeValueAsString(new AtomicReference<String[]>(strs)));
+ }
+
+ public void testCustomSerializer() throws Exception
+ {
+ final String VALUE = "fooBAR";
+ String json = MAPPER.writeValueAsString(new UCStringWrapper(VALUE));
+ assertEquals(json, aposToQuotes("{'value':'FOOBAR'}"));
+ }
+
+ public void testContextualAtomicReference() throws Exception
+ {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ final ObjectMapper mapper = objectMapper();
+ mapper.setDateFormat(df);
+ ContextualOptionals input = new ContextualOptionals();
+ input.date = new AtomicReference<>(new Date(0L));
+ input.date1 = new AtomicReference<>(new Date(0L));
+ input.date2 = new AtomicReference<>(new Date(0L));
+ final String json = mapper.writeValueAsString(input);
+ assertEquals(aposToQuotes(
+ "{'date1':'1970+01+01','date2':'1970*01*01','date':'1970/01/01'}"),
+ json);
+ }
+
+ // [databind#1673]
+ public void testPolymorphicReferenceSimple() throws Exception
+ {
+ final String EXPECTED = "{\"type\":\"Foo\",\"foo\":42}";
+ String json = MAPPER.writeValueAsString(new ContainerA());
+ assertEquals("{\"strategy\":" + EXPECTED + "}", json);
+ }
+
+ // [databind#1673]
+ public void testPolymorphicReferenceListOf() throws Exception
+ {
+ final String EXPECTED = "{\"type\":\"Foo\",\"foo\":42}";
+ // Reproduction of issue seen with scala.Option and java8 Optional types:
+ // https://github.com/FasterXML/jackson-module-scala/issues/346#issuecomment-336483326
+ String json = MAPPER.writeValueAsString(new ContainerB());
+ assertEquals("{\"strategy\":[" + EXPECTED + "]}", json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestCollectionSerialization.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/CollectionSerializationTest.java
similarity index 85%
rename from src/test/java/com/fasterxml/jackson/databind/ser/TestCollectionSerialization.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/jdk/CollectionSerializationTest.java
index 59267de..1d09d74 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestCollectionSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/CollectionSerializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.ser;
+package com.fasterxml.jackson.databind.ser.jdk;
import java.io.*;
import java.util.*;
@@ -6,9 +6,10 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-public class TestCollectionSerialization
+public class CollectionSerializationTest
extends BaseMapTest
{
enum Key { A, B, C };
@@ -66,6 +67,18 @@
public String[] empty = new String[0];
}
+ static class StaticListWrapper {
+ protected List<String> list;
+
+ public StaticListWrapper(String ... v) {
+ list = new ArrayList<String>(Arrays.asList(v));
+ }
+ protected StaticListWrapper() { }
+
+ public List<String> getList( ) { return list; }
+ public void setList(List<String> l) { list = l; }
+ }
+
/*
/**********************************************************
/* Test methods
@@ -227,14 +240,15 @@
assertNull(result.get("map"));
}
- // Test [JACKSON-220]
public void testListSerializer() throws IOException
{
- assertEquals("\"[ab, cd, ef]\"",
+ assertEquals(quote("[ab, cd, ef]"),
MAPPER.writeValueAsString(new PseudoList("ab", "cd", "ef")));
+ assertEquals(quote("[]"),
+ MAPPER.writeValueAsString(new PseudoList()));
}
- // [JACKSON-254]
+ @SuppressWarnings("deprecation")
public void testEmptyListOrArray() throws IOException
{
// by default, empty lists serialized normally
@@ -250,4 +264,19 @@
assertEquals("{}", m.writeValueAsString(list));
assertEquals("{}", m.writeValueAsString(array));
}
+
+ public void testStaticList() throws IOException
+ {
+ // First: au naturel
+ StaticListWrapper w = new StaticListWrapper("a", "b", "c");
+ String json = MAPPER.writeValueAsString(w);
+ assertEquals(aposToQuotes("{'list':['a','b','c']}"), json);
+
+ // but then with default typing
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enableDefaultTyping(DefaultTyping.NON_FINAL);
+ json = mapper.writeValueAsString(w);
+ assertEquals(aposToQuotes(String.format("['%s',{'list':['%s',['a','b','c']]}]",
+ w.getClass().getName(), w.list.getClass().getName())), json);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/DateSerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/DateSerializationTest.java
similarity index 67%
rename from src/test/java/com/fasterxml/jackson/databind/ser/DateSerializationTest.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/jdk/DateSerializationTest.java
index aff0b09..e865b90 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/DateSerializationTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/DateSerializationTest.java
@@ -1,11 +1,24 @@
-package com.fasterxml.jackson.databind.ser;
+package com.fasterxml.jackson.databind.ser.jdk;
-import java.io.*;
-import java.text.*;
-import java.util.*;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.junit.Assert;
import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.util.StdDateFormat;
public class DateSerializationTest
extends BaseMapTest
@@ -26,17 +39,6 @@
public DateAsNumberBean(long l) { date = new java.util.Date(l); }
}
- static class SqlDateAsDefaultBean {
- public java.sql.Date date;
- public SqlDateAsDefaultBean(long l) { date = new java.sql.Date(l); }
- }
-
- static class SqlDateAsNumberBean {
- @JsonFormat(shape=JsonFormat.Shape.NUMBER)
- public java.sql.Date date;
- public SqlDateAsNumberBean(long l) { date = new java.sql.Date(l); }
- }
-
static class DateAsStringBean {
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
public Date date;
@@ -114,9 +116,72 @@
{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
- // let's hit epoch start
- String json = mapper.writeValueAsString(new Date(0L));
- assertEquals("\"1970-01-01T00:00:00.000+0000\"", json);
+
+ serialize(mapper, judate(1970, 1, 1, 02, 00, 00, 0, "GMT+2"), "1970-01-01T00:00:00.000+0000");
+ serialize(mapper, judate(1970, 1, 1, 00, 00, 00, 0, "UTC"), "1970-01-01T00:00:00.000+0000");
+
+ // 22-Nov-2018, tatu: Also ensure we use padding...
+ serialize(mapper, judate(911, 1, 1, 00, 00, 00, 0, "UTC"), "0911-01-01T00:00:00.000+0000");
+ serialize(mapper, judate(87, 1, 1, 00, 00, 00, 0, "UTC"), "0087-01-01T00:00:00.000+0000");
+ serialize(mapper, judate(1, 1, 1, 00, 00, 00, 0, "UTC"), "0001-01-01T00:00:00.000+0000");
+ }
+
+ // [databind#2167]: beyond year 9999 needs special handling
+ public void testDateISO8601_10k() throws IOException
+ {
+ ObjectWriter w = MAPPER.writer()
+ .without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ serialize(w, judate(10204, 1, 1, 00, 00, 00, 0, "UTC"), "+10204-01-01T00:00:00.000+0000");
+ // and although specification lacks for beyond 5 digits (well, actually even 5...), let's do our best:
+ serialize(w, judate(123456, 1, 1, 00, 00, 00, 0, "UTC"), "+123456-01-01T00:00:00.000+0000");
+ }
+
+ // [databind#2167]: dates before Common Era (CE), that is, BCE, need special care:
+ public void testDateISO8601_BCE() throws IOException
+ {
+ ObjectWriter w = MAPPER.writer()
+ .without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+ // First: I _think_ BCE-1 is what you get with year 0, and should become "+0000"
+ // and from further back in time, it'll be "-0001" (BCE-2) etc)
+
+ serialize(w, judate(0, 1, 1, 00, 00, 00, 0, "UTC"), "+0000-01-01T00:00:00.000+0000");
+ serialize(w, judate(-1, 1, 1, 00, 00, 00, 0, "UTC"), "-0001-01-01T00:00:00.000+0000");
+ serialize(w, judate(-49, 1, 1, 00, 00, 00, 0, "UTC"), "-0049-01-01T00:00:00.000+0000"); // All hail Caesar
+ serialize(w, judate(-264, 1, 1, 00, 00, 00, 0, "UTC"), "-0264-01-01T00:00:00.000+0000"); // Carthage FTW?
+ }
+
+ /**
+ * Use a default TZ other than UTC. Dates must be serialized using that TZ.
+ */
+ public void testDateISO8601_customTZ() throws IOException
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.setTimeZone(TimeZone.getTimeZone("GMT+2"));
+
+ serialize( mapper, judate(1970, 1, 1, 00, 00, 00, 0, "GMT+2"), "1970-01-01T00:00:00.000+0200");
+ serialize( mapper, judate(1970, 1, 1, 00, 00, 00, 0, "UTC"), "1970-01-01T02:00:00.000+0200");
+ }
+
+ /**
+ * Configure the StdDateFormat to serialize TZ offset with a colon between hours and minutes
+ *
+ * See [databind#1744]
+ */
+ public void testDateISO8601_colonInTZ() throws IOException
+ {
+ StdDateFormat dateFormat = new StdDateFormat();
+ assertFalse(dateFormat.isColonIncludedInTimeZone());
+ dateFormat = dateFormat.withColonInTimeZone(true);
+ assertTrue(dateFormat.isColonIncludedInTimeZone());
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.setDateFormat(dateFormat);
+
+ serialize( mapper, judate(1970, 1, 1, 02, 00, 00, 0, "GMT+2"), "1970-01-01T00:00:00.000+00:00");
+ serialize( mapper, judate(1970, 1, 1, 00, 00, 00, 0, "UTC"), "1970-01-01T00:00:00.000+00:00");
}
public void testDateOther() throws IOException
@@ -125,40 +190,9 @@
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'X'HH:mm:ss");
mapper.setDateFormat(df);
mapper.setTimeZone(TimeZone.getTimeZone("PST"));
+
// let's hit epoch start, offset by a bit
- assertEquals(quote("1969-12-31X16:00:00"), mapper.writeValueAsString(new Date(0L)));
- }
-
- @SuppressWarnings("deprecation")
- public void testSqlDate() throws IOException
- {
- // use date 1999-04-01 (note: months are 0-based, use constant)
- java.sql.Date date = new java.sql.Date(99, Calendar.APRIL, 1);
- assertEquals(quote("1999-04-01"), MAPPER.writeValueAsString(date));
-
- java.sql.Date date0 = new java.sql.Date(0L);
- assertEquals(aposToQuotes("{'date':'"+date0.toString()+"'}"),
- MAPPER.writeValueAsString(new SqlDateAsDefaultBean(0L)));
-
- // but may explicitly force timestamp too
- assertEquals(aposToQuotes("{'date':0}"), MAPPER.writeValueAsString(new SqlDateAsNumberBean(0L)));
- }
-
- public void testSqlTime() throws IOException
- {
- java.sql.Time time = new java.sql.Time(0L);
- // not 100% sure what we should expect wrt timezone, but what serializes
- // does use is quite simple:
- assertEquals(quote(time.toString()), MAPPER.writeValueAsString(time));
- }
-
- public void testSqlTimestamp() throws IOException
- {
- java.sql.Timestamp input = new java.sql.Timestamp(0L);
- // just should produce same output as standard `java.util.Date`:
- Date altTnput = new Date(0L);
- assertEquals(MAPPER.writeValueAsString(altTnput),
- MAPPER.writeValueAsString(input));
+ serialize( mapper, judate(1970, 1, 1, 00, 00, 00, 0, "UTC"), "1969-12-31X16:00:00");
}
public void testTimeZone() throws IOException
@@ -243,19 +277,18 @@
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd/HH:mm z"));
mapper.setTimeZone(TimeZone.getTimeZone("PST"));
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
- String json = mapper.writeValueAsString(new Date(0));
+
// pacific time is GMT-8; so midnight becomes 16:00 previous day:
- assertEquals(quote("1969-12-31/16:00 PST"), json);
+ serialize( mapper, judate(1969, 12, 31, 16, 00, 00, 00, "PST"), "1969-12-31/16:00 PST");
// Let's also verify that Locale won't matter too much...
mapper.setLocale(Locale.FRANCE);
- json = mapper.writeValueAsString(new Date(0));
- assertEquals(quote("1969-12-31/16:00 PST"), json);
+ serialize( mapper, judate(1969, 12, 31, 16, 00, 00, 00, "PST"), "1969-12-31/16:00 PST");
// Also: should be able to dynamically change timezone:
ObjectWriter w = mapper.writer();
w = w.with(TimeZone.getTimeZone("EST"));
- json = w.writeValueAsString(new Date(0));
+ String json = w.writeValueAsString(new Date(0));
assertEquals(quote("1969-12-31/19:00 EST"), json);
}
@@ -315,4 +348,28 @@
String json = mapper.writeValueAsString(new DateAsDefaultBeanWithTimezone(0L));
assertEquals(aposToQuotes("{'date':'1970-01-01X01:00:00'}"), json);
}
+
+ private static Date judate(int year, int month, int day, int hour, int minutes, int seconds, int millis, String tz) {
+ Calendar cal = Calendar.getInstance();
+ // 23-Nov-2018, tatu: Safer this way, even though negative appears to work too
+ if (year < 0) {
+ year = -year + 1;
+ cal.set(Calendar.ERA, GregorianCalendar.BC);
+ cal.set(year, month-1, day, hour, minutes, seconds);
+ } else {
+ cal.set(year, month-1, day, hour, minutes, seconds);
+ }
+ cal.set(Calendar.MILLISECOND, millis);
+ cal.setTimeZone(TimeZone.getTimeZone(tz));
+
+ return cal.getTime();
+ }
+
+ private void serialize(ObjectMapper mapper, Object date, String expected) throws IOException {
+ Assert.assertEquals(quote(expected), mapper.writeValueAsString(date));
+ }
+
+ private void serialize(ObjectWriter w, Object date, String expected) throws IOException {
+ Assert.assertEquals(quote(expected), w.writeValueAsString(date));
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestJdkTypes.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/JDKTypeSerializationTest.java
similarity index 75%
rename from src/test/java/com/fasterxml/jackson/databind/ser/TestJdkTypes.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/jdk/JDKTypeSerializationTest.java
index 9aea068..b93d9a9 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestJdkTypes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/JDKTypeSerializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.ser;
+package com.fasterxml.jackson.databind.ser.jdk;
import java.io.*;
import java.math.BigDecimal;
@@ -9,6 +9,7 @@
import java.util.*;
import java.util.regex.Pattern;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
@@ -16,11 +17,22 @@
* Unit tests for JDK types not covered by other tests (i.e. things
* that are not Enums, Collections, Maps, or standard Date/Time types)
*/
-public class TestJdkTypes
+public class JDKTypeSerializationTest
extends com.fasterxml.jackson.databind.BaseMapTest
{
private final ObjectMapper MAPPER = objectMapper();
+ static class InetAddressBean {
+ public InetAddress value;
+
+ public InetAddressBean(InetAddress i) { value = i; }
+ }
+
+ // [databind#2197]
+ static class VoidBean {
+ public Void value;
+ }
+
public void testBigDecimal() throws Exception
{
Map<String, Object> map = new HashMap<String, Object>();
@@ -83,19 +95,26 @@
public void testInetAddress() throws IOException
{
assertEquals(quote("127.0.0.1"), MAPPER.writeValueAsString(InetAddress.getByName("127.0.0.1")));
- assertEquals(quote("ning.com"), MAPPER.writeValueAsString(InetAddress.getByName("ning.com")));
+ InetAddress input = InetAddress.getByName("google.com");
+ assertEquals(quote("google.com"), MAPPER.writeValueAsString(input));
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(InetAddress.class)
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.NUMBER));
+ String json = mapper.writeValueAsString(input);
+ assertEquals(quote(input.getHostAddress()), json);
+
+ assertEquals(String.format("{\"value\":\"%s\"}", input.getHostAddress()),
+ mapper.writeValueAsString(new InetAddressBean(input)));
}
public void testInetSocketAddress() throws IOException
{
- assertEquals(
- quote("127.0.0.1:8080"),
+ assertEquals(quote("127.0.0.1:8080"),
MAPPER.writeValueAsString(new InetSocketAddress("127.0.0.1", 8080)));
- assertEquals(
- quote("ning.com:6667"),
- MAPPER.writeValueAsString(new InetSocketAddress("ning.com", 6667)));
- assertEquals(
- quote("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443"),
+ assertEquals(quote("google.com:6667"),
+ MAPPER.writeValueAsString(new InetSocketAddress("google.com", 6667)));
+ assertEquals(quote("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443"),
MAPPER.writeValueAsString(new InetSocketAddress("2001:db8:85a3:8d3:1319:8a2e:370:7348", 443)));
}
@@ -108,13 +127,12 @@
assertEquals(quote("void"), MAPPER.writeValueAsString(Void.TYPE));
}
- // [JACKSON-789]
public void testCharset() throws IOException
{
assertEquals(quote("UTF-8"), MAPPER.writeValueAsString(Charset.forName("UTF-8")));
}
- // [Issue#239]: Support serialization of ByteBuffer
+ // [databind#239]: Support serialization of ByteBuffer
public void testByteBuffer() throws IOException
{
final byte[] INPUT_BYTES = new byte[] { 1, 2, 3, 4, 5 };
@@ -128,6 +146,19 @@
assertEquals(exp, MAPPER.writeValueAsString(bbuf2));
}
+ // [databind#1662]: Sliced ByteBuffers
+ public void testSlicedByteBuffer() throws IOException
+ {
+ final byte[] INPUT_BYTES = new byte[] { 1, 2, 3, 4, 5 };
+ String exp = MAPPER.writeValueAsString(new byte[] { 3, 4, 5 });
+ ByteBuffer bbuf = ByteBuffer.wrap(INPUT_BYTES);
+
+ bbuf.position(2);
+ ByteBuffer slicedBuf = bbuf.slice();
+
+ assertEquals(exp, MAPPER.writeValueAsString(slicedBuf));
+ }
+
// Verify that efficient UUID codec won't mess things up:
public void testUUIDs() throws IOException
{
@@ -161,4 +192,11 @@
assertEquals(quote(uuid.toString()), json);
}
}
+
+ // [databind#2197]
+ public void testVoidSerialization() throws Exception
+ {
+ assertEquals(aposToQuotes("{'value':null}"),
+ MAPPER.writeValueAsString(new VoidBean()));
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/KeySerializers1679Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/KeySerializers1679Test.java
similarity index 92%
rename from src/test/java/com/fasterxml/jackson/databind/ser/KeySerializers1679Test.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/jdk/KeySerializers1679Test.java
index ea7e600..2578b84 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/KeySerializers1679Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/KeySerializers1679Test.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.ser;
+package com.fasterxml.jackson.databind.ser.jdk;
import java.util.*;
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/jdk/MapKeySerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/MapKeySerializationTest.java
new file mode 100644
index 0000000..8731deb
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/MapKeySerializationTest.java
@@ -0,0 +1,103 @@
+package com.fasterxml.jackson.databind.ser.jdk;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.core.Base64Variants;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.*;
+
+@SuppressWarnings("serial")
+public class MapKeySerializationTest extends BaseMapTest
+{
+ // for [databind#47]
+ public static class Wat
+ {
+ private final String wat;
+
+ @JsonCreator
+ Wat(String wat) {
+ this.wat = wat;
+ }
+
+ @JsonValue
+ public String getWat() {
+ return wat;
+ }
+
+ @Override
+ public String toString() {
+ return "(String)[Wat: " + wat + "]";
+ }
+ }
+
+ static class WatMap extends HashMap<Wat,Boolean> { }
+
+ static class DefaultKeySerializer extends JsonSerializer<Object>
+ {
+ @Override
+ public void serialize(Object value, JsonGenerator g, SerializerProvider provider) throws IOException
+ {
+ g.writeFieldName("DEFAULT:"+value);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = objectMapper();
+
+ // [databind#47]
+ public void testMapJsonValueKey47() throws Exception
+ {
+ WatMap input = new WatMap();
+ input.put(new Wat("3"), true);
+
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("{'3':true}"), json);
+ }
+
+ // [databind#682]
+ public void testClassKey() throws IOException
+ {
+ Map<Class<?>,Integer> map = new LinkedHashMap<Class<?>,Integer>();
+ map.put(String.class, 2);
+ String json = MAPPER.writeValueAsString(map);
+ assertEquals(aposToQuotes("{'java.lang.String':2}"), json);
+ }
+
+ public void testDefaultKeySerializer() throws IOException
+ {
+ ObjectMapper m = new ObjectMapper();
+ m.getSerializerProvider().setDefaultKeySerializer(new DefaultKeySerializer());
+ Map<String,String> map = new HashMap<String,String>();
+ map.put("a", "b");
+ assertEquals("{\"DEFAULT:a\":\"b\"}", m.writeValueAsString(map));
+ }
+
+ // [databind#1552]
+ public void testMapsWithBinaryKeys() throws Exception
+ {
+ byte[] binary = new byte[] { 1, 2, 3, 4, 5 };
+
+ // First, using wrapper
+ MapWrapper<byte[], String> input = new MapWrapper<>(binary, "stuff");
+ String expBase64 = Base64Variants.MIME.encode(binary);
+
+ assertEquals(aposToQuotes("{'map':{'"+expBase64+"':'stuff'}}"),
+ MAPPER.writeValueAsString(input));
+
+ // and then dynamically..
+ Map<byte[],String> map = new LinkedHashMap<>();
+ map.put(binary, "xyz");
+ assertEquals(aposToQuotes("{'"+expBase64+"':'xyz'}"),
+ MAPPER.writeValueAsString(map));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/jdk/NumberSerTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/NumberSerTest.java
new file mode 100644
index 0000000..d99eaf4
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/NumberSerTest.java
@@ -0,0 +1,142 @@
+package com.fasterxml.jackson.databind.ser.jdk;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+/**
+ * Unit tests for verifying serialization of simple basic non-structured
+ * types; primitives (and/or their wrappers), Strings.
+ */
+public class NumberSerTest extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = objectMapper();
+
+ static class IntWrapper {
+ public int i;
+ public IntWrapper(int value) { i = value; }
+ }
+
+ static class DoubleWrapper {
+ public double value;
+ public DoubleWrapper(double v) { value = v; }
+ }
+
+ static class BigDecimalWrapper {
+ public BigDecimal value;
+ public BigDecimalWrapper(BigDecimal v) { value = v; }
+ }
+
+ static class IntAsString {
+ @JsonFormat(shape=JsonFormat.Shape.STRING)
+ @JsonProperty("value")
+ public int foo = 3;
+ }
+
+ static class LongAsString {
+ @JsonFormat(shape=JsonFormat.Shape.STRING)
+ public long value = 4;
+ }
+
+ static class DoubleAsString {
+ @JsonFormat(shape=JsonFormat.Shape.STRING)
+ public double value = -0.5;
+ }
+
+ static class BigIntegerAsString {
+ @JsonFormat(shape=JsonFormat.Shape.STRING)
+ public BigInteger value = BigInteger.valueOf(123456L);
+ }
+
+ static class BigDecimalAsString {
+ @JsonFormat(shape=JsonFormat.Shape.STRING)
+ public BigDecimal value = BigDecimal.valueOf(0.25);
+ }
+
+ static class NumberWrapper {
+ // ensure it will use `Number` as statically force type, when looking for serializer
+ @JsonSerialize(as=Number.class)
+ public Number value;
+
+ public NumberWrapper(Number v) { value = v; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testDouble() throws Exception
+ {
+ double[] values = new double[] {
+ 0.0, 1.0, 0.1, -37.01, 999.99, 0.3, 33.3, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
+ };
+ for (double d : values) {
+ String expected = String.valueOf(d);
+ if (Double.isNaN(d) || Double.isInfinite(d)) {
+ expected = "\""+d+"\"";
+ }
+ assertEquals(expected, MAPPER.writeValueAsString(Double.valueOf(d)));
+ }
+ }
+
+ public void testBigInteger() throws Exception
+ {
+ BigInteger[] values = new BigInteger[] {
+ BigInteger.ONE, BigInteger.TEN, BigInteger.ZERO,
+ BigInteger.valueOf(1234567890L),
+ new BigInteger("123456789012345678901234568"),
+ new BigInteger("-1250000124326904597090347547457")
+ };
+
+ for (BigInteger value : values) {
+ String expected = value.toString();
+ assertEquals(expected, MAPPER.writeValueAsString(value));
+ }
+ }
+
+ public void testNumbersAsString() throws Exception
+ {
+ assertEquals(aposToQuotes("{'value':'3'}"), MAPPER.writeValueAsString(new IntAsString()));
+ assertEquals(aposToQuotes("{'value':'4'}"), MAPPER.writeValueAsString(new LongAsString()));
+ assertEquals(aposToQuotes("{'value':'-0.5'}"), MAPPER.writeValueAsString(new DoubleAsString()));
+ assertEquals(aposToQuotes("{'value':'0.25'}"), MAPPER.writeValueAsString(new BigDecimalAsString()));
+ assertEquals(aposToQuotes("{'value':'123456'}"), MAPPER.writeValueAsString(new BigIntegerAsString()));
+ }
+
+ public void testConfigOverridesForNumbers() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(Integer.TYPE) // for `int`
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
+ mapper.configOverride(Double.TYPE) // for `double`
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
+ mapper.configOverride(BigDecimal.class)
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
+
+ assertEquals(aposToQuotes("{'i':'3'}"),
+ mapper.writeValueAsString(new IntWrapper(3)));
+ assertEquals(aposToQuotes("{'value':'0.75'}"),
+ mapper.writeValueAsString(new DoubleWrapper(0.75)));
+ assertEquals(aposToQuotes("{'value':'-0.5'}"),
+ mapper.writeValueAsString(new BigDecimalWrapper(BigDecimal.valueOf(-0.5))));
+ }
+
+ public void testNumberType() throws Exception
+ {
+ assertEquals(aposToQuotes("{'value':1}"), MAPPER.writeValueAsString(new NumberWrapper(Byte.valueOf((byte) 1))));
+ assertEquals(aposToQuotes("{'value':2}"), MAPPER.writeValueAsString(new NumberWrapper(Short.valueOf((short) 2))));
+ assertEquals(aposToQuotes("{'value':3}"), MAPPER.writeValueAsString(new NumberWrapper(Integer.valueOf(3))));
+ assertEquals(aposToQuotes("{'value':4}"), MAPPER.writeValueAsString(new NumberWrapper(Long.valueOf(4L))));
+ assertEquals(aposToQuotes("{'value':0.5}"), MAPPER.writeValueAsString(new NumberWrapper(Float.valueOf(0.5f))));
+ assertEquals(aposToQuotes("{'value':0.05}"), MAPPER.writeValueAsString(new NumberWrapper(Double.valueOf(0.05))));
+ assertEquals(aposToQuotes("{'value':123}"), MAPPER.writeValueAsString(new NumberWrapper(BigInteger.valueOf(123))));
+ assertEquals(aposToQuotes("{'value':0.025}"), MAPPER.writeValueAsString(new NumberWrapper(BigDecimal.valueOf(0.025))));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/jdk/SqlDateSerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/SqlDateSerializationTest.java
new file mode 100644
index 0000000..3f8dbba
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/SqlDateSerializationTest.java
@@ -0,0 +1,107 @@
+package com.fasterxml.jackson.databind.ser.jdk;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+
+// Tests for `java.sql.Date`, `java.sql.Time` and `java.sql.Timestamp`
+public class SqlDateSerializationTest extends BaseMapTest
+{
+ static class SqlDateAsDefaultBean {
+ public java.sql.Date date;
+ public SqlDateAsDefaultBean(long l) { date = new java.sql.Date(l); }
+ }
+
+ static class SqlDateAsNumberBean {
+ @JsonFormat(shape=JsonFormat.Shape.NUMBER)
+ public java.sql.Date date;
+ public SqlDateAsNumberBean(long l) { date = new java.sql.Date(l); }
+ }
+
+ // for [databind#1407]
+ static class Person {
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd")
+ public java.sql.Date dateOfBirth;
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ @SuppressWarnings("deprecation")
+ public void testSqlDate() throws IOException
+ {
+ // use date 1999-04-01 (note: months are 0-based, use constant)
+ final java.sql.Date date99 = new java.sql.Date(99, Calendar.APRIL, 1);
+ final java.sql.Date date0 = new java.sql.Date(0);
+
+ // 11-Oct-2016, tatu: As per [databind#219] we really should use global
+ // defaults in 2.9, even if this changes behavior.
+
+ assertEquals(String.valueOf(date99.getTime()),
+ MAPPER.writeValueAsString(date99));
+
+ assertEquals(aposToQuotes("{'date':0}"),
+ MAPPER.writeValueAsString(new SqlDateAsDefaultBean(0L)));
+
+ // but may explicitly force timestamp too
+ assertEquals(aposToQuotes("{'date':0}"),
+ MAPPER.writeValueAsString(new SqlDateAsNumberBean(0L)));
+
+ // And also should be able to use String output as need be:
+ ObjectWriter w = MAPPER.writer().without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+ assertEquals(quote("1999-04-01"), w.writeValueAsString(date99));
+ assertEquals(quote(date0.toString()), w.writeValueAsString(date0));
+ assertEquals(aposToQuotes("{'date':'"+date0.toString()+"'}"),
+ w.writeValueAsString(new SqlDateAsDefaultBean(0L)));
+ }
+
+ public void testSqlTime() throws IOException
+ {
+ java.sql.Time time = new java.sql.Time(0L);
+ // not 100% sure what we should expect wrt timezone, but what serializes
+ // does use is quite simple:
+ assertEquals(quote(time.toString()), MAPPER.writeValueAsString(time));
+ }
+
+ public void testSqlTimestamp() throws IOException
+ {
+ java.sql.Timestamp input = new java.sql.Timestamp(0L);
+ // just should produce same output as standard `java.util.Date`:
+ Date altTnput = new Date(0L);
+ assertEquals(MAPPER.writeValueAsString(altTnput),
+ MAPPER.writeValueAsString(input));
+ }
+
+ public void testPatternWithSqlDate() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ // `java.sql.Date` applies system default zone (and not UTC)
+ mapper.setTimeZone(TimeZone.getDefault());
+
+ Person i = new Person();
+ i.dateOfBirth = java.sql.Date.valueOf("1980-04-14");
+ assertEquals(aposToQuotes("{'dateOfBirth':'1980.04.14'}"),
+ mapper.writeValueAsString(i));
+ }
+
+ // [databind#2064]
+ public void testSqlDateConfigOverride() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configOverride(java.sql.Date.class)
+ .setFormat(JsonFormat.Value.forPattern("yyyy+MM+dd"));
+ assertEquals("\"1980+04+14\"",
+ mapper.writeValueAsString(java.sql.Date.valueOf("1980-04-14")));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestUntypedSerialization.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/UntypedSerializationTest.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/ser/TestUntypedSerialization.java
rename to src/test/java/com/fasterxml/jackson/databind/ser/jdk/UntypedSerializationTest.java
index e62ed96..9117e79 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestUntypedSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/UntypedSerializationTest.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.ser;
+package com.fasterxml.jackson.databind.ser.jdk;
import java.util.*;
@@ -11,7 +11,7 @@
* "Native" java type mapper; basically that is can properly serialize
* core JDK objects to JSON.
*/
-public class TestUntypedSerialization
+public class UntypedSerializationTest
extends BaseMapTest
{
public void testFromArray()
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/BackReference1878Test.java b/src/test/java/com/fasterxml/jackson/databind/struct/BackReference1878Test.java
new file mode 100644
index 0000000..6a86565
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/BackReference1878Test.java
@@ -0,0 +1,30 @@
+package com.fasterxml.jackson.databind.struct;
+
+import com.fasterxml.jackson.annotation.JsonBackReference;
+import com.fasterxml.jackson.annotation.JsonManagedReference;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author Reda.Housni-Alaoui
+ */
+public class BackReference1878Test extends BaseMapTest
+{
+ static class Child {
+ @JsonBackReference
+ public Parent b;
+ }
+
+ static class Parent {
+ @JsonManagedReference
+ public Child a;
+ }
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testChildDeserialization() throws Exception {
+ Child child = MAPPER.readValue("{\"b\": {}}", Child.class);
+ assertNotNull(child.b);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java
index 5199545..71aba32 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java
@@ -2,11 +2,8 @@
import java.math.BigDecimal;
import java.math.BigInteger;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.EnumMap;
-import java.util.Map;
-import java.util.UUID;
+
+import java.util.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
@@ -105,14 +102,14 @@
public void testWrapperFromEmptyArray() throws Exception
{
-// _testNullWrapper(Boolean.class);
-// _testNullWrapper(Byte.class);
+ _testNullWrapper(Boolean.class);
+ _testNullWrapper(Byte.class);
_testNullWrapper(Character.class);
-// _testNullWrapper(Short.class);
-// _testNullWrapper(Integer.class);
-// _testNullWrapper(Long.class);
-// _testNullWrapper(Float.class);
-// _testNullWrapper(Double.class);
+ _testNullWrapper(Short.class);
+ _testNullWrapper(Integer.class);
+ _testNullWrapper(Long.class);
+ _testNullWrapper(Float.class);
+ _testNullWrapper(Double.class);
}
/*
@@ -136,10 +133,8 @@
_testNullWrapper(UUID.class);
- /*
_testNullWrapper(Date.class);
_testNullWrapper(Calendar.class);
- */
}
/*
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureAcceptSingleTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureAcceptSingleTest.java
new file mode 100644
index 0000000..3aca91e
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureAcceptSingleTest.java
@@ -0,0 +1,179 @@
+package com.fasterxml.jackson.databind.struct;
+
+import java.util.EnumSet;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class FormatFeatureAcceptSingleTest extends BaseMapTest
+{
+ static class StringArrayNotAnnoted {
+ public String[] values;
+
+ protected StringArrayNotAnnoted() { }
+ public StringArrayNotAnnoted(String ... v) { values = v; }
+ }
+
+ static class StringArrayWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public String[] values;
+ }
+
+ static class BooleanArrayWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public boolean[] values;
+ }
+
+ static class IntArrayWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public int[] values;
+ }
+
+ static class LongArrayWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public long[] values;
+ }
+
+ static class FloatArrayWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public float[] values;
+ }
+
+ static class DoubleArrayWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public double[] values;
+ }
+
+ static class StringListWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public List<String> values;
+ }
+
+ static class EnumSetWrapper {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public EnumSet<ABC> values;
+ }
+
+ static class RolesInArray {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public Role[] roles;
+ }
+
+ static class RolesInList {
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public List<Role> roles;
+ }
+
+ static class Role {
+ public String ID;
+ public String Name;
+ }
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ /*
+ /**********************************************************
+ /* Test methods, reading with single-element unwrapping
+ /**********************************************************
+ */
+
+ public void testSingleStringArrayRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'values': 'first' }");
+ StringArrayWrapper result = MAPPER.readValue(json, StringArrayWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.length);
+ assertEquals("first", result.values[0]);
+
+ // and then without annotation, but with global override
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(String[].class).setFormat(JsonFormat.Value.empty()
+ .withFeature(JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
+ StringArrayNotAnnoted result2 = mapper.readValue(json, StringArrayNotAnnoted.class);
+ assertNotNull(result2.values);
+ assertEquals(1, result2.values.length);
+ assertEquals("first", result2.values[0]);
+ }
+
+ public void testSingleIntArrayRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'values': 123 }");
+ IntArrayWrapper result = MAPPER.readValue(json, IntArrayWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.length);
+ assertEquals(123, result.values[0]);
+ }
+
+ public void testSingleLongArrayRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'values': -205 }");
+ LongArrayWrapper result = MAPPER.readValue(json, LongArrayWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.length);
+ assertEquals(-205L, result.values[0]);
+ }
+
+ public void testSingleBooleanArrayRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'values': true }");
+ BooleanArrayWrapper result = MAPPER.readValue(json, BooleanArrayWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.length);
+ assertEquals(true, result.values[0]);
+ }
+
+ public void testSingleDoubleArrayRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'values': -0.5 }");
+ DoubleArrayWrapper result = MAPPER.readValue(json, DoubleArrayWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.length);
+ assertEquals(-0.5, result.values[0]);
+ }
+
+ public void testSingleFloatArrayRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'values': 0.25 }");
+ FloatArrayWrapper result = MAPPER.readValue(json, FloatArrayWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.length);
+ assertEquals(0.25f, result.values[0]);
+ }
+
+ public void testSingleElementArrayRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'roles': { 'Name': 'User', 'ID': '333' } }");
+ RolesInArray response = MAPPER.readValue(json, RolesInArray.class);
+ assertNotNull(response.roles);
+ assertEquals(1, response.roles.length);
+ assertEquals("333", response.roles[0].ID);
+ }
+
+ public void testSingleStringListRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'values': 'first' }");
+ StringListWrapper result = MAPPER.readValue(json, StringListWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.size());
+ assertEquals("first", result.values.get(0));
+ }
+
+ public void testSingleElementListRead() throws Exception {
+ String json = aposToQuotes(
+ "{ 'roles': { 'Name': 'User', 'ID': '333' } }");
+ RolesInList response = MAPPER.readValue(json, RolesInList.class);
+ assertNotNull(response.roles);
+ assertEquals(1, response.roles.size());
+ assertEquals("333", response.roles.get(0).ID);
+ }
+
+ public void testSingleEnumSetRead() throws Exception {
+ EnumSetWrapper result = MAPPER.readValue(aposToQuotes("{ 'values': 'B' }"),
+ EnumSetWrapper.class);
+ assertNotNull(result.values);
+ assertEquals(1, result.values.size());
+ assertEquals(ABC.B, result.values.iterator().next());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureOrderedMapTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureOrderedMapTest.java
new file mode 100644
index 0000000..3391ff8
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureOrderedMapTest.java
@@ -0,0 +1,35 @@
+package com.fasterxml.jackson.databind.struct;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * Tests for {@link JsonFormat} and specifically <code>JsonFormat.Feature</code>s.
+ */
+public class FormatFeatureOrderedMapTest extends BaseMapTest
+{
+ static class SortedKeysMap {
+ @JsonFormat(with = JsonFormat.Feature.WRITE_SORTED_MAP_ENTRIES)
+ public Map<String,Integer> values = new LinkedHashMap<>();
+
+ protected SortedKeysMap() { }
+
+ public SortedKeysMap put(String key, int value) {
+ values.put(key, value);
+ return this;
+ }
+ }
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ // [databind#1232]: allow forcing sorting on Map keys
+ public void testOrderedMaps() throws Exception {
+ SortedKeysMap map = new SortedKeysMap()
+ .put("b", 2)
+ .put("a", 1);
+ assertEquals(aposToQuotes("{'values':{'a':1,'b':2}}"),
+ MAPPER.writeValueAsString(map));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureUnwrapSingleTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureUnwrapSingleTest.java
new file mode 100644
index 0000000..ab8b38c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeatureUnwrapSingleTest.java
@@ -0,0 +1,192 @@
+package com.fasterxml.jackson.databind.struct;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+public class FormatFeatureUnwrapSingleTest extends BaseMapTest
+{
+ static class StringArrayNotAnnoted {
+ public String[] values;
+
+ protected StringArrayNotAnnoted() { }
+ public StringArrayNotAnnoted(String ... v) { values = v; }
+ }
+
+ @JsonPropertyOrder( { "strings", "ints", "bools" })
+ static class WrapWriteWithArrays
+ {
+ @JsonProperty("strings")
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public String[] _strings = new String[] {
+ "a"
+ };
+
+ @JsonFormat(without={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public int[] ints = new int[] { 1 };
+
+ public boolean[] bools = new boolean[] { true };
+ }
+
+ static class UnwrapShortArray {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public short[] v = { (short) 7 };
+ }
+
+ static class UnwrapIntArray {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public int[] v = { 3 };
+ }
+
+ static class UnwrapLongArray {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public long[] v = { 1L };
+ }
+
+ static class UnwrapBooleanArray {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public boolean[] v = { true };
+ }
+
+ static class UnwrapFloatArray {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public float[] v = { 0.5f };
+ }
+
+ static class UnwrapDoubleArray {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public double[] v = { 0.25 };
+ }
+
+ static class UnwrapIterable {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ @JsonSerialize(as=Iterable.class)
+ public Iterable<String> v;
+
+ public UnwrapIterable() {
+ v = Collections.singletonList("foo");
+ }
+
+ public UnwrapIterable(String... values) {
+ v = Arrays.asList(values);
+ }
+ }
+
+ static class UnwrapCollection {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ @JsonSerialize(as=Collection.class)
+ public Collection<String> v;
+
+ public UnwrapCollection() {
+ v = Collections.singletonList("foo");
+ }
+
+ public UnwrapCollection(String... values) {
+ v = new LinkedHashSet<String>(Arrays.asList(values));
+ }
+ }
+
+ static class UnwrapStringLike {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public URI[] v = { URI.create("http://foo") };
+ }
+
+ @JsonPropertyOrder( { "strings", "ints", "bools", "enums" })
+ static class WrapWriteWithCollections
+ {
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public List<String> strings = Arrays.asList("a");
+
+ @JsonFormat(without={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public Collection<Integer> ints = Arrays.asList(Integer.valueOf(1));
+
+ public Set<Boolean> bools = Collections.singleton(true);
+
+ @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
+ public EnumSet<ABC> enums = EnumSet.of(ABC.B);
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods, writing with single-element unwrapping
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testWithArrayTypes() throws Exception
+ {
+ // default: strings unwrapped, ints wrapped
+ assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true]}"),
+ MAPPER.writeValueAsString(new WrapWriteWithArrays()));
+
+ // change global default to "yes, unwrap"; changes 'bools' only
+ assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':true}"),
+ MAPPER.writer().with(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
+ .writeValueAsString(new WrapWriteWithArrays()));
+
+ // change global default to "no, don't, unwrap", same as first case
+ assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true]}"),
+ MAPPER.writer().without(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
+ .writeValueAsString(new WrapWriteWithArrays()));
+
+ // And then without SerializationFeature but with config override:
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configOverride(String[].class).setFormat(JsonFormat.Value.empty()
+ .withFeature(JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED));
+ assertEquals(aposToQuotes("{'values':'a'}"),
+ mapper.writeValueAsString(new StringArrayNotAnnoted("a")));
+ }
+
+ public void testWithCollectionTypes() throws Exception
+ {
+ // default: strings unwrapped, ints wrapped
+ assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true],'enums':'B'}"),
+ MAPPER.writeValueAsString(new WrapWriteWithCollections()));
+
+ // change global default to "yes, unwrap"; changes 'bools' only
+ assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':true,'enums':'B'}"),
+ MAPPER.writer().with(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
+ .writeValueAsString(new WrapWriteWithCollections()));
+
+ // change global default to "no, don't, unwrap", same as first case
+ assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true],'enums':'B'}"),
+ MAPPER.writer().without(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
+ .writeValueAsString(new WrapWriteWithCollections()));
+ }
+
+ public void testUnwrapWithPrimitiveArraysEtc() throws Exception {
+ assertEquals("{\"v\":7}", MAPPER.writeValueAsString(new UnwrapShortArray()));
+ assertEquals("{\"v\":3}", MAPPER.writeValueAsString(new UnwrapIntArray()));
+ assertEquals("{\"v\":1}", MAPPER.writeValueAsString(new UnwrapLongArray()));
+ assertEquals("{\"v\":true}", MAPPER.writeValueAsString(new UnwrapBooleanArray()));
+
+ assertEquals("{\"v\":0.5}", MAPPER.writeValueAsString(new UnwrapFloatArray()));
+ assertEquals("{\"v\":0.25}", MAPPER.writeValueAsString(new UnwrapDoubleArray()));
+ assertEquals("0.5",
+ MAPPER.writer().with(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
+ .writeValueAsString(new double[] { 0.5 }));
+
+ assertEquals("{\"v\":\"foo\"}", MAPPER.writeValueAsString(new UnwrapIterable()));
+ assertEquals("{\"v\":\"x\"}", MAPPER.writeValueAsString(new UnwrapIterable("x")));
+ assertEquals("{\"v\":[\"x\",null]}", MAPPER.writeValueAsString(new UnwrapIterable("x", null)));
+
+ assertEquals("{\"v\":\"foo\"}", MAPPER.writeValueAsString(new UnwrapCollection()));
+ assertEquals("{\"v\":\"x\"}", MAPPER.writeValueAsString(new UnwrapCollection("x")));
+ assertEquals("{\"v\":[\"x\",null]}", MAPPER.writeValueAsString(new UnwrapCollection("x", null)));
+
+ assertEquals("{\"v\":\"http://foo\"}", MAPPER.writeValueAsString(new UnwrapStringLike()));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeaturesTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeaturesTest.java
deleted file mode 100644
index 87d5922..0000000
--- a/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeaturesTest.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package com.fasterxml.jackson.databind.struct;
-
-import java.util.*;
-
-import com.fasterxml.jackson.annotation.*;
-import com.fasterxml.jackson.databind.*;
-
-/**
- * Tests for {@link JsonFormat} and specifically <code>JsonFormat.Feature</code>s.
- */
-public class FormatFeaturesTest extends BaseMapTest
-{
- @JsonPropertyOrder( { "strings", "ints", "bools" })
- static class WrapWriteWithArrays
- {
- @JsonProperty("strings")
- @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
- public String[] _strings = new String[] {
- "a"
- };
-
- @JsonFormat(without={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
- public int[] ints = new int[] {
- 1
- };
-
- public boolean[] bools = new boolean[] { true };
- }
-
- @JsonPropertyOrder( { "strings", "ints", "bools", "enums" })
- static class WrapWriteWithCollections
- {
- @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
- public List<String> strings = Arrays.asList("a");
-
- @JsonFormat(without={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
- public Collection<Integer> ints = Arrays.asList(Integer.valueOf(1));
-
- public Set<Boolean> bools = Collections.singleton(true);
-
- @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED })
- public EnumSet<ABC> enums = EnumSet.of(ABC.B);
- }
-
- static class StringArrayWrapper {
- @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
- public String[] values;
- }
-
- static class StringArrayNotAnnoted {
- public String[] values;
-
- protected StringArrayNotAnnoted() { }
- public StringArrayNotAnnoted(String ... v) { values = v; }
- }
-
- static class IntArrayWrapper {
- @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
- public int[] values;
- }
-
- static class LongArrayWrapper {
- @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
- public long[] values;
- }
-
- static class StringListWrapper {
- @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
- public List<String> values;
- }
-
- static class EnumSetWrapper {
- @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
- public EnumSet<ABC> values;
- }
-
- static class RolesInArray {
- @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
- public Role[] roles;
- }
-
- static class RolesInList {
- @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
- public List<Role> roles;
- }
-
- static class Role {
- public String ID;
- public String Name;
- }
-
- static class CaseInsensitiveRoleWrapper
- {
- @JsonFormat(with={ JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES })
- public Role role;
- }
-
- static class SortedKeysMap {
- @JsonFormat(with = JsonFormat.Feature.WRITE_SORTED_MAP_ENTRIES)
- public Map<String,Integer> values = new LinkedHashMap<>();
-
- protected SortedKeysMap() { }
-
- public SortedKeysMap put(String key, int value) {
- values.put(key, value);
- return this;
- }
- }
-
- /*
- /**********************************************************
- /* Test methods, writing with single-element unwrapping
- /**********************************************************
- */
-
- private final ObjectMapper MAPPER = new ObjectMapper();
-
- public void testWithArrayTypes() throws Exception
- {
- // default: strings unwrapped, ints wrapped
- assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true]}"),
- MAPPER.writeValueAsString(new WrapWriteWithArrays()));
-
- // change global default to "yes, unwrap"; changes 'bools' only
- assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':true}"),
- MAPPER.writer().with(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
- .writeValueAsString(new WrapWriteWithArrays()));
-
- // change global default to "no, don't, unwrap", same as first case
- assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true]}"),
- MAPPER.writer().without(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
- .writeValueAsString(new WrapWriteWithArrays()));
-
- // And then without SerializationFeature but with config override:
- ObjectMapper mapper = new ObjectMapper();
- mapper.configOverride(String[].class).setFormat(JsonFormat.Value.empty()
- .withFeature(JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED));
- assertEquals(aposToQuotes("{'values':'a'}"),
- mapper.writeValueAsString(new StringArrayNotAnnoted("a")));
- }
-
- public void testWithCollectionTypes() throws Exception
- {
- // default: strings unwrapped, ints wrapped
- assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true],'enums':'B'}"),
- MAPPER.writeValueAsString(new WrapWriteWithCollections()));
-
- // change global default to "yes, unwrap"; changes 'bools' only
- assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':true,'enums':'B'}"),
- MAPPER.writer().with(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
- .writeValueAsString(new WrapWriteWithCollections()));
-
- // change global default to "no, don't, unwrap", same as first case
- assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true],'enums':'B'}"),
- MAPPER.writer().without(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
- .writeValueAsString(new WrapWriteWithCollections()));
- }
-
- /*
- /**********************************************************
- /* Test methods, reading with single-element unwrapping
- /**********************************************************
- */
-
- public void testSingleStringArrayRead() throws Exception {
- String json = aposToQuotes(
- "{ 'values': 'first' }");
- StringArrayWrapper result = MAPPER.readValue(json, StringArrayWrapper.class);
- assertNotNull(result.values);
- assertEquals(1, result.values.length);
- assertEquals("first", result.values[0]);
-
- // and then without annotation, but with global override
- ObjectMapper mapper = new ObjectMapper();
- mapper.configOverride(String[].class).setFormat(JsonFormat.Value.empty()
- .withFeature(JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
- StringArrayNotAnnoted result2 = mapper.readValue(json, StringArrayNotAnnoted.class);
- assertNotNull(result2.values);
- assertEquals(1, result2.values.length);
- assertEquals("first", result2.values[0]);
- }
-
- public void testSingleIntArrayRead() throws Exception {
- String json = aposToQuotes(
- "{ 'values': 123 }");
- IntArrayWrapper result = MAPPER.readValue(json, IntArrayWrapper.class);
- assertNotNull(result.values);
- assertEquals(1, result.values.length);
- assertEquals(123, result.values[0]);
- }
-
- public void testSingleLongArrayRead() throws Exception {
- String json = aposToQuotes(
- "{ 'values': -205 }");
- LongArrayWrapper result = MAPPER.readValue(json, LongArrayWrapper.class);
- assertNotNull(result.values);
- assertEquals(1, result.values.length);
- assertEquals(-205L, result.values[0]);
- }
-
- public void testSingleElementArrayRead() throws Exception {
- String json = aposToQuotes(
- "{ 'roles': { 'Name': 'User', 'ID': '333' } }");
- RolesInArray response = MAPPER.readValue(json, RolesInArray.class);
- assertNotNull(response.roles);
- assertEquals(1, response.roles.length);
- assertEquals("333", response.roles[0].ID);
- }
-
- public void testSingleStringListRead() throws Exception {
- String json = aposToQuotes(
- "{ 'values': 'first' }");
- StringListWrapper result = MAPPER.readValue(json, StringListWrapper.class);
- assertNotNull(result.values);
- assertEquals(1, result.values.size());
- assertEquals("first", result.values.get(0));
- }
-
- public void testSingleElementListRead() throws Exception {
- String json = aposToQuotes(
- "{ 'roles': { 'Name': 'User', 'ID': '333' } }");
- RolesInList response = MAPPER.readValue(json, RolesInList.class);
- assertNotNull(response.roles);
- assertEquals(1, response.roles.size());
- assertEquals("333", response.roles.get(0).ID);
- }
-
- public void testSingleEnumSetRead() throws Exception {
- EnumSetWrapper result = MAPPER.readValue(aposToQuotes("{ 'values': 'B' }"),
- EnumSetWrapper.class);
- assertNotNull(result.values);
- assertEquals(1, result.values.size());
- assertEquals(ABC.B, result.values.iterator().next());
- }
-
- // [databind#1232]: allow per-property case-insensitivity
- public void testCaseInsensitive() throws Exception {
- CaseInsensitiveRoleWrapper w = MAPPER.readValue
- (aposToQuotes("{'role':{'id':'12','name':'Foo'}}"),
- CaseInsensitiveRoleWrapper.class);
- assertNotNull(w);
- assertEquals("12", w.role.ID);
- assertEquals("Foo", w.role.Name);
- }
-
- // [databind#1232]: allow forcing sorting on Map keys
- public void testOrderedMaps() throws Exception {
- SortedKeysMap map = new SortedKeysMap()
- .put("b", 2)
- .put("a", 1);
- assertEquals(aposToQuotes("{'values':{'a':1,'b':2}}"),
- MAPPER.writeValueAsString(map));
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/ScalarCoercionTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/ScalarCoercionTest.java
new file mode 100644
index 0000000..e524d72
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/ScalarCoercionTest.java
@@ -0,0 +1,195 @@
+package com.fasterxml.jackson.databind.struct;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+// for [databind#1106]
+public class ScalarCoercionTest extends BaseMapTest
+{
+ private final ObjectMapper COERCING_MAPPER = new ObjectMapper(); {
+ COERCING_MAPPER.enable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
+ }
+
+ private final ObjectMapper NOT_COERCING_MAPPER = new ObjectMapper(); {
+ NOT_COERCING_MAPPER.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests: coercion from empty String
+ /**********************************************************
+ */
+
+ public void testNullValueFromEmpty() throws Exception
+ {
+ // wrappers accept `null` fine
+ _verifyNullOkFromEmpty(Boolean.class, null);
+ // but primitives require non-null
+ _verifyNullOkFromEmpty(Boolean.TYPE, Boolean.FALSE);
+
+ _verifyNullOkFromEmpty(Byte.class, null);
+ _verifyNullOkFromEmpty(Byte.TYPE, Byte.valueOf((byte) 0));
+ _verifyNullOkFromEmpty(Short.class, null);
+ _verifyNullOkFromEmpty(Short.TYPE, Short.valueOf((short) 0));
+ _verifyNullOkFromEmpty(Character.class, null);
+ _verifyNullOkFromEmpty(Character.TYPE, Character.valueOf((char) 0));
+ _verifyNullOkFromEmpty(Integer.class, null);
+ _verifyNullOkFromEmpty(Integer.TYPE, Integer.valueOf(0));
+ _verifyNullOkFromEmpty(Long.class, null);
+ _verifyNullOkFromEmpty(Long.TYPE, Long.valueOf(0L));
+ _verifyNullOkFromEmpty(Float.class, null);
+ _verifyNullOkFromEmpty(Float.TYPE, Float.valueOf(0.0f));
+ _verifyNullOkFromEmpty(Double.class, null);
+ _verifyNullOkFromEmpty(Double.TYPE, Double.valueOf(0.0));
+
+ _verifyNullOkFromEmpty(BigInteger.class, null);
+ _verifyNullOkFromEmpty(BigDecimal.class, null);
+ }
+
+ private void _verifyNullOkFromEmpty(Class<?> type, Object exp) throws IOException
+ {
+ Object result = COERCING_MAPPER.readerFor(type)
+ .with(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
+ .readValue("\"\"");
+ if (exp == null) {
+ assertNull(result);
+ } else {
+ assertEquals(exp, result);
+ }
+ }
+
+ public void testNullFailFromEmpty() throws Exception
+ {
+ _verifyNullFail(Boolean.class);
+ _verifyNullFail(Boolean.TYPE);
+
+ _verifyNullFail(Byte.class);
+ _verifyNullFail(Byte.TYPE);
+ _verifyNullFail(Short.class);
+ _verifyNullFail(Short.TYPE);
+ _verifyNullFail(Character.class);
+ _verifyNullFail(Character.TYPE);
+ _verifyNullFail(Integer.class);
+ _verifyNullFail(Integer.TYPE);
+ _verifyNullFail(Long.class);
+ _verifyNullFail(Long.TYPE);
+ _verifyNullFail(Float.class);
+ _verifyNullFail(Float.TYPE);
+ _verifyNullFail(Double.class);
+ _verifyNullFail(Double.TYPE);
+
+ _verifyNullFail(BigInteger.class);
+ _verifyNullFail(BigDecimal.class);
+ }
+
+ private void _verifyNullFail(Class<?> type) throws IOException
+ {
+ try {
+ NOT_COERCING_MAPPER.readerFor(type).readValue("\"\"");
+ fail("Should have failed for "+type);
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Cannot coerce empty String");
+ verifyException(e, "Null value for");
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests: coercion from secondary representations
+ /**********************************************************
+ */
+
+ public void testStringCoercionOk() throws Exception
+ {
+ // first successful coercions. Boolean has a ton...
+ _verifyCoerceSuccess("1", Boolean.TYPE, Boolean.TRUE);
+ _verifyCoerceSuccess("1", 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("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));
+ _verifyCoerceSuccess(quote("123"), Short.class, Short.valueOf((short) 123));
+ _verifyCoerceSuccess(quote("123"), Integer.TYPE, Integer.valueOf(123));
+ _verifyCoerceSuccess(quote("123"), Integer.class, Integer.valueOf(123));
+ _verifyCoerceSuccess(quote("123"), Long.TYPE, Long.valueOf(123));
+ _verifyCoerceSuccess(quote("123"), Long.class, Long.valueOf(123));
+ _verifyCoerceSuccess(quote("123.5"), Float.TYPE, Float.valueOf(123.5f));
+ _verifyCoerceSuccess(quote("123.5"), Float.class, Float.valueOf(123.5f));
+ _verifyCoerceSuccess(quote("123.5"), Double.TYPE, Double.valueOf(123.5));
+ _verifyCoerceSuccess(quote("123.5"), Double.class, Double.valueOf(123.5));
+
+ _verifyCoerceSuccess(quote("123"), BigInteger.class, BigInteger.valueOf(123));
+ _verifyCoerceSuccess(quote("123.0"), BigDecimal.class, new BigDecimal("123.0"));
+ }
+
+ public void testStringCoercionFail() throws Exception
+ {
+ _verifyCoerceFail(quote("true"), Boolean.TYPE);
+ _verifyCoerceFail(quote("true"), Boolean.class);
+ _verifyCoerceFail(quote("123"), Byte.TYPE);
+ _verifyCoerceFail(quote("123"), Byte.class);
+ _verifyCoerceFail(quote("123"), Short.TYPE);
+ _verifyCoerceFail(quote("123"), Short.class);
+ _verifyCoerceFail(quote("123"), Integer.TYPE);
+ _verifyCoerceFail(quote("123"), Integer.class);
+ _verifyCoerceFail(quote("123"), Long.TYPE);
+ _verifyCoerceFail(quote("123"), Long.class);
+ _verifyCoerceFail(quote("123.5"), Float.TYPE);
+ _verifyCoerceFail(quote("123.5"), Float.class);
+ _verifyCoerceFail(quote("123.5"), Double.TYPE);
+ _verifyCoerceFail(quote("123.5"), Double.class);
+
+ _verifyCoerceFail(quote("123"), BigInteger.class);
+ _verifyCoerceFail(quote("123.0"), BigDecimal.class);
+ }
+
+ public void testMiscCoercionFail() throws Exception
+ {
+ // And then we have coercions from more esoteric types too
+ _verifyCoerceFail("1", Boolean.TYPE);
+ _verifyCoerceFail("1", Boolean.class);
+
+ _verifyCoerceFail("65", Character.class);
+ _verifyCoerceFail("65", Character.TYPE);
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void _verifyCoerceSuccess(String input, Class<?> type, Object exp) throws IOException
+ {
+ Object result = COERCING_MAPPER.readerFor(type)
+ .readValue(input);
+ assertEquals(exp, result);
+ }
+
+ private void _verifyCoerceFail(String input, Class<?> type) 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");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/SingleValueAsArrayTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/SingleValueAsArrayTest.java
index 1903b2e..f0f9354 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/SingleValueAsArrayTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/SingleValueAsArrayTest.java
@@ -71,16 +71,33 @@
public void testSuccessfulDeserializationOfObjectWithChainedArrayCreators() throws IOException
{
- MAPPER.readValue(JSON, Bean1421A.class);
+ Bean1421A result = MAPPER.readValue(JSON, Bean1421A.class);
+ assertNotNull(result);
}
public void testWithSingleString() throws Exception {
- ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
- Bean1421B<List<String>> a = objectMapper.readValue(quote("test2"),
+ Bean1421B<List<String>> a = MAPPER.readValue(quote("test2"),
new TypeReference<Bean1421B<List<String>>>() {});
List<String> expected = new ArrayList<>();
expected.add("test2");
assertEquals(expected, a.value);
}
+
+ public void testPrimitives() throws Exception {
+ int[] i = MAPPER.readValue("16", int[].class);
+ assertEquals(1, i.length);
+ assertEquals(16, i[0]);
+
+ long[] l = MAPPER.readValue("1234", long[].class);
+ assertEquals(1, l.length);
+ assertEquals(1234L, l[0]);
+
+ double[] d = MAPPER.readValue("12.5", double[].class);
+ assertEquals(1, d.length);
+ assertEquals(12.5, d[0]);
+
+ boolean[] b = MAPPER.readValue("true", boolean[].class);
+ assertEquals(1, d.length);
+ assertEquals(true, b[0]);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArray.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArray.java
index 7e8f091..391cd3c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArray.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArray.java
@@ -7,6 +7,7 @@
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
@@ -191,8 +192,7 @@
/* Compatibility with "single-elem as array" feature
/*****************************************************
*/
-
- // for [JACKSON-805]
+
public void testSerializeAsArrayWithSingleProperty() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);
@@ -262,4 +262,32 @@
assertNotNull(result);
assertEquals(3, result.y);
}
+
+ /*
+ /*****************************************************
+ /* Failure tests
+ /*****************************************************
+ */
+
+ public void testUnknownExtraProp() throws Exception
+ {
+ String json = "{\"value\":[true,\"Foobar\",42,13, false]}";
+ try {
+ MAPPER.readValue(json, PojoAsArrayWrapper.class);
+ fail("should not pass with extra element");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Unexpected JSON values");
+ }
+
+ // but actually fine if skip-unknown set
+ PojoAsArrayWrapper v = MAPPER.readerFor(PojoAsArrayWrapper.class)
+ .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .readValue(json);
+ assertNotNull(v);
+ // note: +1 for both so
+ assertEquals(v.value.x, 42);
+ assertEquals(v.value.y, 13);
+ assertTrue(v.value.complete);
+ assertEquals("Foobar", v.value.name);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayAdvanced.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayAdvanced.java
index 989669c..d2e6ddd 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayAdvanced.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayAdvanced.java
@@ -56,6 +56,27 @@
public int c;
}
+ @JsonFormat(shape=JsonFormat.Shape.ARRAY)
+ @JsonPropertyOrder(alphabetic=true)
+ static class AsArrayWithViewAndCreator
+ {
+ @JsonView(ViewA.class)
+ public int a;
+ @JsonView(ViewB.class)
+ public int b;
+ public int c;
+
+ @JsonCreator
+ public AsArrayWithViewAndCreator(@JsonProperty("a") int a,
+ @JsonProperty("b") int b,
+ @JsonProperty("c") int c)
+ {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+ }
+
/*
/*****************************************************
/* Basic tests
@@ -75,12 +96,23 @@
assertEquals("[1,null,3]", json);
// and then that conversely deserializer does something similar
- AsArrayWithView output = MAPPER.readerFor(AsArrayWithView.class).withView(ViewB.class)
+ AsArrayWithView result = MAPPER.readerFor(AsArrayWithView.class).withView(ViewB.class)
.readValue("[1,2,3]");
// should include 'c' (not view-able) and 'b' (include in ViewB) but not 'a'
- assertEquals(3, output.c);
- assertEquals(2, output.b);
- assertEquals(0, output.a);
+ assertEquals(3, result.c);
+ assertEquals(2, result.b);
+ assertEquals(0, result.a);
+ }
+
+ public void testWithViewAndCreator() throws Exception
+ {
+ AsArrayWithViewAndCreator result = MAPPER.readerFor(AsArrayWithViewAndCreator.class)
+ .withView(ViewB.class)
+ .readValue("[1,2,3]");
+ // should include 'c' (not view-able) and 'b' (include in ViewB) but not 'a'
+ assertEquals(3, result.c);
+ assertEquals(2, result.b);
+ assertEquals(0, result.a);
}
public void testWithCreatorsOrdered() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayWithBuilder.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayWithBuilder.java
index 2f572a4..321c86c 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayWithBuilder.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestPOJOAsArrayWithBuilder.java
@@ -1,9 +1,14 @@
package com.fasterxml.jackson.databind.struct;
import com.fasterxml.jackson.annotation.*;
+
import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
/**
* Unit tests for "POJO as array" feature using Builder-style
@@ -28,6 +33,12 @@
static class SimpleBuilderXY
{
public int x, y;
+
+ protected SimpleBuilderXY() { }
+ protected SimpleBuilderXY(int x0, int y0) {
+ x = x0;
+ y = y0;
+ }
public SimpleBuilderXY withX(int x0) {
this.x = x0;
@@ -43,7 +54,48 @@
return new ValueClassXY(x, y);
}
}
-
+
+ // Also, with creator:
+
+ @JsonDeserialize(builder=CreatorBuilder.class)
+ @JsonFormat(shape=JsonFormat.Shape.ARRAY)
+ @JsonPropertyOrder(alphabetic=true)
+ static class CreatorValue
+ {
+ final int a, b, c;
+
+ protected CreatorValue(int a, int b, int c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+ }
+
+ @JsonFormat(shape=JsonFormat.Shape.ARRAY)
+ static class CreatorBuilder {
+ private final int a, b;
+
+ private int c;
+
+ @JsonCreator
+ public CreatorBuilder(@JsonProperty("a") int a,
+ @JsonProperty("b") int b)
+ {
+ this.a = a;
+ this.b = b;
+ }
+
+ @JsonView(String.class)
+ public CreatorBuilder withC(int v) {
+ c = v;
+ return this;
+ }
+
+ public CreatorValue build() {
+ return new CreatorValue(a, b, c);
+ }
+ }
+
/*
/*****************************************************
/* Basic tests
@@ -59,4 +111,95 @@
assertEquals(2, value._x);
assertEquals(3, value._y);
}
+
+ // Won't work, but verify exception
+ public void testBuilderWithUpdate() throws Exception
+ {
+ // Ok, first, simple case of all values being present
+ try {
+ /*value =*/ MAPPER.readerFor(ValueClassXY.class)
+ .withValueToUpdate(new ValueClassXY(6, 7))
+ .readValue("[1,2]");
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Deserialization of");
+ verifyException(e, "by passing existing instance");
+ verifyException(e, "ValueClassXY");
+ }
+ }
+
+ /*
+ /*****************************************************
+ /* Creator test(s)
+ /*****************************************************
+ */
+
+ // test to ensure @JsonCreator also works
+ public void testWithCreator() throws Exception
+ {
+ CreatorValue value = MAPPER.readValue("[1,2,3]", CreatorValue.class);
+ assertEquals(1, value.a);
+ assertEquals(2, value.b);
+ assertEquals(3, value.c);
+
+ // and should be ok with partial too?
+ value = MAPPER.readValue("[1,2]", CreatorValue.class);
+ assertEquals(1, value.a);
+ assertEquals(2, value.b);
+ assertEquals(0, value.c);
+
+ value = MAPPER.readValue("[1]", CreatorValue.class);
+ assertEquals(1, value.a);
+ assertEquals(0, value.b);
+ assertEquals(0, value.c);
+
+ value = MAPPER.readValue("[]", CreatorValue.class);
+ assertEquals(0, value.a);
+ assertEquals(0, value.b);
+ assertEquals(0, value.c);
+ }
+
+ public void testWithCreatorAndView() throws Exception
+ {
+ ObjectReader reader = MAPPER.readerFor(CreatorValue.class);
+ CreatorValue value;
+
+ // First including values in view
+ value = reader.withView(String.class).readValue("[1,2,3]");
+ assertEquals(1, value.a);
+ assertEquals(2, value.b);
+ assertEquals(3, value.c);
+
+ // then not including view
+ value = reader.withView(Character.class).readValue("[1,2,3]");
+ assertEquals(1, value.a);
+ assertEquals(2, value.b);
+ assertEquals(0, value.c);
+ }
+
+ /*
+ /*****************************************************
+ /* Failure tests
+ /*****************************************************
+ */
+
+ public void testUnknownExtraProp() throws Exception
+ {
+ String json = "[1, 2, 3, 4]";
+ try {
+ MAPPER.readValue(json, ValueClassXY.class);
+ fail("should not pass with extra element");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Unexpected JSON values");
+ }
+
+ // but actually fine if skip-unknown set
+ ValueClassXY v = MAPPER.readerFor(ValueClassXY.class)
+ .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .readValue(json);
+ assertNotNull(v);
+ // note: +1 for both so
+ assertEquals(v._x, 2);
+ assertEquals(v._y, 3);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java
index dd373f6..b799172 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java
@@ -23,6 +23,17 @@
}
}
+ final static class Location {
+ public int x;
+ public int y;
+
+ public Location() { }
+ public Location(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
static class DeepUnwrapping
{
@JsonUnwrapped
@@ -45,17 +56,6 @@
name = n;
}
}
-
- final static class Location {
- public int x;
- public int y;
-
- public Location() { }
- public Location(int x, int y) {
- this.x = x;
- this.y = y;
- }
- }
// Class with two unwrapped properties
static class TwoUnwrappedProperties {
@@ -111,6 +111,34 @@
public String country;
}
+ // [databind#2088]
+ static class Issue2088Bean {
+ int x;
+ int y;
+
+ @JsonUnwrapped
+ Issue2088UnwrappedBean w;
+
+ public Issue2088Bean(@JsonProperty("x") int x, @JsonProperty("y") int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public void setW(Issue2088UnwrappedBean w) {
+ this.w = w;
+ }
+ }
+
+ static class Issue2088UnwrappedBean {
+ int a;
+ int b;
+
+ public Issue2088UnwrappedBean(@JsonProperty("a") int a, @JsonProperty("b") int b) {
+ this.a = a;
+ this.b = b;
+ }
+ }
+
/*
/**********************************************************
/* Tests, serialization
@@ -215,4 +243,14 @@
Person p = mapper.readValue("{ }", Person.class);
assertNotNull(p);
}
+
+ // [databind#2088]: accidental skipping of values
+ public void testIssue2088UnwrappedFieldsAfterLastCreatorProp() throws Exception
+ {
+ Issue2088Bean bean = MAPPER.readValue("{\"x\":1,\"a\":2,\"y\":3,\"b\":4}", Issue2088Bean.class);
+ assertEquals(1, bean.x);
+ assertEquals(2, bean.w.a);
+ assertEquals(3, bean.y);
+ assertEquals(4, bean.w.b);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestUnwrappedIssue383.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedRecursive383.java
similarity index 82%
rename from src/test/java/com/fasterxml/jackson/failing/TestUnwrappedIssue383.java
rename to src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedRecursive383.java
index a9a8746..5b41be6 100644
--- a/src/test/java/com/fasterxml/jackson/failing/TestUnwrappedIssue383.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedRecursive383.java
@@ -1,12 +1,13 @@
-package com.fasterxml.jackson.failing;
+package com.fasterxml.jackson.databind.struct;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
-public class TestUnwrappedIssue383 extends BaseMapTest
+// Problem with recursive definition of unwrapping
+public class TestUnwrappedRecursive383 extends BaseMapTest
{
- // [Issue#383]
+ // [databind#383]
static class RecursivePerson {
public String name;
public int age;
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedWithPrefix.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedWithPrefix.java
index 5d439c5..29e742f 100644
--- a/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedWithPrefix.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedWithPrefix.java
@@ -137,10 +137,6 @@
static class SubChild {
public String value;
}
-
- // // // Reuse mapper to keep tests bit faster
-
- private final ObjectMapper MAPPER = new ObjectMapper();
/*
/**********************************************************
@@ -148,6 +144,8 @@
/**********************************************************
*/
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
public void testPrefixedUnwrappingSerialize() throws Exception
{
assertEquals("{\"name\":\"Tatu\",\"_x\":1,\"_y\":2}",
diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestUnwrappedWithTypeInfo.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedWithTypeInfo.java
similarity index 97%
rename from src/test/java/com/fasterxml/jackson/databind/ser/TestUnwrappedWithTypeInfo.java
rename to src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedWithTypeInfo.java
index d199044..5a5f2cc 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ser/TestUnwrappedWithTypeInfo.java
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrappedWithTypeInfo.java
@@ -1,4 +1,4 @@
-package com.fasterxml.jackson.databind.ser;
+package com.fasterxml.jackson.databind.struct;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java
new file mode 100644
index 0000000..4352835
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrapSingleArrayScalarsTest.java
@@ -0,0 +1,408 @@
+package com.fasterxml.jackson.databind.struct;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URI;
+import java.util.UUID;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+public class UnwrapSingleArrayScalarsTest extends BaseMapTest
+{
+ static class BooleanBean {
+ boolean _v;
+ void setV(boolean v) { _v = v; }
+ }
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final ObjectReader NO_UNWRAPPING_READER = MAPPER.reader();
+ private final ObjectReader UNWRAPPING_READER = MAPPER.reader()
+ .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ /*
+ /**********************************************************
+ /* Tests for boolean
+ /**********************************************************
+ */
+
+ public void testBooleanPrimitiveArrayUnwrap() throws Exception
+ {
+ // [databind#381]
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ BooleanBean result = mapper.readValue(new StringReader("{\"v\":[true]}"), BooleanBean.class);
+ assertTrue(result._v);
+
+ _verifyMultiValueArrayFail("[{\"v\":[true,true]}]", BooleanBean.class);
+
+ result = mapper.readValue("{\"v\":[null]}", BooleanBean.class);
+ assertNotNull(result);
+ assertFalse(result._v);
+
+ result = mapper.readValue("[{\"v\":[null]}]", BooleanBean.class);
+ assertNotNull(result);
+ assertFalse(result._v);
+
+ boolean[] array = mapper.readValue(new StringReader("[ [ null ] ]"), boolean[].class);
+ assertNotNull(array);
+ assertEquals(1, array.length);
+ assertFalse(array[0]);
+ }
+
+ /*
+ /**********************************************************
+ /* Single-element as array tests, numbers
+ /**********************************************************
+ */
+
+ // [databind#381]
+ public void testSingleElementScalarArrays() throws Exception {
+ final int intTest = 932832;
+ final double doubleTest = 32.3234;
+ final long longTest = 2374237428374293423L;
+ final short shortTest = (short) intTest;
+ final float floatTest = 84.3743f;
+ final byte byteTest = (byte) 43;
+ final char charTest = 'c';
+
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ final int intValue = mapper.readValue(asArray(intTest), Integer.TYPE);
+ assertEquals(intTest, intValue);
+ final Integer integerWrapperValue = mapper.readValue(asArray(Integer.valueOf(intTest)), Integer.class);
+ assertEquals(Integer.valueOf(intTest), integerWrapperValue);
+
+ final double doubleValue = mapper.readValue(asArray(doubleTest), Double.class);
+ assertEquals(doubleTest, doubleValue);
+ final Double doubleWrapperValue = mapper.readValue(asArray(Double.valueOf(doubleTest)), Double.class);
+ assertEquals(Double.valueOf(doubleTest), doubleWrapperValue);
+
+ final long longValue = mapper.readValue(asArray(longTest), Long.TYPE);
+ assertEquals(longTest, longValue);
+ final Long longWrapperValue = mapper.readValue(asArray(Long.valueOf(longTest)), Long.class);
+ assertEquals(Long.valueOf(longTest), longWrapperValue);
+
+ final short shortValue = mapper.readValue(asArray(shortTest), Short.TYPE);
+ assertEquals(shortTest, shortValue);
+ final Short shortWrapperValue = mapper.readValue(asArray(Short.valueOf(shortTest)), Short.class);
+ assertEquals(Short.valueOf(shortTest), shortWrapperValue);
+
+ final float floatValue = mapper.readValue(asArray(floatTest), Float.TYPE);
+ assertEquals(floatTest, floatValue);
+ final Float floatWrapperValue = mapper.readValue(asArray(Float.valueOf(floatTest)), Float.class);
+ assertEquals(Float.valueOf(floatTest), floatWrapperValue);
+
+ final byte byteValue = mapper.readValue(asArray(byteTest), Byte.TYPE);
+ assertEquals(byteTest, byteValue);
+ final Byte byteWrapperValue = mapper.readValue(asArray(Byte.valueOf(byteTest)), Byte.class);
+ assertEquals(Byte.valueOf(byteTest), byteWrapperValue);
+
+ final char charValue = mapper.readValue(asArray(quote(String.valueOf(charTest))), Character.TYPE);
+ assertEquals(charTest, charValue);
+ final Character charWrapperValue = mapper.readValue(asArray(quote(String.valueOf(charTest))), Character.class);
+ assertEquals(Character.valueOf(charTest), charWrapperValue);
+
+ final boolean booleanTrueValue = mapper.readValue(asArray(true), Boolean.TYPE);
+ assertTrue(booleanTrueValue);
+
+ final boolean booleanFalseValue = mapper.readValue(asArray(false), Boolean.TYPE);
+ assertFalse(booleanFalseValue);
+
+ final Boolean booleanWrapperTrueValue = mapper.readValue(asArray(Boolean.valueOf(true)), Boolean.class);
+ assertEquals(Boolean.TRUE, booleanWrapperTrueValue);
+ }
+
+ public void testSingleElementArrayDisabled() throws Exception {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ try {
+ mapper.readValue("[42]", Integer.class);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("[42]", Integer.TYPE);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("[42342342342342]", Long.class);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("[42342342342342342]", Long.TYPE);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+
+ try {
+ mapper.readValue("[42]", Short.class);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("[42]", Short.TYPE);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+
+ try {
+ mapper.readValue("[327.2323]", Float.class);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("[82.81902]", Float.TYPE);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+
+ try {
+ mapper.readValue("[22]", Byte.class);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("[22]", Byte.TYPE);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+
+ try {
+ mapper.readValue("['d']", Character.class);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("['d']", Character.TYPE);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+
+ try {
+ mapper.readValue("[true]", Boolean.class);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ try {
+ mapper.readValue("[true]", Boolean.TYPE);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException exp) {
+ //Exception was thrown correctly
+ }
+ }
+
+ public void testMultiValueArrayException() throws IOException {
+ _verifyMultiValueArrayFail("[42,42]", Integer.class);
+ _verifyMultiValueArrayFail("[42,42]", Integer.TYPE);
+ _verifyMultiValueArrayFail("[42342342342342,42342342342342]", Long.class);
+ _verifyMultiValueArrayFail("[42342342342342342,42342342342342]", Long.TYPE);
+ _verifyMultiValueArrayFail("[42,42]", Short.class);
+ _verifyMultiValueArrayFail("[42,42]", Short.TYPE);
+ _verifyMultiValueArrayFail("[22,23]", Byte.class);
+ _verifyMultiValueArrayFail("[22,23]", Byte.TYPE);
+ _verifyMultiValueArrayFail("[327.2323,327.2323]", Float.class);
+ _verifyMultiValueArrayFail("[82.81902,327.2323]", Float.TYPE);
+ _verifyMultiValueArrayFail("[42.273,42.273]", Double.class);
+ _verifyMultiValueArrayFail("[42.2723,42.273]", Double.TYPE);
+ _verifyMultiValueArrayFail(asArray(quote("c") + "," + quote("d")), Character.class);
+ _verifyMultiValueArrayFail(asArray(quote("c") + "," + quote("d")), Character.TYPE);
+ _verifyMultiValueArrayFail("[true,false]", Boolean.class);
+ _verifyMultiValueArrayFail("[true,false]", Boolean.TYPE);
+ }
+
+ /*
+ /**********************************************************
+ /* Simple non-primitive types
+ /**********************************************************
+ */
+
+ public void testSingleString() throws Exception
+ {
+ String value = "FOO!";
+ String result = MAPPER.readValue("\""+value+"\"", String.class);
+ assertEquals(value, result);
+ }
+
+ public void testSingleStringWrapped() throws Exception
+ {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ String value = "FOO!";
+ try {
+ mapper.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");
+ }
+
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ try {
+ mapper.readValue("[\""+value+"\",\""+value+"\"]", String.class);
+ fail("Exception not thrown when attempting to unwrap a single value 'String' array that contained more than one value into a simple String");
+ } catch (MismatchedInputException exp) {
+ verifyException(exp, "Attempted to unwrap");
+ }
+ String result = mapper.readValue("[\""+value+"\"]", String.class);
+ assertEquals(value, result);
+ }
+
+ public void testBigDecimal() throws Exception
+ {
+ final ObjectMapper mapper = objectMapper();
+ mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ BigDecimal value = new BigDecimal("0.001");
+ BigDecimal result = mapper.readValue(value.toString(), BigDecimal.class);
+ assertEquals(value, result);
+ try {
+ mapper.readValue("[" + value.toString() + "]", BigDecimal.class);
+ 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");
+ }
+
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ result = mapper.readValue("[" + value.toString() + "]", BigDecimal.class);
+ assertEquals(value, result);
+
+ try {
+ mapper.readValue("[" + value.toString() + "," + value.toString() + "]", BigDecimal.class);
+ fail("Exception was not thrown when attempting to read a muti value array of BigDecimal when UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled");
+ } catch (MismatchedInputException exp) {
+ verifyException(exp, "Attempted to unwrap");
+ }
+ }
+
+ public void testBigInteger() throws Exception
+ {
+ final ObjectMapper mapper = objectMapper();
+ mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+
+ BigInteger value = new BigInteger("-1234567890123456789012345567809");
+ BigInteger result = mapper.readValue(value.toString(), BigInteger.class);
+ assertEquals(value, result);
+
+ try {
+ mapper.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");
+ }
+
+ mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
+ result = mapper.readValue("[" + value.toString() + "]", BigInteger.class);
+ assertEquals(value, result);
+
+ try {
+ mapper.readValue("[" + value.toString() + "," + value.toString() + "]", BigInteger.class);
+ fail("Exception was not thrown when attempting to read a multi-value array of BigInteger when UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled");
+ } catch (MismatchedInputException exp) {
+ verifyException(exp, "Attempted to unwrap");
+ }
+ }
+
+ public void testClassAsArray() throws Exception
+ {
+ Class<?> result = MAPPER
+ .readerFor(Class.class)
+ .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ .readValue(quote(String.class.getName()));
+ assertEquals(String.class, result);
+
+ try {
+ MAPPER.readerFor(Class.class)
+ .without(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ .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");
+ }
+
+ _verifyMultiValueArrayFail("[" + quote(Object.class.getName()) + "," + quote(Object.class.getName()) +"]",
+ Class.class);
+ result = MAPPER.readerFor(Class.class)
+ .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ .readValue("[" + quote(String.class.getName()) + "]");
+ assertEquals(String.class, result);
+ }
+
+ public void testURIAsArray() throws Exception
+ {
+ final ObjectReader reader = MAPPER.readerFor(URI.class);
+ final URI value = new URI("http://foo.com");
+ try {
+ reader.without(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ .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");
+ }
+
+ _verifyMultiValueArrayFail("[\""+value.toString()+"\",\""+value.toString()+"\"]", URI.class);
+ }
+
+ public void testUUIDAsArray() throws Exception
+ {
+ final ObjectReader reader = MAPPER.readerFor(UUID.class);
+ final String uuidStr = "76e6d183-5f68-4afa-b94a-922c1fdb83f8";
+ UUID uuid = UUID.fromString(uuidStr);
+ try {
+ NO_UNWRAPPING_READER.forType(UUID.class)
+ .readValue("[" + quote(uuidStr) + "]");
+ fail("Exception was not thrown when UNWRAP_SINGLE_VALUE_ARRAYS is disabled and attempted to read a single value array as a single element");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "out of START_ARRAY token");
+ }
+ assertEquals(uuid,
+ reader.with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ .readValue("[" + quote(uuidStr) + "]"));
+ _verifyMultiValueArrayFail("[" + quote(uuidStr) + "," + quote(uuidStr) + "]", UUID.class);
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void _verifyMultiValueArrayFail(String input, Class<?> type) throws IOException {
+ try {
+ UNWRAPPING_READER.forType(type).readValue(input);
+ fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled");
+ } catch (MismatchedInputException e) {
+ verifyException(e, "Attempted to unwrap");
+ }
+ }
+
+ private static String asArray(Object value) {
+ return "["+value+"]";
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedCreatorParam265Test.java b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedCreatorParam265Test.java
new file mode 100644
index 0000000..62010d8
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedCreatorParam265Test.java
@@ -0,0 +1,101 @@
+package com.fasterxml.jackson.databind.struct;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+
+public class UnwrappedCreatorParam265Test extends BaseMapTest
+{
+ static class JAddress {
+ public String address;
+ public String city;
+ public String state;
+
+ protected JAddress() { }
+
+ public JAddress(String address, String city, String state) {
+ this.address = address;
+ this.city = city;
+ this.state = state;
+ }
+ }
+
+ static class JPersonWithoutName
+ {
+ public String name;
+
+ protected JAddress _address;
+
+ @JsonCreator
+ public JPersonWithoutName(@JsonProperty("name") String name,
+ @JsonUnwrapped JAddress address)
+ {
+ this.name = name;
+ _address = address;
+ }
+
+ @JsonUnwrapped
+ public JAddress getAddress() { return _address; }
+ }
+
+ static class JPersonWithName
+ {
+ public String name;
+
+ protected JAddress _address;
+
+ @JsonCreator
+ public JPersonWithName(@JsonProperty("name") String name,
+ @JsonUnwrapped
+ @JsonProperty("address")
+ JAddress address)
+ {
+ this.name = name;
+ _address = address;
+ }
+
+ @JsonUnwrapped
+ public JAddress getAddress() { return _address; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ // For [databind#265]: handle problem by throwing exception
+ public void testUnwrappedWithUnnamedCreatorParam() throws Exception
+ {
+ JPersonWithoutName person = new JPersonWithoutName("MyName", new JAddress("main street", "springfield", "WA"));
+ ObjectMapper mapper = new ObjectMapper();
+ // serialization should be fine as far as that goes
+ String json = mapper.writeValueAsString(person);
+
+ // but not deserialization:
+ try {
+ /*JPersonWithoutName result =*/ mapper.readValue(json, JPersonWithoutName.class);
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Cannot define Creator parameter");
+ verifyException(e, "@JsonUnwrapped");
+ }
+ }
+
+ // For [databind#265]: handle problem by throwing exception
+ public void testUnwrappedWithNamedCreatorParam() throws Exception
+ {
+ JPersonWithName person = new JPersonWithName("MyName", new JAddress("main street", "springfield", "WA"));
+ ObjectMapper mapper = new ObjectMapper();
+ // serialization should be fine as far as that goes
+ String json = mapper.writeValueAsString(person);
+ try {
+ /*JPersonWithName result =*/ mapper.readValue(json, JPersonWithName.class);
+ fail("Should not pass");
+ } catch (InvalidDefinitionException e) {
+ verifyException(e, "Cannot define Creator property \"address\"");
+ verifyException(e, "@JsonUnwrapped");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedWithView1559Test.java b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedWithView1559Test.java
new file mode 100644
index 0000000..b23cf71
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedWithView1559Test.java
@@ -0,0 +1,36 @@
+package com.fasterxml.jackson.databind.struct;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.*;
+
+public class UnwrappedWithView1559Test extends BaseMapTest
+{
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ static final class Health {
+ @JsonUnwrapped(prefix="xxx.")
+ public Status status;
+ }
+
+ // NOTE: `final` is required to trigger [databind#1559]
+ static final class Status {
+ public String code;
+ }
+
+ /*
+ /**********************************************************
+ /* Tests methods
+ /**********************************************************
+ */
+
+ // for [databind#1559]
+ public void testCanSerializeSimpleWithDefaultView() throws Exception {
+ String json = new ObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false)
+ .writeValueAsString(new Health());
+ assertEquals(aposToQuotes("{}"), json);
+ // and just in case this, although won't matter wrt output
+ json = new ObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true)
+ .writeValueAsString(new Health());
+ assertEquals(aposToQuotes("{}"), json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/ContainerTypesTest.java b/src/test/java/com/fasterxml/jackson/databind/type/ContainerTypesTest.java
new file mode 100644
index 0000000..e91f827
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/type/ContainerTypesTest.java
@@ -0,0 +1,113 @@
+package com.fasterxml.jackson.databind.type;
+
+import java.util.*;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.databind.util.LRUMap;
+
+// for [databind#1415]
+public class ContainerTypesTest extends BaseMapTest
+{
+ static abstract class LongList implements List<Long> { }
+
+ static abstract class StringLongMap implements Map<String,Long> { }
+
+ /*
+ /**********************************************************
+ /* Unit tests, success
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testExplicitCollectionType() throws Exception
+ {
+ JavaType t = MAPPER.getTypeFactory()
+ .constructCollectionType(LongList.class, Long.class);
+ assertEquals(LongList.class, t.getRawClass());
+ assertEquals(Long.class, t.getContentType().getRawClass());
+ }
+
+ public void testImplicitCollectionType() throws Exception
+ {
+ JavaType t = MAPPER.getTypeFactory()
+ .constructParametricType(List.class, Long.class);
+ assertEquals(CollectionType.class, t.getClass());
+ assertEquals(List.class, t.getRawClass());
+ assertEquals(Long.class, t.getContentType().getRawClass());
+ }
+
+ // [databind#1725]
+ public void testMissingCollectionType() throws Exception
+ {
+ TypeFactory tf = MAPPER.getTypeFactory().withCache(new LRUMap<Object,JavaType>(4, 8));
+ JavaType t = tf.constructParametricType(List.class, HashMap.class);
+ assertEquals(CollectionType.class, t.getClass());
+ assertEquals(List.class, t.getRawClass());
+ assertEquals(HashMap.class, t.getContentType().getRawClass());
+ }
+
+ public void testExplicitMapType() throws Exception
+ {
+ JavaType t = MAPPER.getTypeFactory()
+ .constructMapType(StringLongMap.class,
+ String.class, Long.class);
+ assertEquals(StringLongMap.class, t.getRawClass());
+ assertEquals(String.class, t.getKeyType().getRawClass());
+ assertEquals(Long.class, t.getContentType().getRawClass());
+ }
+
+ public void testImplicitMapType() throws Exception
+ {
+ JavaType t = MAPPER.getTypeFactory()
+ .constructParametricType(Map.class, Long.class, Boolean.class);
+ assertEquals(MapType.class, t.getClass());
+ assertEquals(Long.class, t.getKeyType().getRawClass());
+ assertEquals(Boolean.class, t.getContentType().getRawClass());
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests, fails
+ /**********************************************************
+ */
+
+ public void testMismatchedCollectionType() throws Exception
+ {
+ try {
+ MAPPER.getTypeFactory()
+ .constructCollectionType(LongList.class, String.class);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "`"+getClass().getName()+"$LongList` did not resolve to something");
+ verifyException(e, "element type");
+ }
+ }
+
+ public void testMismatchedMapType() throws Exception
+ {
+ // first, mismatched key type
+ try {
+ MAPPER.getTypeFactory()
+ .constructMapType(StringLongMap.class, Boolean.class, Long.class);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "`"+getClass().getName()+"$StringLongMap` did not resolve to something");
+ verifyException(e, "key type");
+ }
+ // then, mismatched value type
+ try {
+ MAPPER.getTypeFactory()
+ .constructMapType(StringLongMap.class, String.class, Class.class);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "`"+getClass().getName()+"$StringLongMap` did not resolve to something");
+ verifyException(e, "value type");
+ }
+ }
+}
+
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/NestedTypes1604Test.java b/src/test/java/com/fasterxml/jackson/databind/type/NestedTypes1604Test.java
index 753f017..bf77b68 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/NestedTypes1604Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/NestedTypes1604Test.java
@@ -89,7 +89,7 @@
}
}
- private final ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectMapper objectMapper = newObjectMapper();
public void testIssue1604Simple() throws Exception
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/RecursiveTypeTest.java b/src/test/java/com/fasterxml/jackson/databind/type/RecursiveTypeTest.java
index f0643af..36b27f2 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/RecursiveTypeTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/RecursiveTypeTest.java
@@ -85,7 +85,7 @@
assertNotNull(json);
- // can not deserialize with current definition, however
+ // cannot deserialize with current definition, however
}
// for [databind#1301]
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java b/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java
index dd16220..c5fb1a5 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java
@@ -2,8 +2,7 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
-import com.fasterxml.jackson.databind.introspect.AnnotatedField;
+import com.fasterxml.jackson.databind.introspect.*;
/**
* Unit test for verifying that {@link AnnotatedClass}
@@ -82,7 +81,7 @@
{
SerializationConfig config = MAPPER.getSerializationConfig();
JavaType t = MAPPER.constructType(FieldBean.class);
- AnnotatedClass ac = AnnotatedClass.construct(t, config);
+ AnnotatedClass ac = AnnotatedClassResolver.resolve(config, t, config);
// AnnotatedClass does not ignore non-visible fields, yet
assertEquals(2, ac.getFieldCount());
for (AnnotatedField f : ac.fields()) {
@@ -101,7 +100,27 @@
Bean1005 bean = new Bean1005(13);
SerializationConfig config = MAPPER.getSerializationConfig();
JavaType t = MAPPER.constructType(bean.getClass());
- AnnotatedClass ac = AnnotatedClass.construct(t, config);
+ AnnotatedClass ac = AnnotatedClassResolver.resolve(config, t, config);
assertEquals(1, ac.getConstructors().size());
}
+
+ public void testArrayTypeIntrospection() throws Exception
+ {
+ AnnotatedClass ac = AnnotatedClassResolver.resolve(MAPPER.getSerializationConfig(),
+ MAPPER.constructType(int[].class), null);
+ // 09-Jun-2017, tatu: During 2.9 development, access methods were failing at
+ // certain points so
+ assertFalse(ac.memberMethods().iterator().hasNext());
+ assertFalse(ac.fields().iterator().hasNext());
+ }
+
+ public void testIntrospectionWithRawClass() throws Exception
+ {
+ AnnotatedClass ac = AnnotatedClassResolver.resolveWithoutSuperTypes(MAPPER.getSerializationConfig(),
+ String.class, null);
+ // 09-Jun-2017, tatu: During 2.9 development, access methods were failing at
+ // certain points so
+ assertFalse(ac.memberMethods().iterator().hasNext());
+ assertFalse(ac.fields().iterator().hasNext());
+ }
}
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 91be3f7..ffe0b29 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java
@@ -72,6 +72,7 @@
JavaType baseType = tf.constructType(BaseType.class);
assertSame(BaseType.class, baseType.getRawClass());
assertTrue(baseType.hasRawClass(BaseType.class));
+ assertFalse(baseType.isTypeOrSubTypeOf(SubType.class));
assertFalse(baseType.isArrayType());
assertFalse(baseType.isContainerType());
@@ -84,8 +85,28 @@
assertNull(baseType.getContentType());
assertNull(baseType.getKeyType());
assertNull(baseType.getValueHandler());
+
+ assertEquals("Lcom/fasterxml/jackson/databind/type/TestJavaType$BaseType;", baseType.getGenericSignature());
+ assertEquals("Lcom/fasterxml/jackson/databind/type/TestJavaType$BaseType;", baseType.getErasedSignature());
}
+ @SuppressWarnings("deprecation")
+ public void testDeprecated()
+ {
+ TypeFactory tf = TypeFactory.defaultInstance();
+ JavaType baseType = tf.constructType(BaseType.class);
+ assertTrue(baseType.hasRawClass(BaseType.class));
+ assertNull(baseType.getParameterSource());
+ assertNull(baseType.getContentTypeHandler());
+ assertNull(baseType.getContentValueHandler());
+ assertFalse(baseType.hasValueHandler());
+ assertFalse(baseType.hasHandlers());
+
+ assertSame(baseType, baseType.forcedNarrowBy(BaseType.class));
+ JavaType sub = baseType.forcedNarrowBy(SubType.class);
+ assertTrue(sub.hasRawClass(SubType.class));
+ }
+
public void testArrayType()
{
TypeFactory tf = TypeFactory.defaultInstance();
@@ -119,6 +140,9 @@
assertNotNull(mapT.getContentType());
assertNotNull(mapT.getKeyType());
+ assertEquals("Ljava/util/HashMap<Ljava/lang/Object;Ljava/lang/Object;>;", mapT.getGenericSignature());
+ assertEquals("Ljava/util/HashMap;", mapT.getErasedSignature());
+
assertTrue(mapT.equals(mapT));
assertFalse(mapT.equals(null));
assertFalse(mapT.equals("xyz"));
@@ -127,7 +151,17 @@
public void testEnumType()
{
TypeFactory tf = TypeFactory.defaultInstance();
- assertTrue(tf.constructType(MyEnum.class).isEnumType());
+ JavaType enumT = tf.constructType(MyEnum.class);
+ assertTrue(enumT.isEnumType());
+ assertFalse(enumT.hasHandlers());
+ assertTrue(enumT.isTypeOrSubTypeOf(MyEnum.class));
+ assertTrue(enumT.isTypeOrSubTypeOf(Object.class));
+ assertNull(enumT.containedType(3));
+ assertTrue(enumT.containedTypeOrUnknown(3).isJavaLangObject());
+
+ assertEquals("Lcom/fasterxml/jackson/databind/type/TestJavaType$MyEnum;", enumT.getGenericSignature());
+ assertEquals("Lcom/fasterxml/jackson/databind/type/TestJavaType$MyEnum;", enumT.getErasedSignature());
+
assertTrue(tf.constructType(MyEnum2.class).isEnumType());
assertTrue(tf.constructType(MyEnum.A.getClass()).isEnumType());
assertTrue(tf.constructType(MyEnum2.A.getClass()).isEnumType());
@@ -164,7 +198,8 @@
m = Generic1194.class.getMethod("getList");
t = tf.constructType(m.getGenericReturnType());
assertEquals("Ljava/util/List<Ljava/lang/String;>;", t.getGenericSignature());
-
+ assertEquals("Ljava/util/List;", t.getErasedSignature());
+
m = Generic1194.class.getMethod("getMap");
t = tf.constructType(m.getGenericReturnType());
assertEquals("Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;",
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeBindings.java b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeBindings.java
index 7af37e8..6216842 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeBindings.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeBindings.java
@@ -40,10 +40,11 @@
/**********************************************************
*/
+ private final TypeFactory DEFAULT_TF = TypeFactory.defaultInstance();
+
public void testInnerType() throws Exception
{
- TypeFactory tf = TypeFactory.defaultInstance();
- JavaType type = tf.constructType(InnerGenericTyping.InnerClass.class);
+ JavaType type = DEFAULT_TF.constructType(InnerGenericTyping.InnerClass.class);
assertEquals(MapType.class, type.getClass());
JavaType keyType = type.getKeyType();
assertEquals(Object.class, keyType.getRawClass());
@@ -56,8 +57,34 @@
// for [databind#76]
public void testRecursiveType()
{
- TypeFactory tf = TypeFactory.defaultInstance();
- JavaType type = tf.constructType(HashTree.class);
+ JavaType type = DEFAULT_TF.constructType(HashTree.class);
assertNotNull(type);
}
+
+ public void testBindingsBasics()
+ {
+ TypeBindings b = TypeBindings.create(Collection.class,
+ TypeFactory.unknownType());
+ // let's just call it -- should probably try to inspect but...
+ assertNotNull(b.toString());
+ assertEquals(Object.class, b.getBoundType(0).getRawClass());
+ assertNull(b.getBoundName(-1));
+ assertNull(b.getBoundType(-1));
+ assertNull(b.getBoundName(1));
+ assertNull(b.getBoundType(1));
+
+ assertFalse(b.equals("foo"));
+ }
+
+ public void testInvalidBindings()
+ {
+ JavaType unknown = TypeFactory.unknownType();
+ try {
+ TypeBindings.create(AbstractType.class, unknown);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "Cannot create TypeBindings");
+ verifyException(e, "class expects 2");
+ }
+ }
}
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 ab0ff60..60bcee8 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java
@@ -192,14 +192,14 @@
// Maps must take 2 type parameters, not just one
tf.constructParametrizedType(Map.class, Map.class, strC);
} catch (IllegalArgumentException e) {
- verifyException(e, "Can not create TypeBindings for class java.util.Map");
+ verifyException(e, "Cannot create TypeBindings for class java.util.Map");
}
try {
// Type only accepts one type param
tf.constructParametrizedType(SingleArgGeneric.class, SingleArgGeneric.class, strC, strC);
} catch (IllegalArgumentException e) {
- verifyException(e, "Can not create TypeBindings for class ");
+ verifyException(e, "Cannot create TypeBindings for class ");
}
}
@@ -383,10 +383,6 @@
assertEquals(Integer.class, subtype.getContentType().getContentType().getRawClass());
// but with refinement, should have non-null super class
- // 20-Oct-2015, tatu: For now refinement does not faithfully replicate the
- // structure, it only retains most important information. Here it means
- // that actually existing super-classes are skipped, and only original
- // type is linked as expected
JavaType superType = subtype.getSuperClass();
assertNotNull(superType);
@@ -397,6 +393,25 @@
assertEquals(Integer.class, superType.getContentType().getContentType().getRawClass());
}
+ public void testTypeGeneralization()
+ {
+ TypeFactory tf = newTypeFactory();
+ MapType t = tf.constructMapType(HashMap.class, String.class, Long.class);
+ JavaType superT = tf.constructGeneralizedType(t, Map.class);
+ assertEquals(String.class, superT.getKeyType().getRawClass());
+ assertEquals(Long.class, superT.getContentType().getRawClass());
+
+ assertSame(t, tf.constructGeneralizedType(t, HashMap.class));
+
+ // plus check there is super/sub relationship
+ try {
+ tf.constructGeneralizedType(t, TreeMap.class);
+ fail("Should not pass");
+ } catch (IllegalArgumentException e) {
+ verifyException(e, "not a super-type of");
+ }
+ }
+
public void testMapTypesRaw()
{
TypeFactory tf = TypeFactory.defaultInstance();
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory1604.java b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory1604.java
index 6bfec7c..4455547 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory1604.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory1604.java
@@ -96,7 +96,6 @@
public void testErrorForMismatch()
{
TypeFactory tf = newTypeFactory();
-
// NOTE: plain `String` NOT `List<String>`
JavaType base = tf.constructType(new TypeReference<Data1604<String>>() { });
@@ -109,10 +108,4 @@
verifyException(e, "DataList1604");
}
}
-
- /*
- static class TwoParam1604<KEY,VALUE> { }
-
- static class SneakyTwoParam1604<V,K> extends TwoParam1604<K,List<V>> { }
- */
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactoryWithClassLoader.java b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactoryWithClassLoader.java
index e9a8070..fa787ba 100644
--- a/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactoryWithClassLoader.java
+++ b/src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactoryWithClassLoader.java
@@ -24,31 +24,31 @@
private static ClassLoader classLoader;
private static ClassLoader threadClassLoader;
private static String aClassName;
- private ObjectMapper objectMapper;
+ private ObjectMapper mapper;
-@BeforeClass
-public static void beforeClass() {
+ @BeforeClass
+ public static void beforeClass() {
classLoader = AClass.class.getClassLoader();
aClassName = AClass.getStaticClassName();
threadClassLoader = Thread.currentThread().getContextClassLoader();
Assert.assertNotNull(threadClassLoader);
-}
+ }
-@Before
-public void before() {
- objectMapper = new ObjectMapper();
-}
+ @Before
+ public void before() {
+ mapper = new ObjectMapper();
+ }
-@After
-public void after() {
+ @After
+ public void after() {
Thread.currentThread().setContextClassLoader(threadClassLoader);
- objectMapper = null;
-}
-
-@Test
-public void testUsesCorrectClassLoaderWhenThreadClassLoaderIsNull() throws ClassNotFoundException {
+ mapper = null;
+ }
+
+ @Test
+ public void testUsesCorrectClassLoaderWhenThreadClassLoaderIsNull() throws ClassNotFoundException {
Thread.currentThread().setContextClassLoader(null);
- TypeFactory spySut = spy(objectMapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader));
+ TypeFactory spySut = spy(mapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader));
Class<?> clazz = spySut.findClass(aClassName);
verify(spySut).getClassLoader();
verify(spySut).classForName(any(String.class), any(Boolean.class), eq(classLoader));
@@ -56,11 +56,11 @@
Assert.assertEquals(classLoader, spySut.getClassLoader());
Assert.assertEquals(typeModifier,spySut._modifiers[0]);
Assert.assertEquals(null, Thread.currentThread().getContextClassLoader());
-}
+ }
-@Test
+ @Test
public void testUsesCorrectClassLoaderWhenThreadClassLoaderIsNotNull() throws ClassNotFoundException {
- TypeFactory spySut = spy(objectMapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader));
+ TypeFactory spySut = spy(mapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader));
Class<?> clazz = spySut.findClass(aClassName);
verify(spySut).getClassLoader();
verify(spySut).classForName(any(String.class), any(Boolean.class), eq(classLoader));
@@ -71,42 +71,42 @@
@Test
public void testCallingOnlyWithModifierGivesExpectedResults(){
- TypeFactory sut = objectMapper.getTypeFactory().withModifier(typeModifier);
+ TypeFactory sut = mapper.getTypeFactory().withModifier(typeModifier);
Assert.assertNull(sut.getClassLoader());
Assert.assertEquals(typeModifier,sut._modifiers[0]);
}
@Test
public void testCallingOnlyWithClassLoaderGivesExpectedResults(){
- TypeFactory sut = objectMapper.getTypeFactory().withClassLoader(classLoader);
+ TypeFactory sut = mapper.getTypeFactory().withClassLoader(classLoader);
Assert.assertNotNull(sut.getClassLoader());
Assert.assertArrayEquals(null,sut._modifiers);
}
@Test
public void testDefaultTypeFactoryNotAffectedByWithConstructors() {
- TypeFactory sut = objectMapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader);
+ TypeFactory sut = mapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader);
Assert.assertEquals(classLoader, sut.getClassLoader());
Assert.assertEquals(typeModifier,sut._modifiers[0]);
- Assert.assertNull(objectMapper.getTypeFactory().getClassLoader());
- Assert.assertArrayEquals(null,objectMapper.getTypeFactory()._modifiers);
+ Assert.assertNull(mapper.getTypeFactory().getClassLoader());
+ Assert.assertArrayEquals(null,mapper.getTypeFactory()._modifiers);
}
@Test
public void testSetsTheCorrectClassLoderIfUsingWithModifierFollowedByWithClassLoader() {
- TypeFactory sut = objectMapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader);
+ TypeFactory sut = mapper.getTypeFactory().withModifier(typeModifier).withClassLoader(classLoader);
Assert.assertNotNull(sut.getClassLoader());
}
@Test
public void testSetsTheCorrectClassLoderIfUsingWithClassLoaderFollowedByWithModifier() {
- TypeFactory sut = objectMapper.getTypeFactory().withClassLoader(classLoader).withModifier(typeModifier);
+ TypeFactory sut = mapper.getTypeFactory().withClassLoader(classLoader).withModifier(typeModifier);
Assert.assertNotNull(sut.getClassLoader());
}
@Test
public void testThreadContextClassLoaderIsUsedIfNotUsingWithClassLoader() throws ClassNotFoundException {
- TypeFactory spySut = spy(objectMapper.getTypeFactory());
+ TypeFactory spySut = spy(mapper.getTypeFactory());
Assert.assertNull(spySut.getClassLoader());
Class<?> clazz = spySut.findClass(aClassName);
Assert.assertNotNull(clazz);
@@ -116,7 +116,7 @@
@Test
public void testUsesFallBackClassLoaderIfNoThreadClassLoaderAndNoWithClassLoader() throws ClassNotFoundException {
Thread.currentThread().setContextClassLoader(null);
- TypeFactory spySut = spy(objectMapper.getTypeFactory());
+ TypeFactory spySut = spy(mapper.getTypeFactory());
Assert.assertNull(spySut.getClassLoader());
Assert.assertArrayEquals(null,spySut._modifiers);
Class<?> clazz = spySut.findClass(aClassName);
@@ -124,24 +124,24 @@
verify(spySut).classForName(any(String.class));
}
-public static class AClass
-{
- private String _foo, _bar;
- protected final static Class<?> thisClass = new Object() {
- }.getClass().getEnclosingClass();
+ public static class AClass
+ {
+ private String _foo, _bar;
+ protected final static Class<?> thisClass = new Object() {
+ }.getClass().getEnclosingClass();
- public AClass() { }
- public AClass(String foo, String bar) {
- _foo = foo;
- _bar = bar;
- }
- public String getFoo() { return _foo; }
- public String getBar() { return _bar; }
+ public AClass() { }
+ public AClass(String foo, String bar) {
+ _foo = foo;
+ _bar = bar;
+ }
+ public String getFoo() { return _foo; }
+ public String getBar() { return _bar; }
- public void setFoo(String foo) { _foo = foo; }
- public void setBar(String bar) { _bar = bar; }
- public static String getStaticClassName() {
- return thisClass.getCanonicalName().replace("."+thisClass.getSimpleName(), "$"+thisClass.getSimpleName());
- }
-}
+ public void setFoo(String foo) { _foo = foo; }
+ public void setBar(String bar) { _bar = bar; }
+ public static String getStaticClassName() {
+ return thisClass.getCanonicalName().replace("."+thisClass.getSimpleName(), "$"+thisClass.getSimpleName());
+ }
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/ArrayBuildersTest.java b/src/test/java/com/fasterxml/jackson/databind/util/ArrayBuildersTest.java
index a788e28..bae0285 100644
--- a/src/test/java/com/fasterxml/jackson/databind/util/ArrayBuildersTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/util/ArrayBuildersTest.java
@@ -1,12 +1,22 @@
package com.fasterxml.jackson.databind.util;
+import java.util.Arrays;
+import java.util.HashSet;
+
import org.junit.Assert;
import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.util.ArrayBuilders.BooleanBuilder;
+import com.fasterxml.jackson.databind.util.ArrayBuilders.ByteBuilder;
+import com.fasterxml.jackson.databind.util.ArrayBuilders.DoubleBuilder;
+import com.fasterxml.jackson.databind.util.ArrayBuilders.FloatBuilder;
+import com.fasterxml.jackson.databind.util.ArrayBuilders.IntBuilder;
+import com.fasterxml.jackson.databind.util.ArrayBuilders.LongBuilder;
+import com.fasterxml.jackson.databind.util.ArrayBuilders.ShortBuilder;
public class ArrayBuildersTest extends BaseMapTest
{
- // Test for [Issue#157]
+ // [databind#157]
public void testInsertInListNoDup()
{
String [] arr = new String[]{"me", "you", "him"};
@@ -24,4 +34,57 @@
newarr = ArrayBuilders.insertInListNoDup(arr, "foobar");
Assert.assertArrayEquals(new String[]{"foobar", "me", "you", "him"}, newarr);
}
+
+ public void testBuilderAccess()
+ {
+ ArrayBuilders builders = new ArrayBuilders();
+
+ BooleanBuilder bb = builders.getBooleanBuilder();
+ assertNotNull(bb);
+ assertSame(bb, builders.getBooleanBuilder());
+
+ ByteBuilder b2 = builders.getByteBuilder();
+ assertNotNull(b2);
+ assertSame(b2, builders.getByteBuilder());
+
+ ShortBuilder sb = builders.getShortBuilder();
+ assertNotNull(sb);
+ assertSame(sb, builders.getShortBuilder());
+
+ IntBuilder ib = builders.getIntBuilder();
+ assertNotNull(ib);
+ assertSame(ib, builders.getIntBuilder());
+
+ LongBuilder lb = builders.getLongBuilder();
+ assertNotNull(lb);
+ assertSame(lb, builders.getLongBuilder());
+
+ FloatBuilder fb = builders.getFloatBuilder();
+ assertNotNull(fb);
+ assertSame(fb, builders.getFloatBuilder());
+
+ DoubleBuilder db = builders.getDoubleBuilder();
+ assertNotNull(db);
+ assertSame(db, builders.getDoubleBuilder());
+ }
+
+ public void testArrayComparator()
+ {
+ final int[] INT3 = new int[] { 3, 4, 5 };
+ Object comp = ArrayBuilders.getArrayComparator(INT3);
+ assertFalse(comp.equals(null));
+ assertTrue(comp.equals(INT3));
+ assertTrue(comp.equals(new int[] { 3, 4, 5 }));
+ assertFalse(comp.equals(new int[] { 5 }));
+ assertFalse(comp.equals(new int[] { 3, 4 }));
+ assertFalse(comp.equals(new int[] { 3, 5, 4 }));
+ assertFalse(comp.equals(new int[] { 3, 4, 5, 6 }));
+ }
+
+ public void testArraySet()
+ {
+ HashSet<String> set = ArrayBuilders.arrayToSet(new String[] { "foo", "bar" });
+ assertEquals(2, set.size());
+ assertEquals(new HashSet<String>(Arrays.asList("bar", "foo")), set);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/BeanUtilTest.java b/src/test/java/com/fasterxml/jackson/databind/util/BeanUtilTest.java
new file mode 100644
index 0000000..afd1732
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/BeanUtilTest.java
@@ -0,0 +1,147 @@
+package com.fasterxml.jackson.databind.util;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+public class BeanUtilTest extends BaseMapTest
+{
+ static class IsGetters {
+ public boolean isPrimitive() { return false; }
+ public Boolean isWrapper() { return false; }
+ public String isNotGetter() { return null; }
+ public boolean is() { return false; }
+ }
+
+ static class Getters {
+ public String getCallbacks() { return null; }
+ public String getMetaClass() { return null; }
+ public boolean get() { return false; }
+ }
+
+ static class Setters {
+ public void setFoo() { }
+ public void notSetter() { }
+ public void set() { }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testNameMangle()
+ {
+ assertEquals("foo", BeanUtil.legacyManglePropertyName("getFoo", 3));
+ assertEquals("foo", BeanUtil.stdManglePropertyName("getFoo", 3));
+
+ assertEquals("url", BeanUtil.legacyManglePropertyName("getURL", 3));
+ assertEquals("URL", BeanUtil.stdManglePropertyName("getURL", 3));
+ }
+
+ public void testGetDefaultValue()
+ {
+ TypeFactory tf = TypeFactory.defaultInstance();
+ // For collection/array/Map types, should give `NOT_EMPTY`:
+ assertEquals(JsonInclude.Include.NON_EMPTY,
+ BeanUtil.getDefaultValue(tf.constructType(Map.class)));
+ assertEquals(JsonInclude.Include.NON_EMPTY,
+ BeanUtil.getDefaultValue(tf.constructType(List.class)));
+ assertEquals(JsonInclude.Include.NON_EMPTY,
+ BeanUtil.getDefaultValue(tf.constructType(Object[].class)));
+ // as well as ReferenceTypes, String
+ assertEquals(JsonInclude.Include.NON_EMPTY,
+ BeanUtil.getDefaultValue(tf.constructType(AtomicReference.class)));
+ assertEquals("",
+ BeanUtil.getDefaultValue(tf.constructType(String.class)));
+ // primitive/wrappers have others
+ assertEquals(Integer.valueOf(0),
+ BeanUtil.getDefaultValue(tf.constructType(Integer.class)));
+
+
+ // but POJOs have no real default
+ assertNull(BeanUtil.getDefaultValue(tf.constructType(getClass())));
+ }
+
+ public void testIsGetter() throws Exception
+ {
+ _testIsGetter("isPrimitive", "primitive");
+ _testIsGetter("isWrapper", "wrapper");
+ _testIsGetter("isNotGetter", null);
+ _testIsGetter("is", null);
+ }
+
+ public void testOkNameForGetter() throws Exception
+ {
+ // mostly chosen to exercise groovy exclusion
+ _testOkNameForGetter("getCallbacks", "callbacks");
+ _testOkNameForGetter("getMetaClass", "metaClass");
+ _testOkNameForGetter("get", null);
+ }
+
+ public void testOkNameForSetter() throws Exception
+ {
+ _testOkNameForSetter("setFoo", "foo");
+ _testOkNameForSetter("notSetter", null);
+ _testOkNameForSetter("set", null);
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void _testIsGetter(String name, String expName) throws Exception {
+ _testIsGetter(name, expName, true);
+ _testIsGetter(name, expName, false);
+ }
+
+ private void _testIsGetter(String name, String expName, boolean useStd) throws Exception
+ {
+ AnnotatedMethod m = _method(IsGetters.class, name);
+ if (expName == null) {
+ assertNull(BeanUtil.okNameForIsGetter(m, name, useStd));
+ } else {
+ assertEquals(expName, BeanUtil.okNameForIsGetter(m, name, useStd));
+ }
+ }
+
+ private void _testOkNameForGetter(String name, String expName) throws Exception {
+ _testOkNameForGetter(name, expName, true);
+ _testOkNameForGetter(name, expName, false);
+ }
+
+ private void _testOkNameForGetter(String name, String expName, boolean useStd) throws Exception {
+ AnnotatedMethod m = _method(Getters.class, name);
+ if (expName == null) {
+ assertNull(BeanUtil.okNameForGetter(m, useStd));
+ } else {
+ assertEquals(expName, BeanUtil.okNameForGetter(m, useStd));
+ }
+ }
+
+ private void _testOkNameForSetter(String name, String expName) throws Exception {
+ _testOkNameForSetter(name, expName, true);
+ _testOkNameForSetter(name, expName, false);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void _testOkNameForSetter(String name, String expName, boolean useStd) throws Exception {
+ AnnotatedMethod m = _method(Setters.class, name);
+ if (expName == null) {
+ assertNull(BeanUtil.okNameForSetter(m, useStd));
+ } else {
+ assertEquals(expName, BeanUtil.okNameForSetter(m, useStd));
+ }
+ }
+
+ private AnnotatedMethod _method(Class<?> cls, String name, Class<?>...parameterTypes) throws Exception {
+ return new AnnotatedMethod(null, cls.getMethod(name, parameterTypes), null, null);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/ByteBufferUtilsTest.java b/src/test/java/com/fasterxml/jackson/databind/util/ByteBufferUtilsTest.java
new file mode 100644
index 0000000..da66aa4
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/ByteBufferUtilsTest.java
@@ -0,0 +1,28 @@
+package com.fasterxml.jackson.databind.util;
+
+import java.nio.ByteBuffer;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+
+public class ByteBufferUtilsTest extends BaseMapTest
+{
+ public void testByteBufferInput() throws Exception {
+ byte[] input = new byte[] { 1, 2, 3 };
+ ByteBufferBackedInputStream wrapped = new ByteBufferBackedInputStream(ByteBuffer.wrap(input));
+ assertEquals(3, wrapped.available());
+ assertEquals(1, wrapped.read());
+ byte[] buffer = new byte[10];
+ assertEquals(2, wrapped.read(buffer, 0, 5));
+ wrapped.close();
+ }
+
+ public void testByteBufferOutput() throws Exception {
+ ByteBuffer b = ByteBuffer.wrap(new byte[10]);
+ ByteBufferBackedOutputStream wrappedOut = new ByteBufferBackedOutputStream(b);
+ wrappedOut.write(1);
+ wrappedOut.write(new byte[] { 2, 3 });
+ assertEquals(3, b.position());
+ assertEquals(7, b.remaining());
+ wrappedOut.close();
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/TestClassUtil.java b/src/test/java/com/fasterxml/jackson/databind/util/ClassUtilTest.java
similarity index 81%
rename from src/test/java/com/fasterxml/jackson/databind/util/TestClassUtil.java
rename to src/test/java/com/fasterxml/jackson/databind/util/ClassUtilTest.java
index b02f6ac..61faf3b 100644
--- a/src/test/java/com/fasterxml/jackson/databind/util/TestClassUtil.java
+++ b/src/test/java/com/fasterxml/jackson/databind/util/ClassUtilTest.java
@@ -5,8 +5,7 @@
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.util.ClassUtil;
-public class TestClassUtil
- extends BaseMapTest
+public class ClassUtilTest extends BaseMapTest
{
/*
/**********************************************************
@@ -91,13 +90,6 @@
assertSame(e, e2);
}
- try {
- ClassUtil.throwRootCause(e);
- fail("Shouldn't get this far");
- } catch (Exception eAct) {
- assertSame(e, eAct);
- }
-
Error err = new Error();
try {
ClassUtil.throwAsIAE(err);
@@ -137,7 +129,7 @@
}
}
- public void testPrimiteDefaultValue()
+ public void testPrimitiveDefaultValue()
{
assertEquals(Integer.valueOf(0), ClassUtil.defaultValue(Integer.TYPE));
assertEquals(Long.valueOf(0L), ClassUtil.defaultValue(Long.TYPE));
@@ -148,6 +140,8 @@
assertEquals(Double.valueOf(0.0), ClassUtil.defaultValue(Double.TYPE));
assertEquals(Float.valueOf(0.0f), ClassUtil.defaultValue(Float.TYPE));
+ assertEquals(Boolean.FALSE, ClassUtil.defaultValue(Boolean.TYPE));
+
try {
ClassUtil.defaultValue(String.class);
} catch (IllegalArgumentException e) {
@@ -155,34 +149,54 @@
}
}
- public void testPrimiteWrapperType()
+ public void testPrimitiveWrapperType()
{
+ assertEquals(Byte.class, ClassUtil.wrapperType(Byte.TYPE));
+ assertEquals(Short.class, ClassUtil.wrapperType(Short.TYPE));
+ assertEquals(Character.class, ClassUtil.wrapperType(Character.TYPE));
assertEquals(Integer.class, ClassUtil.wrapperType(Integer.TYPE));
assertEquals(Long.class, ClassUtil.wrapperType(Long.TYPE));
- assertEquals(Character.class, ClassUtil.wrapperType(Character.TYPE));
- assertEquals(Short.class, ClassUtil.wrapperType(Short.TYPE));
- assertEquals(Byte.class, ClassUtil.wrapperType(Byte.TYPE));
assertEquals(Double.class, ClassUtil.wrapperType(Double.TYPE));
assertEquals(Float.class, ClassUtil.wrapperType(Float.TYPE));
+ assertEquals(Boolean.class, ClassUtil.wrapperType(Boolean.TYPE));
+
try {
ClassUtil.wrapperType(String.class);
+ fail("Should not pass");
} catch (IllegalArgumentException e) {
verifyException(e, "String is not a primitive type");
}
}
+ public void testWrapperToPrimitiveType()
+ {
+ assertEquals(Integer.TYPE, ClassUtil.primitiveType(Integer.class));
+ assertEquals(Long.TYPE, ClassUtil.primitiveType(Long.class));
+ assertEquals(Character.TYPE, ClassUtil.primitiveType(Character.class));
+ assertEquals(Short.TYPE, ClassUtil.primitiveType(Short.class));
+ assertEquals(Byte.TYPE, ClassUtil.primitiveType(Byte.class));
+ assertEquals(Float.TYPE, ClassUtil.primitiveType(Float.class));
+ assertEquals(Double.TYPE, ClassUtil.primitiveType(Double.class));
+ assertEquals(Boolean.TYPE, ClassUtil.primitiveType(Boolean.class));
+
+ assertNull(ClassUtil.primitiveType(String.class));
+ }
+
public void testFindEnumType()
{
assertEquals(TestEnum.class, ClassUtil.findEnumType(TestEnum.A));
+ // different codepaths for empty and non-empty EnumSets...
assertEquals(TestEnum.class, ClassUtil.findEnumType(EnumSet.allOf(TestEnum.class)));
+ assertEquals(TestEnum.class, ClassUtil.findEnumType(EnumSet.noneOf(TestEnum.class)));
+
assertEquals(TestEnum.class, ClassUtil.findEnumType(new EnumMap<TestEnum,Integer>(TestEnum.class)));
}
public void testDescs()
{
- final String exp = String.class.getName();
+ final String exp = "`java.lang.String`";
assertEquals(exp, ClassUtil.getClassDescription("foo"));
assertEquals(exp, ClassUtil.getClassDescription(String.class));
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/CompactStringObjectMapTest.java b/src/test/java/com/fasterxml/jackson/databind/util/CompactStringObjectMapTest.java
new file mode 100644
index 0000000..325d2f2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/CompactStringObjectMapTest.java
@@ -0,0 +1,28 @@
+package com.fasterxml.jackson.databind.util;
+
+import java.util.*;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+
+public class CompactStringObjectMapTest extends BaseMapTest
+{
+ public void testBig()
+ {
+ Map<String,String> all = new LinkedHashMap<>();
+ for (int i = 0; i < 1000; ++i) {
+ String key = "key"+i;
+ all.put(key, key);
+ }
+ CompactStringObjectMap map = CompactStringObjectMap.construct(all);
+ assertEquals(1000, map.keys().size());
+
+ for (String key : all.keySet()) {
+ assertEquals(key, map.find(key));
+ }
+
+ // and then bogus empty keys
+ assertNull(map.find("key1000"));
+ assertNull(map.find("keyXXX"));
+ assertNull(map.find(""));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/EnumValuesTest.java b/src/test/java/com/fasterxml/jackson/databind/util/EnumValuesTest.java
new file mode 100644
index 0000000..ccc3ea2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/EnumValuesTest.java
@@ -0,0 +1,66 @@
+package com.fasterxml.jackson.databind.util;
+
+import java.util.List;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public class EnumValuesTest extends BaseMapTest
+{
+ enum ABC {
+ A("A"),
+ B("b"),
+ C("C");
+
+ private final String desc;
+
+ private ABC(String d) { desc = d; }
+
+ @Override
+ public String toString() { return desc; }
+ }
+
+ final ObjectMapper MAPPER = new ObjectMapper();
+
+ @SuppressWarnings("unchecked")
+ public void testConstructFromName() {
+ SerializationConfig cfg = MAPPER.getSerializationConfig()
+ .without(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+ Class<Enum<?>> enumClass = (Class<Enum<?>>)(Class<?>) ABC.class;
+ EnumValues values = EnumValues.construct(cfg, enumClass);
+ assertEquals("A", values.serializedValueFor(ABC.A).toString());
+ assertEquals("B", values.serializedValueFor(ABC.B).toString());
+ assertEquals("C", values.serializedValueFor(ABC.C).toString());
+ assertEquals(3, values.values().size());
+ assertEquals(3, values.internalMap().size());
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testConstructWithToString() {
+ SerializationConfig cfg = MAPPER.getSerializationConfig()
+ .with(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+ Class<Enum<?>> enumClass = (Class<Enum<?>>)(Class<?>) ABC.class;
+ EnumValues values = EnumValues.construct(cfg, enumClass);
+ assertEquals("A", values.serializedValueFor(ABC.A).toString());
+ assertEquals("b", values.serializedValueFor(ABC.B).toString());
+ assertEquals("C", values.serializedValueFor(ABC.C).toString());
+ assertEquals(3, values.values().size());
+ assertEquals(3, values.internalMap().size());
+ }
+
+ public void testEnumResolver()
+ {
+ EnumResolver enumRes = EnumResolver.constructUnsafeUsingToString(ABC.class, null);
+ assertEquals(ABC.B, enumRes.getEnum(1));
+ assertNull(enumRes.getEnum(-1));
+ assertNull(enumRes.getEnum(3));
+ assertEquals(2, enumRes.lastValidIndex());
+ List<Enum<?>> enums = enumRes.getEnums();
+ assertEquals(3, enums.size());
+ assertEquals(ABC.A, enums.get(0));
+ assertEquals(ABC.B, enums.get(1));
+ assertEquals(ABC.C, enums.get(2));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/ISO8601DateFormatTest.java b/src/test/java/com/fasterxml/jackson/databind/util/ISO8601DateFormatTest.java
index 1264292..20e9412 100644
--- a/src/test/java/com/fasterxml/jackson/databind/util/ISO8601DateFormatTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/util/ISO8601DateFormatTest.java
@@ -6,9 +6,7 @@
import com.fasterxml.jackson.databind.BaseMapTest;
-/**
- * @see ISO8601DateFormat
- */
+@SuppressWarnings("deprecation")
public class ISO8601DateFormatTest extends BaseMapTest
{
private ISO8601DateFormat df;
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java b/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java
index 6680048..5ce6751 100644
--- a/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java
@@ -10,10 +10,9 @@
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
-/**
- * @see ISO8601Utils
- */
-public class ISO8601UtilsTest extends BaseMapTest {
+@SuppressWarnings("deprecation")
+public class ISO8601UtilsTest extends BaseMapTest
+{
private Date date;
private Date dateWithoutTime;
private Date dateZeroMillis;
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/JsonParserSequenceTest.java b/src/test/java/com/fasterxml/jackson/databind/util/JsonParserSequenceTest.java
new file mode 100644
index 0000000..b7b6228
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/JsonParserSequenceTest.java
@@ -0,0 +1,45 @@
+package com.fasterxml.jackson.databind.util;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.util.JsonParserSequence;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JsonParserSequenceTest extends BaseMapTest {
+
+ private final ObjectMapper MAPPER = objectMapper();
+
+ /**
+ * Verifies fix for [core#372]
+ */
+ @SuppressWarnings("resource")
+ public void testJsonParserSequenceOverridesSkipChildren() throws Exception
+ {
+ // Create parser from TokenBuffer containing an incomplete JSON object
+ TokenBuffer buf1 = new TokenBuffer(MAPPER, false);
+ buf1.writeStartObject();
+ buf1.writeFieldName("foo");
+ buf1.writeStartObject();
+ JsonParser parser1 = buf1.asParser();
+
+ // Create parser from second TokenBuffer that completes the object started by the first buffer
+ TokenBuffer buf2 = new TokenBuffer(MAPPER, false);
+ buf2.writeEndObject();
+ buf2.writeEndObject();
+ JsonParser parser2 = buf2.asParser();
+
+ // Create sequence of both parsers and verify tokens
+ JsonParser parserSequence = JsonParserSequence.createFlattened(false, parser1, parser2);
+ assertToken(JsonToken.START_OBJECT, parserSequence.nextToken());
+ assertToken(JsonToken.FIELD_NAME, parserSequence.nextToken());
+ assertToken(JsonToken.START_OBJECT, parserSequence.nextToken());
+
+ // Skip children of current token. JsonParserSequence's overridden version should switch to the next parser
+ // in the sequence
+ parserSequence.skipChildren();
+
+ // Verify last token
+ assertToken(JsonToken.END_OBJECT, parserSequence.nextToken());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/NameTransformerTest.java b/src/test/java/com/fasterxml/jackson/databind/util/NameTransformerTest.java
new file mode 100644
index 0000000..648c8a9
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/NameTransformerTest.java
@@ -0,0 +1,23 @@
+package com.fasterxml.jackson.databind.util;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+
+public class NameTransformerTest extends BaseMapTest
+{
+ public void testSimpleTransformer() throws Exception
+ {
+ NameTransformer xfer;
+
+ xfer = NameTransformer.simpleTransformer("a", null);
+ assertEquals("aFoo", xfer.transform("Foo"));
+ assertEquals("Foo", xfer.reverse("aFoo"));
+
+ xfer = NameTransformer.simpleTransformer(null, "++");
+ assertEquals("foo++", xfer.transform("foo"));
+ assertEquals("foo", xfer.reverse("foo++"));
+
+ xfer = NameTransformer.simpleTransformer("(", ")");
+ assertEquals("(foo)", xfer.transform("foo"));
+ assertEquals("foo", xfer.reverse("(foo)"));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/RawValueTest.java b/src/test/java/com/fasterxml/jackson/databind/util/RawValueTest.java
new file mode 100644
index 0000000..4d652b0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/RawValueTest.java
@@ -0,0 +1,24 @@
+package com.fasterxml.jackson.databind.util;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.JsonSerializable;
+
+public class RawValueTest extends BaseMapTest
+{
+ public void testEquality()
+ {
+ RawValue raw1 = new RawValue("foo");
+ RawValue raw1b = new RawValue("foo");
+ RawValue raw2 = new RawValue("bar");
+
+ assertTrue(raw1.equals(raw1));
+ assertTrue(raw1.equals(raw1b));
+
+ assertFalse(raw1.equals(raw2));
+ assertFalse(raw1.equals(null));
+
+ assertFalse(new RawValue((JsonSerializable) null).equals(raw1));
+
+ assertNotNull(raw1.toString());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/TestStdDateFormat.java b/src/test/java/com/fasterxml/jackson/databind/util/TestStdDateFormat.java
new file mode 100644
index 0000000..ec99545
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/util/TestStdDateFormat.java
@@ -0,0 +1,142 @@
+package com.fasterxml.jackson.databind.util;
+
+import java.text.ParseException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.util.StdDateFormat;
+
+public class TestStdDateFormat
+ extends BaseMapTest
+{
+ @SuppressWarnings("deprecation")
+ public void testFactories() {
+ TimeZone tz = TimeZone.getTimeZone("GMT");
+ Locale loc = Locale.US;
+ assertNotNull(StdDateFormat.getISO8601Format(tz, loc));
+ assertNotNull(StdDateFormat.getRFC1123Format(tz, loc));
+ }
+
+ // [databind#803]
+ public void testLenientDefaults() throws Exception
+ {
+ StdDateFormat f = StdDateFormat.instance;
+
+ // default should be lenient
+ assertTrue(f.isLenient());
+
+ StdDateFormat f2 = f.clone();
+ assertTrue(f2.isLenient());
+
+ f2.setLenient(false);
+ assertFalse(f2.isLenient());
+
+ f2.setLenient(true);
+ assertTrue(f2.isLenient());
+
+ // and for testing, finally, leave as non-lenient
+ f2.setLenient(false);
+ assertFalse(f2.isLenient());
+ StdDateFormat f3 = f2.clone();
+ assertFalse(f3.isLenient());
+ }
+
+ public void testISO8601RegexpDateOnly() throws Exception
+ {
+ Pattern p = StdDateFormat.PATTERN_PLAIN;
+ Matcher m = p.matcher("1997-07-16");
+ assertTrue(m.matches());
+ // no matching groups...
+ }
+
+ public void testISO8601RegexpFull() throws Exception
+ {
+ /*
+ String PATTERN_PLAIN_STR = "\\d\\d\\d\\d[-]\\d\\d[-]\\d\\d";
+ Pattern PATTERN_ISO8601 = Pattern.compile(PATTERN_PLAIN_STR
+ +"[T]\\d\\d[:]\\d\\d(?:[:]\\d\\d)?" // hours, minutes, optional seconds
+ +"(\\.\\d+)?" // optional second fractions
+ +"(Z|[+-]\\d\\d(?:[:]?\\d\\d)?)?" // optional timeoffset/Z
+ );
+ final Pattern p = PATTERN_ISO8601;
+ */
+ final Pattern p = StdDateFormat.PATTERN_ISO8601;
+ Matcher m;
+
+ // First simple full representation (except no millisecs)
+ m = p.matcher("1997-07-16T19:20:00+01:00");
+ assertTrue(m.matches());
+ assertEquals(2, m.groupCount());
+ assertNull(m.group(1)); // no match (why not empty String)
+ assertEquals("+01:00", m.group(2));
+
+ // Then with 'Z' instead
+ m = p.matcher("1997-07-16T19:20:00Z");
+ assertTrue(m.matches());
+ assertNull(m.group(1));
+ assertEquals("Z", m.group(2));
+
+ // Then drop seconds too
+ m = p.matcher("1997-07-16T19:20+01:00");
+ assertTrue(m.matches());
+ assertNull(m.group(1));
+ assertEquals("+01:00", m.group(2));
+
+ // Full with milliseconds:
+ m = p.matcher("1997-07-16T19:20:00.2+03:00");
+ assertTrue(m.matches());
+ assertEquals(2, m.groupCount());
+ assertEquals(".2", m.group(1));
+ assertEquals("+03:00", m.group(2));
+
+ m = p.matcher("1972-12-28T00:00:00.01-0300");
+ assertTrue(m.matches());
+ assertEquals(".01", m.group(1));
+ assertEquals("-0300", m.group(2));
+
+ m = p.matcher("1972-12-28T00:00:00.400+00");
+ assertTrue(m.matches());
+ assertEquals(".400", m.group(1));
+ assertEquals("+00", m.group(2));
+
+ // and then drop time offset AND seconds
+ m = p.matcher("1972-12-28T04:15");
+ assertTrue(m.matches());
+ assertNull(m.group(1));
+ assertNull(m.group(2));
+ }
+
+ public void testLenientParsing() throws Exception
+ {
+ StdDateFormat f = StdDateFormat.instance.clone();
+ f.setLenient(false);
+
+ // first, legal dates are... legal
+ Date dt = f.parse("2015-11-30");
+ assertNotNull(dt);
+
+ // but as importantly, when not lenient, do not allow
+ try {
+ f.parse("2015-11-32");
+ fail("Should not pass");
+ } catch (ParseException e) {
+ verifyException(e, "Cannot parse date");
+ }
+
+ // ... yet, with lenient, do allow
+ f.setLenient(true);
+ dt = f.parse("2015-11-32");
+ assertNotNull(dt);
+ }
+
+ public void testInvalid() {
+ StdDateFormat std = new StdDateFormat();
+ try {
+ std.parse("foobar");
+ } catch (java.text.ParseException e) {
+ verifyException(e, "Cannot parse");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java b/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java
index 477301e..e785be7 100644
--- a/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java
+++ b/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java
@@ -1,30 +1,61 @@
package com.fasterxml.jackson.databind.util;
import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.UUID;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.JsonParser.NumberType;
+import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.core.util.JsonParserSequence;
-import com.fasterxml.jackson.databind.BaseMapTest;
-import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.fasterxml.jackson.databind.*;
public class TestTokenBuffer extends BaseMapTest
{
private final ObjectMapper MAPPER = objectMapper();
-
+
+ static class Base1730 { }
+
+ static class Sub1730 extends Base1730 { }
+
/*
/**********************************************************
/* Basic TokenBuffer tests
/**********************************************************
*/
-
+
+ public void testBasicConfig() throws IOException
+ {
+ TokenBuffer buf;
+
+ buf = new TokenBuffer(MAPPER, false);
+ assertEquals(MAPPER.version(), buf.version());
+ assertSame(MAPPER, buf.getCodec());
+ assertNotNull(buf.getOutputContext());
+ assertFalse(buf.isClosed());
+
+ buf.setCodec(null);
+ assertNull(buf.getCodec());
+
+ assertFalse(buf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+ buf.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
+ assertTrue(buf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+ buf.disable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
+ assertFalse(buf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
+
+ buf.close();
+ assertTrue(buf.isClosed());
+ }
+
/**
* Test writing of individual simple values
*/
public void testSimpleWrites() throws IOException
{
TokenBuffer buf = new TokenBuffer(null, false); // no ObjectCodec
-
+
// First, with empty buffer
JsonParser p = buf.asParser();
assertNull(p.getCurrentToken());
@@ -34,7 +65,6 @@
// Then with simple text
buf.writeString("abc");
- // First, simple text
p = buf.asParser();
assertNull(p.getCurrentToken());
assertToken(JsonToken.VALUE_STRING, p.nextToken());
@@ -54,6 +84,100 @@
buf.close();
}
+ // For 2.9, explicit "isNaN" check
+ public void testSimpleNumberWrites() throws IOException
+ {
+ TokenBuffer buf = new TokenBuffer(null, false);
+
+ double[] values1 = new double[] {
+ 0.25, Double.NaN, -2.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY
+ };
+ float[] values2 = new float[] {
+ Float.NEGATIVE_INFINITY,
+ 0.25f,
+ Float.POSITIVE_INFINITY
+ };
+
+ for (double v : values1) {
+ buf.writeNumber(v);
+ }
+ for (float v : values2) {
+ buf.writeNumber(v);
+ }
+
+ JsonParser p = buf.asParser();
+ assertNull(p.getCurrentToken());
+
+ for (double v : values1) {
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ double actual = p.getDoubleValue();
+ boolean expNan = Double.isNaN(v) || Double.isInfinite(v);
+ assertEquals(expNan, p.isNaN());
+ assertEquals(0, Double.compare(v, actual));
+ }
+ for (float v : values2) {
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ float actual = p.getFloatValue();
+ boolean expNan = Float.isNaN(v) || Float.isInfinite(v);
+ assertEquals(expNan, p.isNaN());
+ assertEquals(0, Float.compare(v, actual));
+ }
+ p.close();
+ buf.close();
+ }
+
+ // [databind#1729]
+ public void testNumberOverflowInt() throws IOException
+ {
+ try (TokenBuffer buf = new TokenBuffer(null, false)) {
+ long big = 1L + Integer.MAX_VALUE;
+ buf.writeNumber(big);
+ try (JsonParser p = buf.asParser()) {
+ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals(NumberType.LONG, p.getNumberType());
+ try {
+ p.getIntValue();
+ fail("Expected failure for `int` overflow");
+ } catch (JsonParseException e) {
+ verifyException(e, "Numeric value ("+big+") out of range of int");
+ }
+ }
+ }
+ // and ditto for coercion.
+ try (TokenBuffer buf = new TokenBuffer(null, false)) {
+ long big = 1L + Integer.MAX_VALUE;
+ buf.writeNumber(String.valueOf(big));
+ try (JsonParser p = buf.asParser()) {
+ // NOTE: oddity of buffering, no inspection of "real" type if given String...
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ try {
+ p.getIntValue();
+ fail("Expected failure for `int` overflow");
+ } catch (JsonParseException e) {
+ verifyException(e, "Numeric value ("+big+") out of range of int");
+ }
+ }
+ }
+ }
+
+ public void testNumberOverflowLong() throws IOException
+ {
+ try (TokenBuffer buf = new TokenBuffer(null, false)) {
+ BigInteger big = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
+ buf.writeNumber(big);
+ try (JsonParser p = buf.asParser()) {
+ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals(NumberType.BIG_INTEGER, p.getNumberType());
+ try {
+ p.getLongValue();
+ fail("Expected failure for `long` overflow");
+ } catch (JsonParseException e) {
+ verifyException(e, "Numeric value ("+big+") out of range of long");
+ }
+ }
+ }
+ }
+
public void testParentContext() throws IOException
{
TokenBuffer buf = new TokenBuffer(null, false); // no ObjectCodec
@@ -200,6 +324,11 @@
verifyJsonSpecSampleDoc(tb.asParser(), true);
tb.close();
p.close();
+
+
+ // 19-Oct-2016, tatu: Just for fun, trigger `toString()` for code coverage
+ String desc = tb.toString();
+ assertNotNull(desc);
}
public void testAppend() throws IOException
@@ -382,6 +511,39 @@
buf.close();
}
+ public void testBasicSerialize() throws IOException
+ {
+ TokenBuffer buf;
+
+ // let's see how empty works...
+ buf = new TokenBuffer(MAPPER, false);
+ assertEquals("", MAPPER.writeValueAsString(buf));
+ buf.close();
+
+ buf = new TokenBuffer(MAPPER, false);
+ buf.writeStartArray();
+ buf.writeBoolean(true);
+ buf.writeBoolean(false);
+ long l = 1L + Integer.MAX_VALUE;
+ buf.writeNumber(l);
+ buf.writeNumber((short) 4);
+ buf.writeNumber(0.5);
+ buf.writeEndArray();
+ assertEquals(aposToQuotes("[true,false,"+l+",4,0.5]"), MAPPER.writeValueAsString(buf));
+ buf.close();
+
+ buf = new TokenBuffer(MAPPER, false);
+ buf.writeStartObject();
+ buf.writeFieldName(new SerializedString("foo"));
+ buf.writeNull();
+ buf.writeFieldName("bar");
+ buf.writeNumber(BigInteger.valueOf(123));
+ buf.writeFieldName("dec");
+ buf.writeNumber(BigDecimal.valueOf(5).movePointLeft(2));
+ assertEquals(aposToQuotes("{'foo':null,'bar':123,'dec':0.05}"), MAPPER.writeValueAsString(buf));
+ buf.close();
+ }
+
/*
/**********************************************************
/* Tests to verify interaction of TokenBuffer and JsonParserSequence
@@ -468,7 +630,7 @@
buf2.close();
buf3.close();
buf4.close();
- }
+ }
// [databind#743]
public void testRawValues() throws Exception
@@ -487,4 +649,20 @@
// then verify it would be serialized just fine
assertEquals(RAW, MAPPER.writeValueAsString(buf));
}
+
+ // [databind#1730]
+ public void testEmbeddedObjectCoerceCheck() throws Exception
+ {
+ TokenBuffer buf = new TokenBuffer(null, false);
+ Object inputPojo = new Sub1730();
+ buf.writeEmbeddedObject(inputPojo);
+
+ // first: raw value won't be transformed in any way:
+ JsonParser p = buf.asParser();
+ Base1730 out = MAPPER.readValue(p, Base1730.class);
+
+ assertSame(inputPojo, out);
+ p.close();
+ buf.close();
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/views/DefaultViewTest.java b/src/test/java/com/fasterxml/jackson/databind/views/DefaultViewTest.java
new file mode 100644
index 0000000..34e8c08
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/views/DefaultViewTest.java
@@ -0,0 +1,71 @@
+package com.fasterxml.jackson.databind.views;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+
+// for [databind#507], supporting default views
+public class DefaultViewTest extends BaseMapTest
+{
+ // Classes that represent views
+ static class ViewA { }
+ static class ViewAA extends ViewA { }
+ static class ViewB { }
+ static class ViewBB extends ViewB { }
+
+ @JsonView(ViewA.class)
+ @JsonPropertyOrder({ "a", "b" })
+ static class Defaulting {
+ public int a = 3;
+
+ @JsonView(ViewB.class)
+ public int b = 5;
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testDeserialization() throws IOException
+ {
+ final String JSON = aposToQuotes("{'a':1,'b':2}");
+
+ // first: no views:
+ Defaulting result = MAPPER.readerFor(Defaulting.class)
+ .readValue(JSON);
+ assertEquals(result.a, 1);
+ assertEquals(result.b, 2);
+
+ // Then views; first A, then B(B)
+ result = MAPPER.readerFor(Defaulting.class)
+ .withView(ViewA.class)
+ .readValue(JSON);
+ assertEquals(result.a, 1);
+ assertEquals(result.b, 5);
+
+ result = MAPPER.readerFor(Defaulting.class)
+ .withView(ViewBB.class)
+ .readValue(JSON);
+ assertEquals(result.a, 3);
+ assertEquals(result.b, 2);
+ }
+
+ public void testSerialization() throws IOException
+ {
+ assertEquals(aposToQuotes("{'a':3,'b':5}"),
+ MAPPER.writeValueAsString(new Defaulting()));
+
+ assertEquals(aposToQuotes("{'a':3}"),
+ MAPPER.writerWithView(ViewA.class)
+ .writeValueAsString(new Defaulting()));
+ assertEquals(aposToQuotes("{'b':5}"),
+ MAPPER.writerWithView(ViewB.class)
+ .writeValueAsString(new Defaulting()));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/views/TestViewDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/views/TestViewDeserialization.java
index 5c28728..fd81d13 100644
--- a/src/test/java/com/fasterxml/jackson/databind/views/TestViewDeserialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/views/TestViewDeserialization.java
@@ -1,17 +1,13 @@
package com.fasterxml.jackson.databind.views;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.*;
public class TestViewDeserialization extends BaseMapTest
{
- /*
- /**********************************************************
- /* Helper types
- /**********************************************************
- */
-
// Classes that represent views
static class ViewA { }
static class ViewAA extends ViewA { }
@@ -40,6 +36,23 @@
public int b;
}
+ static class ViewsAndCreatorBean
+ {
+ @JsonView(ViewA.class)
+ public int a;
+
+ @JsonView(ViewB.class)
+ public int b;
+
+ @JsonCreator
+ public ViewsAndCreatorBean(@JsonProperty("a") int a,
+ @JsonProperty("b") int b)
+ {
+ this.a = a;
+ this.b = b;
+ }
+ }
+
/*
/************************************************************************
/* Tests
@@ -101,4 +114,28 @@
assertEquals(0, bean.a);
assertEquals(2, bean.b);
}
+
+ public void testWithCreatorAndViews() throws Exception
+ {
+ ViewsAndCreatorBean result;
+
+ result = mapper.readerFor(ViewsAndCreatorBean.class)
+ .withView(ViewA.class)
+ .readValue(aposToQuotes("{'a':1,'b':2}"));
+ assertEquals(1, result.a);
+ assertEquals(0, result.b);
+
+ result = mapper.readerFor(ViewsAndCreatorBean.class)
+ .withView(ViewB.class)
+ .readValue(aposToQuotes("{'a':1,'b':2}"));
+ assertEquals(0, result.a);
+ assertEquals(2, result.b);
+
+ // and actually... fine to skip incompatible stuff too
+ result = mapper.readerFor(ViewsAndCreatorBean.class)
+ .withView(ViewB.class)
+ .readValue(aposToQuotes("{'a':[ 1, 23, { } ],'b':2}"));
+ assertEquals(0, result.a);
+ assertEquals(2, result.b);
+ }
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/views/TestViewSerialization.java b/src/test/java/com/fasterxml/jackson/databind/views/TestViewSerialization.java
index 564eb41..79dfee0 100644
--- a/src/test/java/com/fasterxml/jackson/databind/views/TestViewSerialization.java
+++ b/src/test/java/com/fasterxml/jackson/databind/views/TestViewSerialization.java
@@ -14,12 +14,6 @@
public class TestViewSerialization
extends BaseMapTest
{
- /*
- /**********************************************************
- /* Helper types
- /**********************************************************
- */
-
// Classes that represent views
static class ViewA { }
static class ViewAA extends ViewA { }
@@ -68,58 +62,63 @@
public String value = "x";
}
- // [JACKSON-868]
public static class WebView { }
public static class OtherView { }
public static class Foo {
@JsonView(WebView.class)
public int getFoo() { return 3; }
}
-
+
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
-
+
+ private final ObjectMapper MAPPER = objectMapper();
+
@SuppressWarnings("unchecked")
public void testSimple() throws IOException
{
StringWriter sw = new StringWriter();
- ObjectMapper mapper = new ObjectMapper();
// Ok, first, using no view whatsoever; all 3
Bean bean = new Bean();
- Map<String,Object> map = writeAndMap(mapper, bean);
+ Map<String,Object> map = writeAndMap(MAPPER, bean);
assertEquals(3, map.size());
// Then with "ViewA", just one property
sw = new StringWriter();
- mapper.writerWithView(ViewA.class).writeValue(sw, bean);
- map = mapper.readValue(sw.toString(), Map.class);
+ MAPPER.writerWithView(ViewA.class).writeValue(sw, bean);
+ map = MAPPER.readValue(sw.toString(), Map.class);
assertEquals(1, map.size());
assertEquals("1", map.get("a"));
// "ViewAA", 2 properties
sw = new StringWriter();
- mapper.writerWithView(ViewAA.class).writeValue(sw, bean);
- map = mapper.readValue(sw.toString(), Map.class);
+ MAPPER.writerWithView(ViewAA.class).writeValue(sw, bean);
+ map = MAPPER.readValue(sw.toString(), Map.class);
assertEquals(2, map.size());
assertEquals("1", map.get("a"));
assertEquals("2", map.get("aa"));
// "ViewB", 2 prop2
- String json = mapper.writerWithView(ViewB.class).writeValueAsString(bean);
- map = mapper.readValue(json, Map.class);
+ String json = MAPPER.writerWithView(ViewB.class).writeValueAsString(bean);
+ map = MAPPER.readValue(json, Map.class);
assertEquals(2, map.size());
assertEquals("2", map.get("aa"));
assertEquals("3", map.get("b"));
// and "ViewBB", 2 as well
- json = mapper.writerWithView(ViewBB.class).writeValueAsString(bean);
- map = mapper.readValue(json, Map.class);
+ json = MAPPER.writerWithView(ViewBB.class).writeValueAsString(bean);
+ map = MAPPER.readValue(json, Map.class);
assertEquals(2, map.size());
assertEquals("2", map.get("aa"));
assertEquals("3", map.get("b"));
+
+ // and finally, without view.
+ json = MAPPER.writerWithView(null).writeValueAsString(bean);
+ map = MAPPER.readValue(json, Map.class);
+ assertEquals(3, map.size());
}
/**
@@ -132,25 +131,31 @@
public void testDefaultExclusion() throws IOException
{
MixedBean bean = new MixedBean();
- StringWriter sw = new StringWriter();
- ObjectMapper mapper = new ObjectMapper();
// default setting: both fields will get included
- mapper.writerWithView(ViewA.class).writeValue(sw, bean);
- Map<String,Object> map = mapper.readValue(sw.toString(), Map.class);
+ String json = MAPPER.writerWithView(ViewA.class).writeValueAsString(bean);
+ Map<String,Object> map = MAPPER.readValue(json, Map.class);
assertEquals(2, map.size());
assertEquals("1", map.get("a"));
assertEquals("2", map.get("b"));
// but can also change (but not necessarily on the fly...)
- mapper = new ObjectMapper();
+ ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
+
// with this setting, only explicit inclusions count:
- String json = mapper.writerWithView(ViewA.class).writeValueAsString(bean);
+ json = mapper.writerWithView(ViewA.class).writeValueAsString(bean);
map = mapper.readValue(json, Map.class);
assertEquals(1, map.size());
assertEquals("1", map.get("a"));
assertNull(map.get("b"));
+
+ // but without view, view processing disabled:
+ json = mapper.writer().withView(null).writeValueAsString(bean);
+ map = mapper.readValue(json, Map.class);
+ assertEquals(2, map.size());
+ assertEquals("1", map.get("a"));
+ assertEquals("2", map.get("b"));
}
/**
@@ -159,15 +164,15 @@
*/
public void testImplicitAutoDetection() throws Exception
{
- assertEquals("{\"a\":1}", objectMapper().writeValueAsString(new ImplicitBean()));
+ assertEquals("{\"a\":1}",
+ MAPPER.writeValueAsString(new ImplicitBean()));
}
public void testVisibility() throws Exception
{
- ObjectMapper mapper = new ObjectMapper();
VisibilityBean bean = new VisibilityBean();
// Without view setting, should only see "id"
- String json = mapper.writerWithView(Object.class).writeValueAsString(bean);
+ String json = MAPPER.writerWithView(Object.class).writeValueAsString(bean);
//json = mapper.writeValueAsString(bean);
assertEquals("{\"id\":\"id\"}", json);
}
diff --git a/src/test/java/com/fasterxml/jackson/databind/views/TestViewsSerialization2.java b/src/test/java/com/fasterxml/jackson/databind/views/TestViewsSerialization2.java
index eac65b2..96b11cf 100644
--- a/src/test/java/com/fasterxml/jackson/databind/views/TestViewsSerialization2.java
+++ b/src/test/java/com/fasterxml/jackson/databind/views/TestViewsSerialization2.java
@@ -9,59 +9,16 @@
public class TestViewsSerialization2 extends BaseMapTest
{
/*
- /************************************************************************
- /* Tests
- /************************************************************************
+ /************************************************************************
+ /* Helper classes
+ /************************************************************************
*/
-
- public void testDataBindingUsage( ) throws Exception
- {
- ObjectMapper objectMapper = createObjectMapper( null );
- String result = serializeWithObjectMapper(new ComplexTestData( ), Views.View.class, objectMapper );
- assertEquals(-1, result.indexOf( "nameHidden" ));
- }
- public void testDataBindingUsageWithoutView( ) throws Exception
- {
- ObjectMapper objectMapper = createObjectMapper( null );
- String json = serializeWithObjectMapper(new ComplexTestData( ), null, objectMapper);
- assertTrue(json.indexOf( "nameHidden" ) > 0);
- }
-
- /*
- /************************************************************************
- /* Helper methods
- /************************************************************************
- */
-
- private ObjectMapper createObjectMapper(Class<?> viewClass)
- {
- ObjectMapper objectMapper = new ObjectMapper( );
- objectMapper.configure( SerializationFeature.FAIL_ON_EMPTY_BEANS, false );
- objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL );
- objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false );
-// objectMapper.getSerializationConfig( ).disable( SerializationConfig.SerializationFeature.DEFAULT_VIEW_INCLUSION );
-// objectMapper.getSerializationConfig( ).setSerializationView( viewClass );
- return objectMapper;
- }
-
- private String serializeWithObjectMapper(Object object, Class<? extends Views.View> view, ObjectMapper objectMapper )
- throws IOException
- {
- return objectMapper.writerWithView(view).writeValueAsString(object);
- }
-
- /*
- /************************************************************************
- /* Helper classes
- /************************************************************************
- */
-
- static class Views
- {
- public interface View { }
- public interface ExtendedView extends View { }
- }
+ static class Views
+ {
+ public interface View { }
+ public interface ExtendedView extends View { }
+ }
static class ComplexTestData
{
@@ -156,6 +113,47 @@
{
this.nameHidden = nameHidden;
}
- }
+ }
-}
\ No newline at end of file
+ /*
+ /************************************************************************
+ /* Tests
+ /************************************************************************
+ */
+
+ public void testDataBindingUsage( ) throws Exception
+ {
+ ObjectMapper mapper = createMapper();
+ String result = serializeWithObjectMapper(new ComplexTestData( ), Views.View.class, mapper);
+ assertEquals(-1, result.indexOf( "nameHidden" ));
+ }
+
+ public void testDataBindingUsageWithoutView( ) throws Exception
+ {
+ ObjectMapper mapper = createMapper();
+ String json = serializeWithObjectMapper(new ComplexTestData( ), null, mapper);
+ assertTrue(json.indexOf( "nameHidden" ) > 0);
+ }
+
+ /*
+ /************************************************************************
+ /* Helper methods
+ /************************************************************************
+ */
+
+ private ObjectMapper createMapper()
+ {
+ ObjectMapper mapper = newObjectMapper();
+ mapper.configure( SerializationFeature.FAIL_ON_EMPTY_BEANS, false );
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL );
+ mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false );
+ return mapper;
+ }
+
+ private String serializeWithObjectMapper(Object object, Class<? extends Views.View> view, ObjectMapper mapper )
+ throws IOException
+ {
+ return mapper.writerWithView(view).writeValueAsString(object);
+ }
+
+ }
\ No newline at end of file
diff --git a/src/test/java/com/fasterxml/jackson/failing/AnyPropSorting518Test.java b/src/test/java/com/fasterxml/jackson/failing/AnyPropSorting518Test.java
new file mode 100644
index 0000000..dbef399
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/AnyPropSorting518Test.java
@@ -0,0 +1,49 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.*;
+
+public class AnyPropSorting518Test extends BaseMapTest
+{
+ @JsonPropertyOrder(alphabetic = true)
+ static class Bean
+ {
+ public int b;
+
+ protected Map<String,Object> extra = new HashMap<>();
+
+ public int a;
+
+ public Bean(int a, int b, Map<String,Object> x) {
+ this.a = a;
+ this.b = b;
+ extra = x;
+ }
+
+ @JsonAnyGetter
+ public Map<String,Object> getExtra() { return extra; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testAnyBeanWithSort() throws Exception
+ {
+ Map<String,Object> extra = new LinkedHashMap<>();
+ extra.put("y", 4);
+ extra.put("x", 3);
+ String json = MAPPER.writeValueAsString(new Bean(1, 2, extra));
+ assertEquals(aposToQuotes("{'a':1,'b':2,'x':3,'y':4}"),
+ json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/AnySetter1035Test.java b/src/test/java/com/fasterxml/jackson/failing/AnySetter1035Test.java
deleted file mode 100644
index 451cab3..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/AnySetter1035Test.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import java.util.*;
-
-import com.fasterxml.jackson.annotation.*;
-
-import com.fasterxml.jackson.databind.*;
-
-/**
- * Test for [databind#1035], wherein key type of a `Map` not used with `@JsonAnySetter`
- * (value type is).
- */
-public class AnySetter1035Test extends BaseMapTest
-{
- static class MyGeneric<T>
- {
- private String staticallyMappedProperty;
- private Map<T, Integer> dynamicallyMappedProperties = new HashMap<T, Integer>();
-
- public String getStaticallyMappedProperty() {
- return staticallyMappedProperty;
- }
-
- @JsonAnySetter
- public void addDynamicallyMappedProperty(T key, int value) {
- dynamicallyMappedProperties.put(key, value);
- }
-
- public void setStaticallyMappedProperty(String staticallyMappedProperty) {
- this.staticallyMappedProperty = staticallyMappedProperty;
- }
-
- @JsonAnyGetter
- public Map<T, Integer> getDynamicallyMappedProperties() {
- return dynamicallyMappedProperties;
- }
- }
-
- static class MyWrapper
- {
- private MyGeneric<String> myStringGeneric;
- private MyGeneric<Integer> myIntegerGeneric;
-
- public MyGeneric<String> getMyStringGeneric() {
- return myStringGeneric;
- }
-
- public void setMyStringGeneric(MyGeneric<String> myStringGeneric) {
- this.myStringGeneric = myStringGeneric;
- }
-
- public MyGeneric<Integer> getMyIntegerGeneric() {
- return myIntegerGeneric;
- }
-
- public void setMyIntegerGeneric(MyGeneric<Integer> myIntegerGeneric) {
- this.myIntegerGeneric = myIntegerGeneric;
- }
- }
-
- public void testGenericAnySetter() throws Exception
- {
- ObjectMapper mapper = new ObjectMapper();
-
- Map<String, Integer> stringGenericMap = new HashMap<String, Integer>();
- stringGenericMap.put("testStringKey", 5);
- Map<Integer, Integer> integerGenericMap = new HashMap<Integer, Integer>();
- integerGenericMap.put(111, 6);
-
- MyWrapper deserialized = mapper.readValue(aposToQuotes(
- "{'myStringGeneric':{'staticallyMappedProperty':'Test','testStringKey':5},'myIntegerGeneric':{'staticallyMappedProperty':'Test2','111':6}}"
- ), MyWrapper.class);
- MyGeneric<String> stringGeneric = deserialized.getMyStringGeneric();
- MyGeneric<Integer> integerGeneric = deserialized.getMyIntegerGeneric();
-
- assertNotNull(stringGeneric);
- assertEquals(stringGeneric.getStaticallyMappedProperty(), "Test");
- for(Map.Entry<String, Integer> entry : stringGeneric.getDynamicallyMappedProperties().entrySet()) {
- assertTrue("A key in MyGeneric<String> is not an String.", entry.getKey() instanceof String);
- assertTrue("A value in MyGeneric<Integer> is not an Integer.", entry.getValue() instanceof Integer);
- }
- assertEquals(stringGeneric.getDynamicallyMappedProperties(), stringGenericMap);
-
- assertNotNull(integerGeneric);
- assertEquals(integerGeneric.getStaticallyMappedProperty(), "Test2");
- for(Map.Entry<Integer, Integer> entry : integerGeneric.getDynamicallyMappedProperties().entrySet()) {
- Object key = entry.getKey();
- assertEquals("A key in MyGeneric<Integer> is not an Integer.", Integer.class, key.getClass());
- Object value = entry.getValue();
- assertEquals("A value in MyGeneric<Integer> is not an Integer.", Integer.class, value.getClass());
- }
- assertEquals(integerGeneric.getDynamicallyMappedProperties(), integerGenericMap);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/BackReference1516Test.java b/src/test/java/com/fasterxml/jackson/failing/BackReference1516Test.java
index 95c4de7..23e298b 100644
--- a/src/test/java/com/fasterxml/jackson/failing/BackReference1516Test.java
+++ b/src/test/java/com/fasterxml/jackson/failing/BackReference1516Test.java
@@ -74,7 +74,7 @@
private final String PARENT_CHILD_JSON = aposToQuotes(
"{ 'id': 'abc',\n"+
" 'name': 'Bob',\n"+
-" 'child': { 'id': 'def', 'title':'Bert' }\n"+
+" 'child': { 'id': 'def', 'name':'Bert' }\n"+
"}");
public void testWithParentCreator() throws Exception
@@ -82,6 +82,8 @@
ParentWithCreator result = MAPPER.readValue(PARENT_CHILD_JSON,
ParentWithCreator.class);
assertNotNull(result);
+ assertNotNull(result.child);
+ assertSame(result, result.child.parent);
}
public void testWithParentNoCreator() throws Exception
@@ -89,5 +91,7 @@
ParentWithoutCreator result = MAPPER.readValue(PARENT_CHILD_JSON,
ParentWithoutCreator.class);
assertNotNull(result);
+ assertNotNull(result.child);
+ assertSame(result, result.child.parent);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/CollectionType1415Test.java b/src/test/java/com/fasterxml/jackson/failing/CollectionType1415Test.java
deleted file mode 100644
index dcf8718..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/CollectionType1415Test.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import java.util.*;
-
-import com.fasterxml.jackson.databind.BaseMapTest;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.CollectionType;
-import com.fasterxml.jackson.databind.type.MapType;
-
-// for [databind#1415]
-public class CollectionType1415Test extends BaseMapTest
-{
- static abstract class LongList implements List<Long> { }
-
- static abstract class StringLongMap implements Map<String,Long> { }
-
- /*
- /**********************************************************
- /* Unit tests
- /**********************************************************
- */
-
- private final ObjectMapper MAPPER = new ObjectMapper();
-
- public void testExplicitCollectionType() throws Exception
- {
- JavaType t = MAPPER.getTypeFactory()
- .constructCollectionType(LongList.class, Long.class);
- assertEquals(LongList.class, t.getRawClass());
- assertEquals(Long.class, t.getContentType().getRawClass());
- }
-
- public void testImplicitCollectionType() throws Exception
- {
- JavaType t = MAPPER.getTypeFactory()
- .constructParametricType(List.class, Long.class);
- assertEquals(CollectionType.class, t.getClass());
- assertEquals(List.class, t.getRawClass());
- assertEquals(Long.class, t.getContentType().getRawClass());
- }
-
- public void testExplicitMapType() throws Exception
- {
- JavaType t = MAPPER.getTypeFactory()
- .constructMapType(StringLongMap.class,
- String.class, Long.class);
- assertEquals(StringLongMap.class, t.getRawClass());
- assertEquals(String.class, t.getKeyType().getRawClass());
- assertEquals(Long.class, t.getContentType().getRawClass());
- }
-
- public void testImplicitMapType() throws Exception
- {
- JavaType t = MAPPER.getTypeFactory()
- .constructParametricType(Map.class, Long.class, Boolean.class);
- assertEquals(MapType.class, t.getClass());
- assertEquals(Long.class, t.getKeyType().getRawClass());
- assertEquals(Boolean.class, t.getContentType().getRawClass());
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/CreatorProperties1401Test.java b/src/test/java/com/fasterxml/jackson/failing/CreatorAnySetter1401Test.java
similarity index 91%
rename from src/test/java/com/fasterxml/jackson/failing/CreatorProperties1401Test.java
rename to src/test/java/com/fasterxml/jackson/failing/CreatorAnySetter1401Test.java
index 7671519..b8fb645 100644
--- a/src/test/java/com/fasterxml/jackson/failing/CreatorProperties1401Test.java
+++ b/src/test/java/com/fasterxml/jackson/failing/CreatorAnySetter1401Test.java
@@ -4,9 +4,9 @@
import com.fasterxml.jackson.databind.*;
-// for [databind#1401]: should allow "Any Setter" to back up otherwise problematic
-// Creator properties?
-public class CreatorProperties1401Test extends BaseMapTest
+// for [databind#1401]: should allow "Any Setter" to back up otherwise
+// problematic Creator properties?
+public class CreatorAnySetter1401Test extends BaseMapTest
{
// for [databind#1401]
static class NoSetter1401 {
diff --git a/src/test/java/com/fasterxml/jackson/failing/DefaultTypingOverride1391Test.java b/src/test/java/com/fasterxml/jackson/failing/DefaultTypingOverride1391Test.java
new file mode 100644
index 0000000..40d4a31
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/DefaultTypingOverride1391Test.java
@@ -0,0 +1,29 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.*;
+
+// for [databind#1391]: should allow disabling of default typing
+// via explicit {@link JsonTypeInfo}
+public class DefaultTypingOverride1391Test extends BaseMapTest
+{
+ static class ListWrapper {
+ /* 03-Oct-2016, tatu: This doesn't work because it applies to contents
+ * (elements), NOT the container. But there is no current mechanism
+ * to change that; need to add a new feature or properties in 2.9
+ */
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
+ public Collection<String> stuff = Collections.emptyList();
+ }
+
+ public void testCollectionWithOverride() throws Exception
+ {
+ final ObjectMapper mapper = new ObjectMapper()
+ .enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE,
+ "$type");
+ String json = mapper.writeValueAsString(new ListWrapper());
+ assertEquals(aposToQuotes("{'stuff':[]}"), json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/DelegatingCreatorWithAbstractProp2252Test.java b/src/test/java/com/fasterxml/jackson/failing/DelegatingCreatorWithAbstractProp2252Test.java
new file mode 100644
index 0000000..d0d26f2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/DelegatingCreatorWithAbstractProp2252Test.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.databind.*;
+
+public class DelegatingCreatorWithAbstractProp2252Test extends BaseMapTest
+{
+ static class DelegatingWithAbstractSetter
+ {
+ Map<String, Object> _stuff;
+
+ @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+ public DelegatingWithAbstractSetter(Map<String, Object> stuff) {
+ _stuff = stuff;
+ }
+
+ public void setNeverUsed(MyAbstractList bogus) { }
+ }
+
+ // NOTE! Abstract POJO is fine, only Map/Collection causes issues for some reason
+
+// static abstract class MyAbstractMap extends AbstractMap<String, Object> { }
+
+ @SuppressWarnings("serial")
+ static abstract class MyAbstractList extends ArrayList<String> { }
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ // loosely based on [databind#2251], in which delegating creator is used, but
+ // theoretically necessary type for setter can cause issues -- shouldn't, as no
+ // setters (or fields, getter-as-setter) are ever needed due to delegation
+ public void testDelegatingWithUnsupportedSetterType() throws Exception
+ {
+ DelegatingWithAbstractSetter result = MAPPER.readValue("{ \"bogus\": 3 }",DelegatingWithAbstractSetter.class);
+ assertNotNull(result);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/EnumAsExternalPropertyId1328Test.java b/src/test/java/com/fasterxml/jackson/failing/EnumAsExternalPropertyId1328Test.java
deleted file mode 100644
index e0510dd..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/EnumAsExternalPropertyId1328Test.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.annotation.*;
-
-import com.fasterxml.jackson.databind.*;
-
-// Test for [databind#1328]; does not actually reproduce the issue at this point.
-public class EnumAsExternalPropertyId1328Test extends BaseMapTest
-{
- static class Bean1328 {
- public Type1328 type;
-
- @JsonTypeInfo(property = "type", include = JsonTypeInfo.As.EXTERNAL_PROPERTY, use = JsonTypeInfo.Id.NAME,
- visible=true)
- @JsonSubTypes({
- @JsonSubTypes.Type(value = A.class,name = "A"),
- @JsonSubTypes.Type(value = B.class,name = "B")
- })
- public Interface1328 obj;
- }
-
- static interface Interface1328 { }
-
- enum Type1328 {
- A, B;
- }
-
- static class A implements Interface1328 {
- public int a;
- }
-
- static class B implements Interface1328 {
- public int b;
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- private final ObjectMapper MAPPER = new ObjectMapper();
-
- public void testExternalTypeIdAsEnum() throws Exception
- {
- final String JSON = aposToQuotes("{ 'type':'A', 'obj': { 'a' : 4 } }'");
- Bean1328 result = MAPPER.readValue(JSON, Bean1328.class);
- assertNotNull(result.obj);
- assertSame(A.class, result.obj.getClass());
- assertEquals(4, ((A) result.obj).a);
-
- assertSame(Type1328.A, result.type);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/EnumAsIndexMapKey1877Test.java b/src/test/java/com/fasterxml/jackson/failing/EnumAsIndexMapKey1877Test.java
new file mode 100644
index 0000000..ebe12d3
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/EnumAsIndexMapKey1877Test.java
@@ -0,0 +1,65 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+
+public class EnumAsIndexMapKey1877Test extends BaseMapTest
+{
+ public enum Type {
+ ANY,
+ OTHER
+ }
+
+ static class Container {
+ private Type simpleType;
+ private Map<Type, String> map;
+
+ public Type getSimpleType() {
+ return simpleType;
+ }
+
+ public void setSimpleType(Type simpleType) {
+ this.simpleType= simpleType;
+ }
+
+ public Map<Type, String> getMap() {
+ return map;
+ }
+
+ public void setMap(Map<Type, String> map) {
+ this.map = map;
+ }
+ }
+ // [databind#1877]
+ public void testEnumAsIndexMapKey() throws Exception
+ {
+ ObjectMapper mapper = newObjectMapper();
+
+ Map<Type, String> map = new HashMap<>();
+ map.put(Type.OTHER, "hello world");
+ Container container = new Container();
+ container.setSimpleType(Type.ANY);
+ container.setMap(map);
+
+ String json = mapper
+ .writer().with(SerializationFeature.WRITE_ENUMS_USING_INDEX)
+ .writeValueAsString(container);
+
+ Container cont;
+ try {
+ cont = mapper
+ .readerFor(Container.class)
+ .readValue(json);
+ } catch (JsonMappingException e) {
+ throw e;
+ }
+ assertNotNull(cont);
+ assertEquals(1, container.getMap().size());
+ InvalidFormatException foo = null;
+
+ assertSame(Type.OTHER, container.getMap().keySet().iterator().next());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/EnumDeserialization1626Test.java b/src/test/java/com/fasterxml/jackson/failing/EnumDeserialization1626Test.java
new file mode 100644
index 0000000..ef7625d
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/EnumDeserialization1626Test.java
@@ -0,0 +1,87 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * NOTE: not assumed to be actual bug -- real numbers are not coerced into
+ * Strings, and are instead assumed to always mean index numbers.
+ * But test retained in case there might be ways to improve support
+ * here: as is, one MUST use Creator method to resolve from number to
+ * enum.
+ */
+public class EnumDeserialization1626Test extends BaseMapTest
+{
+ static class JsonResponseEnvelope<T> {
+ @JsonProperty("d")
+ public T data;
+ }
+
+ static class ShippingMethodInfo {
+ @JsonProperty("typeId")
+ public int typeId;
+
+ @JsonProperty("value")
+ private ShippingMethods value;
+
+ @JsonProperty("coverage")
+ public int coverage;
+ }
+
+ enum ShippingMethods {
+ @JsonProperty("0")
+ SHIPPING_METHODS_UNSPECIFIED(0),
+
+ @JsonProperty("10")
+ SHIPPING_METHODS_FED_EX_PRIORITY_OVERNIGHT(10),
+
+ @JsonProperty("17")
+ SHIPPING_METHODS_FED_EX_1DAY_FREIGHT(17),
+ ;
+
+ private final int shippingMethodId;
+
+ ShippingMethods(final int shippingMethodId) {
+ this.shippingMethodId = shippingMethodId;
+ }
+
+ public int getShippingMethodId() {
+ return shippingMethodId;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ protected final ObjectMapper MAPPER = new ObjectMapper();
+
+ // [databind#1626]
+ public void testSparseNumericEnum626() throws Exception
+ {
+ String jsonResponse =
+ "{\n" +
+ " \"d\": [\n" +
+ " {\n" +
+ " \"typeId\": 0,\n" +
+ // NOTE! Only real number fails; quoted-as-String is bound as expected
+ " \"value\": 17,\n" +
+ " \"coverage\": 1"+
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ JsonResponseEnvelope<List<ShippingMethodInfo>> mappedResponse =
+ MAPPER.readValue(jsonResponse,
+ new TypeReference<JsonResponseEnvelope<List<ShippingMethodInfo>>>() { });
+ List<ShippingMethodInfo> shippingMethods = mappedResponse.data;
+
+ assertEquals(ShippingMethods.SHIPPING_METHODS_FED_EX_1DAY_FREIGHT,
+ shippingMethods.get(0).value);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/ExternalTypeIdWithUnwrapped2039Test.java b/src/test/java/com/fasterxml/jackson/failing/ExternalTypeIdWithUnwrapped2039Test.java
new file mode 100644
index 0000000..a81f6ab
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/ExternalTypeIdWithUnwrapped2039Test.java
@@ -0,0 +1,58 @@
+package com.fasterxml.jackson.failing;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+
+public class ExternalTypeIdWithUnwrapped2039Test extends BaseMapTest
+{
+ static class MainType2039 {
+ public String text;
+
+ @JsonUnwrapped public Wrapped2039 wrapped;
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "subtype")
+ @JsonSubTypes({ @JsonSubTypes.Type(value = SubA2039.class, name = "SubA") })
+ public SubType2039 sub;
+
+ public void setSub(SubType2039 s) {
+ sub = s;
+ }
+
+ public void setWrapped(Wrapped2039 w) {
+ wrapped = w;
+ }
+ }
+
+ static class Wrapped2039 {
+ public String wrapped;
+ }
+
+ public static class SubType2039 { }
+
+ public static class SubA2039 extends SubType2039 {
+ @JsonProperty public boolean bool;
+ }
+
+ public void testExternalWithUnwrapped2039() throws Exception
+ {
+ final ObjectMapper mapper = newObjectMapper();
+
+ final String json = aposToQuotes("{\n"
+ +"'text': 'this is A',\n"
+ +"'wrapped': 'yes',\n"
+ +"'subtype': 'SubA',\n"
+ +"'sub': {\n"
+ +" 'bool': true\n"
+ +"}\n"
+ +"}");
+ final MainType2039 main = mapper.readValue(json, MainType2039.class);
+
+ assertEquals("this is A", main.text);
+ assertEquals("yes", main.wrapped.wrapped);
+
+ assertNotNull(main.sub);
+ assertEquals(SubA2039.class, main.sub.getClass()); // <- fails here
+ assertEquals(true, ((SubA2039) main.sub).bool);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java b/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java
index 509e572..890f576 100644
--- a/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java
+++ b/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java
@@ -24,7 +24,7 @@
protected int x, y;
// annotation should NOT be needed with 2.6 any more (except for single-arg case)
- //@com.fasterxml.jackson.annotation.JsonCreator
+// @com.fasterxml.jackson.annotation.JsonCreator
public XY(int x, int y) {
this.x = x;
this.y = y;
@@ -37,14 +37,16 @@
/**********************************************************
*/
- // for [databind#806]
- public void testImplicitNameWithNamingStrategy() throws Exception
- {
- ObjectMapper mapper = new ObjectMapper()
+ private final ObjectMapper MAPPER = newObjectMapper()
.setAnnotationIntrospector(new MyParamIntrospector())
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
;
- XY value = mapper.readValue(aposToQuotes("{'param_name0':1,'param_name1':2}"), XY.class);
+
+ // for [databind#806]: problem is that renaming occurs too late for implicitly detected
+ // Creators
+ public void testImplicitNameWithNamingStrategy() throws Exception
+ {
+ XY value = MAPPER.readValue(aposToQuotes("{'param_name0':1,'param_name1':2}"), XY.class);
assertNotNull(value);
assertEquals(1, value.x);
assertEquals(2, value.y);
diff --git a/src/test/java/com/fasterxml/jackson/failing/InnerClassNonStaticCore384Test.java b/src/test/java/com/fasterxml/jackson/failing/InnerClassNonStaticCore384Test.java
new file mode 100644
index 0000000..0ef5e76
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/InnerClassNonStaticCore384Test.java
@@ -0,0 +1,209 @@
+package com.fasterxml.jackson.failing;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+
+import static org.junit.Assert.assertThat;
+
+import com.fasterxml.jackson.databind.*;
+
+// see [https://github.com/FasterXML/jackson-core/issues/384]: most likely
+// can not be fixed, but could we improve error message to indicate issue
+// with non-static type of `Car` and `Truck`, which prevent instantiation?
+public class InnerClassNonStaticCore384Test extends BaseMapTest
+{
+ static class Fleet {
+ private List<Vehicle> vehicles;
+
+ public List<Vehicle> getVehicles() {
+ return vehicles;
+ }
+
+ public void setVehicles(List<Vehicle> vehicles) {
+ this.vehicles = vehicles;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Fleet fleet = (Fleet) o;
+ return Objects.equals(vehicles, fleet.vehicles);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(vehicles);
+ }
+ }
+
+ static abstract class Vehicle {
+ private String make;
+ private String model;
+
+ protected Vehicle(String make, String model) {
+ this.make = make;
+ this.model = model;
+ }
+
+ public Vehicle() {
+ }
+
+ public String getMake() {
+ return make;
+ }
+
+ public void setMake(String make) {
+ this.make = make;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Vehicle)) return false;
+ Vehicle vehicle = (Vehicle) o;
+ return Objects.equals(make, vehicle.make) &&
+ Objects.equals(model, vehicle.model);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(make, model);
+ }
+ }
+
+ class Car extends Vehicle {
+ private int seatingCapacity;
+ private double topSpeed;
+
+ public Car(String make, String model, int seatingCapacity, double topSpeed) {
+ super(make, model);
+ this.seatingCapacity = seatingCapacity;
+ this.topSpeed = topSpeed;
+ }
+
+ public Car() {
+ }
+
+ public int getSeatingCapacity() {
+ return seatingCapacity;
+ }
+
+ public void setSeatingCapacity(int seatingCapacity) {
+ this.seatingCapacity = seatingCapacity;
+ }
+
+ public double getTopSpeed() {
+ return topSpeed;
+ }
+
+ public void setTopSpeed(double topSpeed) {
+ this.topSpeed = topSpeed;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ Car car = (Car) o;
+ return seatingCapacity == car.seatingCapacity &&
+ Double.compare(car.topSpeed, topSpeed) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), seatingCapacity, topSpeed);
+ }
+ }
+
+ class Truck extends Vehicle {
+ private double payloadCapacity;
+
+ public Truck(String make, String model, double payloadCapacity) {
+ super(make, model);
+ this.payloadCapacity = payloadCapacity;
+ }
+
+ public Truck() {
+ }
+
+ public double getPayloadCapacity() {
+ return payloadCapacity;
+ }
+
+ public void setPayloadCapacity(double payloadCapacity) {
+ this.payloadCapacity = payloadCapacity;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ Truck truck = (Truck) o;
+ return Double.compare(truck.payloadCapacity, payloadCapacity) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), payloadCapacity);
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ public void testHierarchy() throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enableDefaultTyping();
+
+ Fleet fleet = initVehicle();
+
+ /*
+for (Vehicle v : fleet.vehicles) {
+ System.out.println("Vehicle, type: "+v.getClass());
+}
+*/
+ String serializedFleet = mapper
+ .writerWithDefaultPrettyPrinter()
+ .writeValueAsString(fleet);
+
+//System.out.println(serializedFleet);
+
+ Fleet deserializedFleet = mapper.readValue(serializedFleet, Fleet.class);
+
+ assertThat(deserializedFleet.getVehicles().get(0), instanceOf(Car.class));
+ assertThat(deserializedFleet.getVehicles().get(1), instanceOf(Truck.class));
+
+ assertEquals(fleet, deserializedFleet);
+ }
+
+ private Fleet initVehicle() {
+ Car car = new Car("Mercedes-Benz", "S500", 5, 250.0);
+ Truck truck = new Truck("Isuzu", "NQR", 7500.0);
+
+ List<Vehicle> vehicles = new ArrayList<>();
+ vehicles.add(car);
+ vehicles.add(truck);
+
+ Fleet serializedFleet = new Fleet();
+ serializedFleet.setVehicles(vehicles);
+ return serializedFleet;
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/KevinFail1410Test.java b/src/test/java/com/fasterxml/jackson/failing/KevinFail1410Test.java
new file mode 100644
index 0000000..8cba59c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/KevinFail1410Test.java
@@ -0,0 +1,79 @@
+package com.fasterxml.jackson.failing;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+
+public class KevinFail1410Test extends BaseMapTest
+{
+ enum EnvironmentEventSource { BACKEND; }
+
+ @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="source")
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = BackendEvent.class, name = "BACKEND")
+ })
+ static abstract class EnvironmentEvent {
+ private String environmentName;
+ private String message;
+
+ protected EnvironmentEvent() { } // for deserializer
+ protected EnvironmentEvent(String env, String msg) {
+ environmentName = env;
+ message = msg;
+ }
+ public String getEnvironmentName() { return environmentName; }
+ public abstract EnvironmentEventSource getSource();
+ public String getMessage() { return message; }
+ }
+
+ static class BackendEvent extends EnvironmentEvent {
+ private String status;
+
+ private Object resultData;
+
+ protected BackendEvent() {} // for deserializer
+
+ public BackendEvent(String envName, String message, String status, Object results)
+ {
+ super(envName, message);
+ this.status = status;
+ resultData = results;
+ }
+
+ public static BackendEvent create(String environmentName, String message,
+ String status, Object results)
+ {
+ return new BackendEvent(environmentName, message,
+ status, results);
+ }
+
+ @Override
+ public EnvironmentEventSource getSource() {
+ return EnvironmentEventSource.BACKEND;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public Object getResultData() {
+ return resultData;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%s): %s", status, getMessage());
+ }
+ }
+
+ public void testDupProps() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ EnvironmentEvent event = new BackendEvent("foo", "hello", "bar", null);
+ String ser = mapper
+ .writerWithDefaultPrettyPrinter()
+ .writeValueAsString(event);
+ mapper.readValue(ser, EnvironmentEvent.class);
+ assertNotNull(ser);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/MapEntryFormat1419Test.java b/src/test/java/com/fasterxml/jackson/failing/MapEntryFormat1419Test.java
new file mode 100644
index 0000000..18a4616
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/MapEntryFormat1419Test.java
@@ -0,0 +1,40 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.*;
+
+// for [databind#1419]
+public class MapEntryFormat1419Test extends BaseMapTest
+{
+ static class BeanWithMapEntryAsObject {
+ @JsonFormat(shape=JsonFormat.Shape.OBJECT)
+ public Map.Entry<String,String> entry;
+
+ protected BeanWithMapEntryAsObject() { }
+ public BeanWithMapEntryAsObject(String key, String value) {
+ Map<String,String> map = new HashMap<>();
+ map.put(key, value);
+ entry = map.entrySet().iterator().next();
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = new ObjectMapper();
+
+ public void testWrappedAsObjectRoundtrip() throws Exception
+ {
+ BeanWithMapEntryAsObject input = new BeanWithMapEntryAsObject("foo" ,"bar");
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("{'entry':{'key':'foo','value':'bar'}}"), json);
+ BeanWithMapEntryAsObject result = MAPPER.readValue(json, BeanWithMapEntryAsObject.class);
+ assertEquals("foo", result.entry.getKey());
+ assertEquals("bar", result.entry.getValue());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/MapInclusion1649Test.java b/src/test/java/com/fasterxml/jackson/failing/MapInclusion1649Test.java
new file mode 100644
index 0000000..d2d0906
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/MapInclusion1649Test.java
@@ -0,0 +1,41 @@
+package com.fasterxml.jackson.failing;
+
+import java.io.IOException;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.*;
+
+public class MapInclusion1649Test extends BaseMapTest
+{
+ @JsonInclude(value=JsonInclude.Include.NON_EMPTY, content=JsonInclude.Include.NON_EMPTY)
+ static class Bean1649 {
+ public Map<String, String> map;
+
+ public Bean1649(String key, String value) {
+ map = new LinkedHashMap<>();
+ map.put(key, value);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ final private ObjectMapper MAPPER = objectMapper();
+
+ // [databind#1649]
+ public void testNonEmptyViaClass() throws IOException
+ {
+ // non-empty/null, include
+ assertEquals(aposToQuotes("{'map':{'a':'b'}}"),
+ MAPPER.writeValueAsString(new Bean1649("a", "b")));
+ // null, empty, nope
+ assertEquals(aposToQuotes("{}"),
+ MAPPER.writeValueAsString(new Bean1649("a", null)));
+ assertEquals(aposToQuotes("{}"),
+ MAPPER.writeValueAsString(new Bean1649("a", "")));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/NoTypeInfo1654Test.java b/src/test/java/com/fasterxml/jackson/failing/NoTypeInfo1654Test.java
new file mode 100644
index 0000000..b39d44a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/NoTypeInfo1654Test.java
@@ -0,0 +1,75 @@
+package com.fasterxml.jackson.failing;
+
+import java.io.IOException;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+public class NoTypeInfo1654Test extends BaseMapTest
+{
+ // [databind#1654]
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
+ static class Value1654 {
+ public int x;
+
+ protected Value1654() { }
+ public Value1654(int x) { this.x = x; }
+ }
+
+ static class Value1654TypedContainer {
+ public List<Value1654> values;
+
+ protected Value1654TypedContainer() { }
+ public Value1654TypedContainer(Value1654... v) {
+ values = Arrays.asList(v);
+ }
+ }
+
+ static class Value1654UntypedContainer {
+ @JsonDeserialize(contentUsing = Value1654Deserializer.class)
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
+ public List<Value1654> values;
+ }
+
+ static class Value1654Deserializer extends JsonDeserializer<Value1654> {
+ @Override
+ public Value1654 deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ p.skipChildren();
+ return new Value1654(13);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ // [databind#1654]
+ public void testNoTypeElementOverride() throws Exception
+ {
+ final ObjectMapper mapper = newObjectMapper();
+
+ // First: regular typed case
+ String json = mapper.writeValueAsString(new Value1654TypedContainer(
+ new Value1654(1),
+ new Value1654(2),
+ new Value1654(3)
+ ));
+ Value1654TypedContainer result = mapper.readValue(json, Value1654TypedContainer.class);
+ assertEquals(3, result.values.size());
+ assertEquals(2, result.values.get(1).x);
+
+ // and then actual failing case
+ final String noTypeJson = aposToQuotes(
+ "{'values':[{'x':3},{'x': 7}] }"
+ );
+ Value1654UntypedContainer unResult = mapper.readValue(noTypeJson, Value1654UntypedContainer.class);
+ assertEquals(2, unResult.values.size());
+ assertEquals(7, unResult.values.get(1).x);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/NodeContext2049Test.java b/src/test/java/com/fasterxml/jackson/failing/NodeContext2049Test.java
new file mode 100644
index 0000000..f60b9f3
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/NodeContext2049Test.java
@@ -0,0 +1,183 @@
+package com.fasterxml.jackson.failing;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.*;
+import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;
+import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionLikeType;
+
+public class NodeContext2049Test extends BaseMapTest
+{
+ public interface HasParent {
+ void setParent(Parent parent);
+ Parent getParent();
+ }
+
+ static class Child implements HasParent {
+ public Parent parent;
+ public String property;
+
+ @Override
+ public void setParent(Parent p) { parent = p; }
+ @Override
+ public Parent getParent() { return parent; }
+ }
+
+ static class Parent {
+ public List<Child> children;
+ public Child singleChild;
+ }
+
+ static class ListValueInstantiator extends ValueInstantiator {
+ @Override
+ public String getValueTypeDesc() {
+ return List.class.getName();
+ }
+
+ @Override
+ public Object createUsingDefault(DeserializationContext ctxt) throws IOException {
+ return new ArrayList<>();
+ }
+ }
+
+ static class ParentSettingDeserializerModifier extends BeanDeserializerModifier {
+ @Override
+ public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc,
+ BeanDeserializerBuilder builder) {
+ for (Iterator<SettableBeanProperty> propertyIt = builder.getProperties(); propertyIt.hasNext(); ) {
+ SettableBeanProperty property = propertyIt.next();
+ builder.addOrReplaceProperty(property.withValueDeserializer(new ParentSettingDeserializerContextual()), false);
+ }
+ return builder;
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static class ParentSettingDeserializer extends DelegatingDeserializer {
+ public ParentSettingDeserializer(JsonDeserializer<?> delegatee) {
+ super(delegatee);
+ }
+
+ @Override
+ public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ Object retValue = super.deserialize(jp, ctxt);
+ if (retValue instanceof HasParent) {
+ HasParent obj = (HasParent) retValue;
+ Parent parent = null;
+ JsonStreamContext parsingContext = jp.getParsingContext();
+ while (parent == null && parsingContext != null) {
+ Object currentValue = parsingContext.getCurrentValue();
+ if (currentValue != null && currentValue instanceof Parent) {
+ parent = (Parent) currentValue;
+ }
+ parsingContext = parsingContext.getParent();
+ }
+ if (parent != null) {
+ obj.setParent(parent);
+ }
+ }
+ return retValue;
+ }
+
+ @Override
+ protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
+ return new ParentSettingDeserializer(newDelegatee);
+ }
+
+ }
+
+ static class ParentSettingDeserializerContextual extends JsonDeserializer<Object> implements ContextualDeserializer {
+
+ @Override
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException {
+ JavaType propertyType = property.getType();
+ JavaType contentType = propertyType;
+ if (propertyType.isCollectionLikeType()) {
+ contentType = propertyType.getContentType();
+ }
+ JsonDeserializer<Object> delegatee = ctxt.findNonContextualValueDeserializer(contentType);
+ JsonDeserializer<Object> objectDeserializer = new ParentSettingDeserializer(delegatee);
+ JsonDeserializer<?> retValue;
+ if (propertyType.isCollectionLikeType()) {
+ CollectionLikeType collectionType = ctxt.getTypeFactory().constructCollectionLikeType(propertyType.getRawClass(),
+ contentType);
+ ValueInstantiator instantiator = new ListValueInstantiator();
+ retValue = new CollectionDeserializer(collectionType, objectDeserializer, null, instantiator);
+ } else {
+ retValue = objectDeserializer;
+ }
+ return retValue;
+ }
+
+ @Override
+ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ private ObjectMapper objectMapper;
+ {
+ objectMapper = new ObjectMapper();
+ objectMapper.registerModule(new com.fasterxml.jackson.databind.Module() {
+ @Override
+ public String getModuleName() {
+ return "parentSetting";
+ }
+ @Override
+ public Version version() {
+ return Version.unknownVersion();
+ }
+ @Override
+ public void setupModule(SetupContext context) {
+ context.addBeanDeserializerModifier(new ParentSettingDeserializerModifier());
+ }
+ });
+ }
+
+ final static String JSON = "{\n" +
+ " \"children\": [\n" +
+ " {\n" +
+ " \"property\": \"value1\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"property\": \"value2\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"singleChild\": {\n" +
+ " \"property\": \"value3\"\n" +
+ " }\n" +
+ "}";
+
+ public void testReadNoBuffering() throws IOException {
+ Parent obj = objectMapper.readerFor(Parent.class).readValue(JSON);
+ assertSame(obj, obj.singleChild.getParent());
+ for (Child child : obj.children) {
+ assertSame(obj, child.getParent());
+ }
+ }
+
+ public void testReadFromTree() throws IOException {
+ JsonNode tree = objectMapper.readTree(JSON);
+ Parent obj = objectMapper.reader().forType(Parent.class).readValue(tree);
+ assertSame(obj, obj.singleChild.getParent());
+ for (Child child : obj.children) {
+ assertSame(obj, child.getParent());
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/NullConversionWithCreatorTest.java b/src/test/java/com/fasterxml/jackson/failing/NullConversionWithCreatorTest.java
new file mode 100644
index 0000000..39e4043
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/NullConversionWithCreatorTest.java
@@ -0,0 +1,62 @@
+package com.fasterxml.jackson.failing;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.InvalidNullException;
+
+public class NullConversionWithCreatorTest extends BaseMapTest
+{
+ // [databind#2024]
+ static class EmptyFromNullViaCreator {
+ @JsonSetter(nulls=Nulls.AS_EMPTY)
+ Point p;
+
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public EmptyFromNullViaCreator(@JsonSetter(nulls=Nulls.AS_EMPTY)
+ @JsonProperty("p") Point p)
+ {
+ this.p = p;
+ }
+ }
+
+ static class FailFromNullViaCreator {
+ @JsonSetter(nulls=Nulls.AS_EMPTY)
+ Point p;
+
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public FailFromNullViaCreator(@JsonSetter(nulls=Nulls.FAIL)
+ @JsonProperty("p") Point p)
+ {
+ this.p = p;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ // [databind#2024]
+ public void testEmptyFromNullViaCreator() throws Exception
+ {
+ EmptyFromNullViaCreator result = MAPPER.readValue(aposToQuotes("{'p':null}"),
+ EmptyFromNullViaCreator.class);
+ assertNotNull(result);
+ assertNotNull(result.p);
+ }
+
+ // [databind#2024]
+ public void testFailForNullViaCreator() throws Exception
+ {
+ try {
+ /*FailFromNullViaCreator result =*/ MAPPER.readValue(aposToQuotes("{'p':null}"),
+ FailFromNullViaCreator.class);
+ fail("Should not pass");
+ } catch (InvalidNullException e) {
+ verifyException(e, "property \"p\"");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java b/src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java
new file mode 100644
index 0000000..93e3602
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/NumberNodes1770Test.java
@@ -0,0 +1,23 @@
+package com.fasterxml.jackson.failing;
+
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * Basic tests for {@link JsonNode} implementations that
+ * contain numeric values.
+ */
+public class NumberNodes1770Test extends BaseMapTest
+{
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ // Related to [databind#1770]
+ public void testBigDecimalCoercion() throws Exception
+ {
+ final JsonNode jsonNode = MAPPER.reader()
+ .with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
+ .readTree("7976931348623157e309");
+ assertTrue(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/ObjectIdWithBuilder1496Test.java b/src/test/java/com/fasterxml/jackson/failing/ObjectIdWithBuilder1496Test.java
new file mode 100644
index 0000000..8338ed2
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/ObjectIdWithBuilder1496Test.java
@@ -0,0 +1,69 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+
+public class ObjectIdWithBuilder1496Test extends BaseMapTest
+{
+ @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
+ @JsonDeserialize(builder=POJOBuilder.class)
+ static class POJO
+ {
+ private long id;
+ public long getId() { return id; }
+ private int var;
+ public int getVar() { return var; }
+ private POJO (long id, int var) { this.id = id; this.var = var; }
+
+ @Override
+ public String toString() { return "id: " + id + ", var: " + var; }
+ }
+
+ @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
+ @JsonPOJOBuilder(withPrefix = "", buildMethodName="readFromCacheOrBuild")
+ static final class POJOBuilder {
+ // Standard builder stuff
+ private long id;
+ private int var;
+
+ public POJOBuilder id(long _id) { id = _id; return this; }
+ public POJOBuilder var(int _var) { var = _var; return this; }
+
+ public POJO build() { return new POJO(id, var); }
+
+ // Special build method for jackson deserializer that caches objects already deserialized
+ private final static ConcurrentHashMap<Long, POJO> cache = new ConcurrentHashMap<>();
+ public POJO readFromCacheOrBuild() {
+ POJO pojo = cache.get(id);
+ if (pojo == null) {
+ POJO newPojo = build();
+ pojo = cache.putIfAbsent(id, newPojo);
+ if (pojo == null) {
+ pojo = newPojo;
+ }
+ }
+ return pojo;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testBuilderId1496() throws Exception
+ {
+ POJO input = new POJOBuilder().id(123L).var(456).build();
+ String json = MAPPER.writeValueAsString(input);
+ POJO result = MAPPER.readValue(json, POJO.class);
+ assertNotNull(result);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/PolymorphicWithObjectId1551Test.java b/src/test/java/com/fasterxml/jackson/failing/PolymorphicWithObjectId1551Test.java
deleted file mode 100644
index 6944644..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/PolymorphicWithObjectId1551Test.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.annotation.JsonIdentityInfo;
-import com.fasterxml.jackson.annotation.JsonIdentityReference;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.ObjectIdGenerators;
-import com.fasterxml.jackson.databind.*;
-
-public class PolymorphicWithObjectId1551Test extends BaseMapTest
-{
- @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY,
- property = "@class")
- static abstract class Vehicle {
- public String vehicleId;
- }
-
- static class Car extends Vehicle {
- public int numberOfDoors;
- }
-
- static class VehicleOwnerViaProp {
- @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "vehicleId")
- @JsonIdentityReference(alwaysAsId = false)
- public Vehicle ownedVehicle;
- }
-
- public void testWithAbstractUsingProp() throws Exception {
- Car c = new Car();
- c.vehicleId = "123";
- c.numberOfDoors = 2;
- // both owners own the same vehicle (car sharing ;-))
- VehicleOwnerViaProp v1 = new VehicleOwnerViaProp();
- v1.ownedVehicle = c;
- VehicleOwnerViaProp v2 = new VehicleOwnerViaProp();
- v2.ownedVehicle = c;
-
- ObjectMapper objectMapper = new ObjectMapper();
- String serialized = objectMapper.writer()
- .writeValueAsString(new VehicleOwnerViaProp[] { v1, v2 });
-
- // 02-May-2017, tatu: Not possible to support as of Jackson 2.8 at least, so:
-
- try {
- /*VehicleOwnerViaProp[] deserialized = */
- objectMapper.readValue(serialized, VehicleOwnerViaProp[].class);
- fail("Should not pass");
- } catch (JsonMappingException e) {
- verifyException(e, "Invalid Object Id definition for abstract type");
- }
-// assertEquals(2, deserialized.length);
-// assertSame(deserialized[0].ownedVehicle, deserialized[1].ownedVehicle);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/RecursiveIgnoreProperties1755Test.java b/src/test/java/com/fasterxml/jackson/failing/RecursiveIgnoreProperties1755Test.java
new file mode 100644
index 0000000..9090429
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/RecursiveIgnoreProperties1755Test.java
@@ -0,0 +1,65 @@
+package com.fasterxml.jackson.failing;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import com.fasterxml.jackson.databind.*;
+
+public class RecursiveIgnoreProperties1755Test extends BaseMapTest
+{
+ // for [databind#1755]
+ static class JackBase1755 {
+ public String id;
+ }
+
+ static class JackExt extends JackBase1755 {
+ public BigDecimal quantity;
+ public String ignoreMe;
+
+ @JsonIgnoreProperties({"ignoreMe"})
+ public List<JackExt> linked;
+
+ public List<KeyValue> metadata;
+ }
+
+ static class KeyValue {
+ public String key;
+ public String value;
+ }
+
+ // for [databind#1755]
+
+ private final ObjectMapper MAPPER = newObjectMapper();
+
+ public void testRecursiveIgnore1755() throws Exception
+ {
+ final String JSON = aposToQuotes("{\n"
+ +"'id': '1',\n"
+ +"'quantity': 5,\n"
+ +"'ignoreMe': 'yzx',\n"
+ +"'metadata': [\n"
+ +" {\n"
+ +" 'key': 'position',\n"
+ +" 'value': '2'\n"
+ +" }\n"
+ +" ],\n"
+ +"'linked': [\n"
+ +" {\n"
+ +" 'id': '1',\n"
+ +" 'quantity': 5,\n"
+ +" 'ignoreMe': 'yzx',\n"
+ +" 'metadata': [\n"
+ +" {\n"
+ +" 'key': 'position',\n"
+ +" 'value': '2'\n"
+ +" }\n"
+ +" ]\n"
+ +" }\n"
+ +" ]\n"
+ +"}");
+ JackExt value = MAPPER.readValue(JSON, JackExt.class);
+ assertNotNull(value);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/SkipInjectableIntrospection962Test.java b/src/test/java/com/fasterxml/jackson/failing/SkipInjectableIntrospection962Test.java
index cb37a5c..88f283a 100644
--- a/src/test/java/com/fasterxml/jackson/failing/SkipInjectableIntrospection962Test.java
+++ b/src/test/java/com/fasterxml/jackson/failing/SkipInjectableIntrospection962Test.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.OptBoolean;
import com.fasterxml.jackson.databind.*;
public class SkipInjectableIntrospection962Test extends BaseMapTest
@@ -30,8 +31,10 @@
{
private String b;
+ // Important! Prevent binding from data
@JsonCreator
- public Injectee(@JacksonInject InjectMe injectMe, @JsonProperty("b") String b) {
+ public Injectee(@JacksonInject(useInput=OptBoolean.FALSE) InjectMe injectMe,
+ @JsonProperty("b") String b) {
this.b = b;
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/StaticTyping1515Test.java b/src/test/java/com/fasterxml/jackson/failing/StaticTyping1515Test.java
new file mode 100644
index 0000000..7a1a554
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/failing/StaticTyping1515Test.java
@@ -0,0 +1,77 @@
+package com.fasterxml.jackson.failing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+public class StaticTyping1515Test extends BaseMapTest
+{
+ static abstract class Base {
+ public int a = 1;
+ }
+
+ static class Derived extends Base {
+ public int b = 2;
+ }
+
+ @JsonSerialize(typing = JsonSerialize.Typing.DYNAMIC)
+ static abstract class BaseDynamic {
+ public int a = 3;
+ }
+
+ static class DerivedDynamic extends BaseDynamic {
+ public int b = 4;
+ }
+
+ @JsonPropertyOrder({ "value", "aValue", "dValue" })
+ static class Issue515Singles {
+ public Base value = new Derived();
+
+ @JsonSerialize(typing = JsonSerialize.Typing.DYNAMIC)
+ public Base aValue = new Derived();
+
+ public BaseDynamic dValue = new DerivedDynamic();
+ }
+
+ @JsonPropertyOrder({ "list", "aList", "dList" })
+ static class Issue515Lists {
+ public List<Base> list = new ArrayList<>(); {
+ list.add(new Derived());
+ }
+
+ @JsonSerialize(typing = JsonSerialize.Typing.DYNAMIC)
+ public List<Base> aList = new ArrayList<>(); {
+ aList.add(new Derived());
+ }
+
+ public List<BaseDynamic> dList = new ArrayList<>(); {
+ dList.add(new DerivedDynamic());
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ private final ObjectMapper STAT_MAPPER = newObjectMapper();
+ {
+ STAT_MAPPER.enable(MapperFeature.USE_STATIC_TYPING);
+ }
+
+ public void testStaticTypingForProperties() throws Exception
+ {
+ String json = STAT_MAPPER.writeValueAsString(new Issue515Singles());
+ assertEquals(aposToQuotes("{'value':{'a':1},'aValue':{'a':1,'b':2},'dValue':{'a':3,'b':4}}"), json);
+ }
+
+ public void testStaticTypingForLists() throws Exception
+ {
+ String json = STAT_MAPPER.writeValueAsString(new Issue515Lists());
+ assertEquals(aposToQuotes("{'list':[{'a':1}],'aList':[{'a':1,'b':2}],'dList:[{'a':3,'b':4}]}"), json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestConvertingSerializer357.java b/src/test/java/com/fasterxml/jackson/failing/TestConvertingSerializer357.java
deleted file mode 100644
index ef0d30e..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/TestConvertingSerializer357.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import java.util.*;
-
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
-import com.fasterxml.jackson.databind.util.StdConverter;
-
-public class TestConvertingSerializer357
- extends com.fasterxml.jackson.databind.BaseMapTest
-{
- // [databind#357]
- static class Value { }
-
- static class ListWrapper {
- @JsonSerialize(contentConverter = ValueToStringListConverter.class)
- public List<Value> list = Arrays.asList(new Value());
- }
-
- static class ValueToStringListConverter extends StdConverter<Value, List<String>> {
- @Override
- public List<String> convert(Value value) {
- return Arrays.asList("Hello world!");
- }
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- // [databind#357]
- public void testConverterForList357() throws Exception {
- String json = objectWriter().writeValueAsString(new ListWrapper());
- assertEquals("{\"list\":[[\"Hello world!\"]]}", json);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestMultipleExternalIds291.java b/src/test/java/com/fasterxml/jackson/failing/TestMultipleExternalIds291.java
deleted file mode 100644
index 75ef0c2..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/TestMultipleExternalIds291.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.annotation.*;
-import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
-import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
-import com.fasterxml.jackson.databind.*;
-
-public class TestMultipleExternalIds291 extends BaseMapTest
-{
- // For [Issue#291]
- interface F1 {}
-
- static class A implements F1 {
- public String a;
- }
-
- static class B implements F1 {
- public String b;
- }
-
- static interface F2 {}
-
- static class C implements F2 {
- public String c;
- }
-
- static class D implements F2{
- public String d;
- }
-
- static class Container {
- public String type;
-
- @JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
- @JsonSubTypes({
- @JsonSubTypes.Type(value = A.class, name = "1"),
- @JsonSubTypes.Type(value = B.class, name = "2")})
- public F1 field1;
-
- @JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
- @JsonSubTypes({
- @JsonSubTypes.Type(value = C.class, name = "1"),
- @JsonSubTypes.Type(value = D.class, name = "2")})
- public F2 field2;
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- // [databind#291]
- public void testMultiple() throws Exception
- {
- final ObjectMapper mapper = objectMapper();
- final String JSON =
-"{\"type\" : \"1\",\n"
-+"\"field1\" : {\n"
-+" \"a\" : \"AAA\"\n"
-+"}, \"field2\" : {\n"
-+" \"c\" : \"CCC\"\n"
-+"}\n"
-+"}";
-
- Container c = mapper.readValue(JSON, Container.class);
- assertNotNull(c);
- assertTrue(c.field1 instanceof A);
- assertTrue(c.field2 instanceof C);
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestObjectIdWithUnwrapping1298.java b/src/test/java/com/fasterxml/jackson/failing/TestObjectIdWithUnwrapping1298.java
index 8c4b97b..d039140 100644
--- a/src/test/java/com/fasterxml/jackson/failing/TestObjectIdWithUnwrapping1298.java
+++ b/src/test/java/com/fasterxml/jackson/failing/TestObjectIdWithUnwrapping1298.java
@@ -45,6 +45,7 @@
public void testObjectIdWithRepeatedChild() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
+ // to keep output faithful to original, prevent auto-closing...
mapper.disable(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT);
// Equivalent to Spring _embedded for Bean w/ List property
@@ -58,9 +59,10 @@
// serialize parent1 and parent2
String json = mapper
-// .writerWithDefaultPrettyPrinter()
+ .writerWithDefaultPrettyPrinter()
.writeValueAsString(parents);
- System.out.println("This works: " + json);
+ assertNotNull(json);
+// System.out.println("This works: " + json);
// Add parent3 to create ObjectId reference
// Bean w/ repeated relationship from parent1, should generate ObjectId
@@ -74,10 +76,7 @@
// .writerWithDefaultPrettyPrinter()
.writeValue(sw, parents);
} catch (Exception e) {
- System.out.println("Failed output so far: " + sw);
- throw e;
+ fail("Failed with "+e.getClass().getName()+", output so far: " + sw);
}
-
- System.out.println("Also works: " + sw);
}
}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestPolymorphicDeserialization283.java b/src/test/java/com/fasterxml/jackson/failing/TestPolymorphicDeserialization283.java
deleted file mode 100644
index bd1c254..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/TestPolymorphicDeserialization283.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-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 org.junit.Assert;
-import org.junit.Test;
-
-/**
- * Reproduction of [https://github.com/FasterXML/jackson-databind/issues/283],
- * contributed by Eric T.
- *<p>
- * Problem here is that although explicit concrete class is indicated, polymorphic
- * deserializer comes to different conclusion (using default implementation class),
- * resulting in a <code>ClassCastException</code>.
- * Whether this is wrong, and if so, can we fix it, is unknown at this point
- * (2.6): quite possibly this can not be changed.
- */
-public class TestPolymorphicDeserialization283 extends BaseMapTest
-{
- @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = ClassA.class)
- @JsonSubTypes({
- @JsonSubTypes.Type(name = "a", value = ClassA.class),
- @JsonSubTypes.Type(name = "b", value = ClassB.class)
- })
- public static interface SomeInterface
- {
- public String get();
- }
-
- public static class ClassA implements SomeInterface
- {
- @Override
- public String get()
- {
- return "A";
- }
- }
-
- public static class ClassB implements SomeInterface
- {
- @Override
- public String get()
- {
- return "B";
- }
- }
-
- public static class ClassC implements SomeInterface
- {
- @Override
- public String get()
- {
- return "C";
- }
- }
-
- @Test
- public void testName() throws Exception
- {
- ObjectMapper mapper = objectMapper();
-
- Assert.assertEquals("A", mapper.readValue("{\"type\": \"a\"}", SomeInterface.class).get());
- Assert.assertEquals("A", mapper.readValue("{}", SomeInterface.class).get());
- Assert.assertEquals("B", mapper.readValue("{\"type\": \"b\"}", SomeInterface.class).get());
- Assert.assertEquals("A", mapper.readValue("{\"type\": \"c\"}", SomeInterface.class).get());
-
- Assert.assertEquals("A", mapper.readValue("{\"type\": \"a\"}", ClassA.class).get());
- Assert.assertEquals("B", mapper.readValue("{\"type\": \"b\"}", ClassB.class).get());
- Assert.assertEquals("C", mapper.readValue("{\"type\": \"c\"}", ClassC.class).get());
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestSubtypes1311.java b/src/test/java/com/fasterxml/jackson/failing/TestSubtypes1311.java
deleted file mode 100644
index b3cfbc9..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/TestSubtypes1311.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonTypeName;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-// Not sure if this is valid, but for what it's worth, shows
-// the thing wrt [databind#1311]. May be removed if we can't find
-// improvements.
-public class TestSubtypes1311 extends com.fasterxml.jackson.databind.BaseMapTest
-{
- // [databind#1311]
- @JsonTypeInfo(property = "type", use = JsonTypeInfo.Id.NAME, defaultImpl = Factory1311ImplA.class)
- interface Factory1311 { }
-
- @JsonTypeName("implA")
- static class Factory1311ImplA implements Factory1311 { }
-
- @JsonTypeName("implB")
- static class Factory1311ImplB implements Factory1311 { }
-
- /*
- /**********************************************************
- /* Unit tests
- /**********************************************************
- */
-
- // [databind#1311]
- public void testSubtypeAssignmentCheck() throws Exception
- {
- ObjectMapper mapper = new ObjectMapper();
- mapper.registerSubtypes(Factory1311ImplA.class, Factory1311ImplB.class);
- Factory1311ImplB result = mapper.readValue("{\"type\":\"implB\"}", Factory1311ImplB.class);
- assertNotNull(result);
- assertEquals(Factory1311ImplB.class, result.getClass());
- }
-}
diff --git a/src/test/java/com/fasterxml/jackson/failing/TestUnwrappedWithCreator265.java b/src/test/java/com/fasterxml/jackson/failing/TestUnwrappedWithCreator265.java
deleted file mode 100644
index a65a678..0000000
--- a/src/test/java/com/fasterxml/jackson/failing/TestUnwrappedWithCreator265.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.fasterxml.jackson.failing;
-
-import com.fasterxml.jackson.annotation.*;
-
-import com.fasterxml.jackson.databind.*;
-
-public class TestUnwrappedWithCreator265 extends BaseMapTest
-{
- static class JAddress {
- protected String address;
- protected String city;
- protected String state;
-
- @JsonCreator
- public JAddress( @JsonProperty("address") String address,
- @JsonProperty("city") String city,
- @JsonProperty("state") String state
- ){
- this.address = address;
- this.city = city;
- this.state = state;
- }
-
- public String getAddress1() { return address; }
- public String getCity() { return city; }
- public String getState() { return state; }
- }
-
- static class JPerson {
- protected String _name;
- protected JAddress _address;
- protected String _alias;
-
- @JsonCreator
- public JPerson(@JsonProperty("name") String name,
- @JsonUnwrapped JAddress address,
- @JsonProperty("alias") String alias) {
- _name = name;
- _address = address;
- _alias = alias;
- }
-
- public String getName() {
- return _name;
- }
-
- @JsonUnwrapped public JAddress getAddress() {
- return _address;
- }
-
- public String getAlias() { return _alias; }
- }
-
- /*
- /**********************************************************
- /* Test methods
- /**********************************************************
- */
-
- // For [Issue#265] / [Scala#90]
- public void testUnwrappedWithCreator() throws Exception
- {
- JPerson person = new JPerson("MyName", new JAddress("main street", "springfield", "WA"), "bubba");
- ObjectMapper mapper = new ObjectMapper();
- String json = mapper.writeValueAsString(person);
- JPerson result = mapper.readValue(json, JPerson.class);
- assertNotNull(result);
- assertEquals(person._name, result._name);
- assertNotNull(result._address);
- assertEquals(person._address.city, result._address.city);
-
- // and see that round-tripping works
- assertEquals(json, mapper.writeValueAsString(result));
- }
-}
diff --git a/src/test/resources/com/fasterxml/jackson/databind/deser/DummyProcessableContent.json b/src/test/resources/com/fasterxml/jackson/databind/deser/DummyProcessableContent.json
new file mode 100644
index 0000000..8972151
--- /dev/null
+++ b/src/test/resources/com/fasterxml/jackson/databind/deser/DummyProcessableContent.json
@@ -0,0 +1,21 @@
+{
+ "_class":"com.fasterxml.jackson.databind.deser.GenericContent",
+ "innerObjects":
+ [
+ "java.util.ArrayList",
+ [
+ [
+ "com.fasterxml.jackson.databind.deser.HandleUnknowTypeIdTest$DummyContent",
+ {
+ "aField":"some value"
+ }
+ ],
+ [
+ "com.fasterxml.jackson.databind.deser.HandleUnknowTypeIdTest$AnInventedClassBeingNotOnTheClasspath",
+ {
+ "aField":"some value"
+ }
+ ]
+ ]
+ ]
+}
\ No newline at end of file