diff --git a/.github/workflows/dep_build_v2.yml b/.github/workflows/dep_build_v2.yml index 69dc7be9809..7e8159e0a54 100644 --- a/.github/workflows/dep_build_v2.yml +++ b/.github/workflows/dep_build_v2.yml @@ -11,12 +11,11 @@ permissions: jobs: build: # Do we want wide matrix build? For now, limited - runs-on: ${{ matrix.os }} + runs-on: 'ubuntu-latest' strategy: fail-fast: false matrix: java_version: ['8', '17', '21'] - os: ['ubuntu-22.04'] env: JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" steps: diff --git a/.github/workflows/dep_build_v3.yml b/.github/workflows/dep_build_v3.yml index 84767f14a02..7651c8ee61b 100644 --- a/.github/workflows/dep_build_v3.yml +++ b/.github/workflows/dep_build_v3.yml @@ -11,18 +11,17 @@ permissions: jobs: build: # Do we want wide matrix build? For now, limited - runs-on: ${{ matrix.os }} + runs-on: 'ubuntu-latest' strategy: fail-fast: false matrix: java_version: ['17', '21'] - os: ['ubuntu-22.04'] env: JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: master + ref: 3.x - name: Set up JDK uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c322a220c3..c1c53994e2a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,18 +1,11 @@ name: Build and Deploy Snapshot on: push: - branches: - - master - - "3.0" - - "2.19" + branches: ['2.*'] paths-ignore: - "README.md" - "release-notes/*" pull_request: - branches: - - master - - "3.0" - - "2.19" paths-ignore: - "README.md" - "release-notes/*" @@ -26,11 +19,11 @@ jobs: strategy: fail-fast: false matrix: - java_version: ['8', '11', '17', '21', '23'] - os: ['ubuntu-22.04'] + java_version: ['8', '11', '17', '21', '24'] + os: ['ubuntu-24.04'] include: - java_version: '8' - os: 'ubuntu-22.04' + os: 'ubuntu-24.04' release_build: 'R' - java_version: '8' os: 'windows-latest' @@ -45,7 +38,7 @@ jobs: distribution: 'temurin' java-version: ${{ matrix.java_version }} cache: 'maven' - server-id: sonatype-nexus-snapshots + server-id: central-snapshots server-username: CI_DEPLOY_USERNAME server-password: CI_DEPLOY_PASSWORD # See https://github.com/actions/setup-java/blob/v2/docs/advanced-usage.md#Publishing-using-Apache-Maven @@ -63,8 +56,8 @@ jobs: - name: Deploy snapshot if: ${{ matrix.release_build && github.event_name != 'pull_request' && endsWith(steps.projectVersion.outputs.version, '-SNAPSHOT') }} env: - CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} - CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} + CI_DEPLOY_USERNAME: ${{ secrets.CENTRAL_DEPLOY_USERNAME }} + CI_DEPLOY_PASSWORD: ${{ secrets.CENTRAL_DEPLOY_PASSWORD }} # MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} run: ./mvnw -B -q -ff -DskipTests -ntp source:jar deploy - name: Generate code coverage @@ -75,14 +68,14 @@ jobs: uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 with: token: ${{ secrets.CODECOV_TOKEN }} - file: ./target/site/jacoco/jacoco.xml + files: ./target/site/jacoco/jacoco.xml flags: unittests trigger-dep-build-v2: name: Trigger v2 dep builds needs: [build] - # Only for pushes to default branch - if: ${{ github.event_name == 'push' && github.ref_name == github.event.repository.default_branch }} + # Only for pushes to 2.x (next 2.x being developed) branch + if: ${{ github.event_name == 'push' && github.ref_name == '2.x' }} uses: ./.github/workflows/trigger_dep_builds_v2.yml secrets: token: ${{ secrets.REPO_DISPATCH }} @@ -90,8 +83,8 @@ jobs: trigger-dep-build-v3: name: Trigger v3 dep builds needs: [build] - # Only for pushes to default branch - if: ${{ github.event_name == 'push' && github.ref_name == 'master' }} + # Only for pushes to 3.x branch + if: ${{ github.event_name == 'push' && github.ref_name == '3.x' }} uses: ./.github/workflows/trigger_dep_builds_v3.yml secrets: token: ${{ secrets.REPO_DISPATCH }} diff --git a/.github/workflows/trigger_dep_builds_v2.yml b/.github/workflows/trigger_dep_builds_v2.yml index 2c31657de3f..9802f146e20 100644 --- a/.github/workflows/trigger_dep_builds_v2.yml +++ b/.github/workflows/trigger_dep_builds_v2.yml @@ -23,13 +23,14 @@ jobs: - 'FasterXML/jackson-dataformat-xml' - 'FasterXML/jackson-datatypes-collections' - 'FasterXML/jackson-datatypes-misc' + - 'FasterXML/jackson-datatype-hibernate' - 'FasterXML/jackson-datatype-joda' - 'FasterXML/jackson-module-kotlin' - 'FasterXML/jackson-module-scala' - - 'FasterXML/jackson-jaxrs-providers' - 'FasterXML/jackson-jakarta-rs-providers' - - 'FasterXML/jackson-integration-tests' + - 'FasterXML/jackson-jaxrs-providers' - 'FasterXML/jackson-benchmarks' + - 'FasterXML/jackson-integration-tests' steps: - name: Repository dispatch @@ -41,5 +42,5 @@ jobs: # Could push information on what was built but not yet client-payload: '{"version": "N/A" }' - name: Delay between dispatches - run: sleep 10s + run: sleep 8s shell: bash diff --git a/.github/workflows/trigger_dep_builds_v3.yml b/.github/workflows/trigger_dep_builds_v3.yml index 575bca646e4..e41edac7e10 100644 --- a/.github/workflows/trigger_dep_builds_v3.yml +++ b/.github/workflows/trigger_dep_builds_v3.yml @@ -41,5 +41,5 @@ jobs: # Could push information on what was built but not yet client-payload: '{"version": "N/A" }' - name: Delay between dispatches - run: sleep 10s + run: sleep 7s shell: bash diff --git a/pom.xml b/pom.xml index bf16259dfaf..1003d96021f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,12 +9,11 @@ com.fasterxml.jackson jackson-base - 2.19.0-SNAPSHOT + 2.19.3-SNAPSHOT - com.fasterxml.jackson.core jackson-databind - 2.19.0-SNAPSHOT + 2.19.3-SNAPSHOT jackson-databind jar General data-binding functionality for Jackson: works on core streaming API @@ -32,7 +31,7 @@ scm:git:git@github.com:FasterXML/jackson-databind.git scm:git:git@github.com:FasterXML/jackson-databind.git https://github.com/FasterXML/jackson-databind - HEAD + jackson-databind-2.19.0-rc2 @@ -55,7 +54,11 @@ 1.15.10 4.11.0 - true + + + false com.fasterxml.jackson.databind.*;version=${project.version} @@ -70,7 +73,7 @@ com.fasterxml.jackson.databind.cfg - 2024-09-27T01:57:15Z + 2025-07-18T17:49:25Z @@ -125,7 +128,7 @@ com.google.guava guava-testlib - 31.1-jre + 32.0.1-jre test @@ -163,13 +166,12 @@ org.junit.platform junit-platform-suite-engine - 1.10.2 test io.micronaut.test micronaut-test-type-pollution - 4.6.2 + 4.8.1 test @@ -177,10 +179,11 @@ + - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots + central-snapshots + Sonatype Central Portal (snapshots) + https://central.sonatype.com/repository/maven-snapshots false true @@ -264,8 +267,8 @@ - https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/2.18.2> - https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.18.2> + https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/2.18.4 + https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.18.4 diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index a0403cee4cc..1e8dc2213f0 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -321,6 +321,9 @@ Michal Letynski (mletynski@github) Jeff Schnitzer (stickfigure@github) * Suggested #504: Add `DeserializationFeature.USE_LONG_FOR_INTS` (2.6.0) + * Requested #4533: Add `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES` to disable + the "Java 8 date/time XYZ not supported by default" error + (2.19.0) Jerry Yang (islanderman@github) * Contributed #820: Add new method for `ObjectReader`, to bind from JSON Pointer position @@ -1857,6 +1860,12 @@ wrongwrong (@k163377) `@JsonDeserialize(keyUsing = ...)` is overwritten by the `KeyDeserializer` specified in the `ObjectMapper`. (2.18.3) + * Contributed fix for #5139: In `CollectionDeserializer`, `JsonSetter.contentNulls` + is sometimes ignored + (2.19.1) + * Contributed fix for #5202: #5202: `JsonSetter.contentNulls` ignored for `Object[]`, + `String[]` and `Collection` + (2.19.2) Bernd Ahlers (@bernd) * Reported #4742: Deserialization with Builder, External type id, `@JsonCreator` failing @@ -1889,6 +1898,14 @@ Zhen Lin Low (@zhenlin-pay2) when collecting bean properties, breaking AsExternalTypeDeserializer (2.18.3) +Fawzi Essam (@iifawzi) + * Contributed fix or #4628: `@JsonIgnore` and `@JsonProperty.access=READ_ONLY` + on Record property + (2.18.4) + * Contributed fix for #5049: Duplicate creator property "b" (index 0 vs 1) + on simple java record + (2.18.4) + Liam Feid (@fxshlein) * Contributed #1467: Support `@JsonUnwrapped` with `@JsonCreator` (2.19.0) @@ -1922,3 +1939,11 @@ Joren Inghelbrecht (@jin-harmoney) Will Paul (@dropofwill) * Contributed #4979: Allow default enums with `@JsonCreator` (2.19.0) + +Ryan Schmitt (@rschmitt) + * Contributed #5099: Fix regression in `ObjectNode.with()` + (2.19.0) + +Eddú Meléndez Gonzales (@eddumelendez) + * Reported #5215: `@JsonAnyGetter` serialization order change from 2.18.4 to 2.19.0 + (2.19.2) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 2f2782c2979..d73d2626d9f 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -4,7 +4,21 @@ Project: jackson-databind === Releases === ------------------------------------------------------------------------ -2.19.0 (not yet released) +2.19.2 (18-Jul-2025) + +#5202: `JsonSetter.contentNulls` ignored for `Object[]`, `String[]` + and `Collection` + (fix by @wrongwrong) +#5215: `@JsonAnyGetter` serialization order change from 2.18.4 to 2.19.0 + (reported by Eddú M) + (fix by Joo-Hyuk K) + +2.19.1 (13-Jun-2025) + +#5139: In `CollectionDeserializer`, `JsonSetter.contentNulls` is sometimes ignored + (contributed by @wrongwrong) + +2.19.0 (24-Apr-2025) #1467: Support `@JsonUnwrapped` with `@JsonCreator` (implementation by Liam F) @@ -18,8 +32,14 @@ Project: jackson-databind server and client side (requested by @qianlong) (contributed by Geoffrey G) +#3343: Allow BeanPropertyWriter Sub-classes to Override `get()` (remove `final`) + (requested by @alzimmermsft) #4388: Allow using `@JsonPropertyOrder` with "any" (`@JsonAnyGetter`) properties (fix by Joo-Hyuk K) +#4533: Add `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES` to disable the + "Java 8 date/time XYZ not supported by default" error + (requested by Jeff S) + (fix by Joo-Hyuk K) #4650: `PrimitiveArrayDeserializers` should deal with single String value if `DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY` enabled (reported, fix suggested by @eeren-bm) @@ -83,9 +103,21 @@ Project: jackson-databind (contributed by @pjfanning) #5052: Minor bug in `FirstCharBasedValidator.forFirstNameRule()`: returns `null` in non-default case +#5063: `SimpleModule` not registered due to `getTypeId()` returning an empty string + (reported by @seadbrane) #5069: Add copy-constructor for `MappingIterator` (contributed by @wrongwrong) +2.18.4 (06-May-2025) + +#4628: `@JsonIgnore` and `@JsonProperty.access=READ_ONLY` on Record property + ignored for deserialization + (reported by Sim Y-T) + (fix contributed by Fawzi E) +#5049: Duplicate creator property "b" (index 0 vs 1) on simple java record + (reported by @richard-melvin) + (fix contributed by Fawzi E) + 2.18.3 (28-Feb-2025) #4444: The `KeyDeserializer` specified in the class with `@JsonDeserialize(keyUsing = ...)` @@ -896,6 +928,13 @@ No changes since 2.13.2.1 but fixed Gradle Module Metadata ("module.json") via `AsNull` - Add `mvnw` wrapper +2.12.7.2 (02-May-2024) + +#3275: JDK 16 Illegal reflective access for `Throwable.setCause()` with + `PropertyNamingStrategy.UPPER_CAMEL_CASE` + (reported by Jason H) + (fix suggested by gsinghlulu@github) + 2.12.7.1 (12-Oct-2022) #3582: Add check in `BeanDeserializer._deserializeFromArray()` to prevent @@ -915,7 +954,7 @@ No changes since 2.13.2.1 but fixed Gradle Module Metadata ("module.json") #3305: ObjectMapper serializes `CharSequence` subtypes as POJO instead of as String (JDK 15+) (reported by stevenupton@github; fix suggested by Sergey C) -#3328: Possible DoS if using JDK serialization to serialize JsonNode +#3328: Possible DoS if using JDK serialization to serialize JsonNode [CVE-2021-46877] 2.12.5 (27-Aug-2021) diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java index f3652f9ac54..f3b8625bfa7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java +++ b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java @@ -539,7 +539,9 @@ public boolean canConvertToExactIntegral() { * Does NOT do any conversions for non-String value nodes; * for non-String values (ones for which {@link #isTextual} returns * false) null will be returned. - * For String values, null is never returned (but empty Strings may be) + * For String values, null is never returned (but empty Strings may be). + * + * IMPORTANT : Starting Jackson 3.0, this method will be named as {@code stringValue()}. * * @return Textual value this node contains, iff it is a textual * JSON node (comes from JSON String value entry) @@ -753,10 +755,10 @@ public long asLong(long defaultValue) { /** * Method that will try to convert value of this node to a Java double. * Numbers are coerced using default Java rules; booleans convert to 0.0 (false) - * and 1.0 (true), and Strings are parsed using default Java language integer + * and 1.0 (true), and Strings are parsed using default Java language {@code double} * parsing rules. *

- * If representation cannot be converted to an int (including structured types + * If representation cannot be converted to a {@code double} (including structured types * like Objects and Arrays), * default value of 0.0 will be returned; no exceptions are thrown. */ @@ -767,10 +769,10 @@ public double asDouble() { /** * Method that will try to convert value of this node to a Java double. * Numbers are coerced using default Java rules; booleans convert to 0.0 (false) - * and 1.0 (true), and Strings are parsed using default Java language integer + * and 1.0 (true), and Strings are parsed using default Java language {@code double} * parsing rules. *

- * If representation cannot be converted to an int (including structured types + * If representation cannot be converted to a {@code double} (including structured types * like Objects and Arrays), * specified defaultValue will be returned; no exceptions are thrown. */ diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java index 75ee3eebd4a..3a053193c89 100644 --- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java @@ -661,7 +661,21 @@ public enum MapperFeature implements ConfigFeature * * @since 2.19 */ - REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS(true) + REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS(true), + + /** + * Feature that determines what happens if Java 8 {@link java.time} (and + * other related Java 8 date/time types) are to be serialized or deserialized, but there + * are no registered handlers for them. + * If enabled, an exception is thrown (to indicate problem, a solution for which is + * to register {@code jackson-datatype-jsr310} module); if disabled, the value is + * serialized and/or deserialized using regular POJO ("Bean") (de)serialization. + *

+ * Feature is enabled by default. + * + * @since 2.19 + */ + REQUIRE_HANDLERS_FOR_JAVA8_TIMES(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 15d8441db91..a044e2da0c6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/Module.java +++ b/src/main/java/com/fasterxml/jackson/databind/Module.java @@ -53,7 +53,7 @@ public abstract class Module * instances are considered to be of same type, for purpose of preventing * multiple registrations of "same type of" module * (see {@link com.fasterxml.jackson.databind.MapperFeature#IGNORE_DUPLICATE_MODULE_REGISTRATIONS}) - * If `null` is returned, every instance is considered unique. + * If {@code null} is returned, every instance is considered unique. * If non-null value is returned, equality of id Objects is used to check whether * modules should be considered to be "of same type" *

diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 140872e85fb..2c2dee418ba 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -658,7 +658,7 @@ protected ObjectMapper(ObjectMapper src, JsonFactory factory) if (reg == null) { _registeredModuleTypes = null; } else { - _registeredModuleTypes = new LinkedHashSet(reg); + _registeredModuleTypes = new LinkedHashSet<>(reg); } } @@ -896,7 +896,7 @@ public ObjectMapper registerModule(Module module) if (_registeredModuleTypes == null) { // plus let's keep them in order too, easier to debug or expose // in registration order if that matter - _registeredModuleTypes = new LinkedHashSet(); + _registeredModuleTypes = new LinkedHashSet<>(); } // try adding; if already had it, should skip if (!_registeredModuleTypes.add(typeId)) { @@ -1503,19 +1503,25 @@ public ObjectMapper setSerializerProvider(DefaultSerializerProvider p) { } /** - * Accessor for the "blueprint" (or, factory) instance, from which instances - * are created by calling {@link DefaultSerializerProvider#createInstance}. - * 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. + * Internal {@link SerializerProvider} accessor used by databind package to get the "blueprint" + * (or, factory) instance, from which actual instances are created by calling {@link DefaultSerializerProvider#createInstance}. + * NOT TO BE USED BY APPLICATION CODE directly: only databind-managed {@code SerializerProvider} + * instances should be used. + *

+ * Implementation note: returned instance cannot be directly used as it is not properly + * configured or initialized. */ public SerializerProvider getSerializerProvider() { return _serializerProvider; } /** - * Accessor for constructing and returning a {@link SerializerProvider} - * instance that may be used for accessing serializers. This is same as + * Internal {@link SerializerProvider} accessor used by databind package to get a + * properly initialized instance to pass to various handlers. + * NOT TO BE USED BY APPLICATION CODE directly: only databind-managed {@code SerializerProvider} + * instances should be used. + *

+ * Implementation note: this is same as * calling {@link #getSerializerProvider}, and calling {@code createInstance()} * on it. * @@ -2562,6 +2568,8 @@ public ObjectMapper setTimeZone(TimeZone tz) { * construction, see {@link com.fasterxml.jackson.databind.json.JsonMapper#builder} * (and {@link MapperBuilder#defaultAttributes}). * + * @see ContextAttributes for details on setting default attributes. + * * @since 2.13 */ public ObjectMapper setDefaultAttributes(ContextAttributes attrs) { @@ -3055,6 +3063,23 @@ public T readValue(JsonParser p, Class valueType) return (T) _readValue(getDeserializationConfig(), p, _typeFactory.constructType(valueType)); } + /** + * Reads the value from the given JSON content and automatically detects the class type. + * + * @param the type of the object to read + * @param p the JsonParser + * @param reified don't pass any values here. It's a trick to detect the class type. + * @return the deserialized object + * @throws JsonProcessingException if there is a problem processing the JSON + */ + public T readObject(JsonParser p, T... reified) throws IOException { + if (reified.length > 0) { + throw new IllegalArgumentException("Please don't pass any values here. Java will detect class automatically."); + } + + return readValue(p, _typeFactory.constructType(getClassOf(reified))); + } + /** * Method to deserialize JSON content into a Java type, reference * to which is passed as argument. Type is passed using so-called @@ -3855,6 +3880,34 @@ public T readValue(String content, Class valueType) return readValue(content, _typeFactory.constructType(valueType)); } + /** + * Utility method to get the class type from a varargs array. + * + * @param the generic type + * @param array the varargs array + * @return the class of the array component + */ + private static Class getClassOf(T[] array) { + return (Class) array.getClass().getComponentType(); + } + + /** + * Reads the value from the given JSON content and automatically detects the class type. + * + * @param the type of the object to read + * @param content the JSON string + * @param reified don't pass any values here. It's a trick to detect the class type. + * @return the deserialized object + * @throws JsonProcessingException if there is a problem processing the JSON + */ + public T readObject(String content, T... reified) throws JsonProcessingException { + if (reified.length > 0) { + throw new IllegalArgumentException("Please don't pass any values here. Java will detect class automatically."); + } + + return readValue(content, _typeFactory.constructType(getClassOf(reified))); + } + /** * Method to deserialize JSON content from given JSON content String. * diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java index fa45a4029cf..4a9335882d5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java @@ -66,7 +66,7 @@ public class ObjectReader protected final DefaultDeserializationContext _context; /** - * Factory used for constructing {@link JsonGenerator}s + * Factory used for constructing {@link JsonParser}s */ protected final JsonFactory _parserFactory; diff --git a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java index e8482e2024d..e5f4d69e2e3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java +++ b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java @@ -187,6 +187,8 @@ public String nameForConstructorParameter(MapperConfig config, AnnotatedParam */ /** + * Replaced by {@link PropertyNamingStrategies.NamingBase}. + * * @deprecated Since 2.12 deprecated. See * databind#2715 * for reasons for deprecation. 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 b4715fae558..9e682baa00e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java @@ -5,6 +5,7 @@ /** * Helper class used for storing and accessing per-call attributes. * Storage is two-layered: at higher precedence, we have actual per-call + * ({@code ObjectMapper.readValue} or {@code ObjectMapper.writeValue}) * attributes; and at lower precedence, default attributes that may be * defined for Object readers and writers. *

@@ -15,11 +16,24 @@ * sharing, by creating new copies instead of modifying state. * This allows sharing of default values without per-call copying, but * requires two-level lookup on access. + *

+ * To set default attributes, use {@link #withSharedAttributes(Map)} + * or {@link #withSharedAttribute(Object, Object)}, starting with + * "empty" instance (see {@link #getEmpty()}). For example: + *

+ *   ContextAttributes attrs = ContextAttributes.getEmpty()
+ *     .withSharedAttribute("foo", "bar")
+ *     .withSharedAttribute("attr2", "value2");
+ *
* * @since 2.3 */ public abstract class ContextAttributes { + /** + * Accessor for an empty instance of {@link ContextAttributes}: usable + * as-is, or as a starting point for building up a set of default attributes. + */ public static ContextAttributes getEmpty() { return Impl.getEmpty(); } @@ -30,8 +44,27 @@ public static ContextAttributes getEmpty() { /********************************************************** */ + /** + * Fluent factory method for creating a new instance with an additional + * shared attribute. + * + * @param key Name of the attribute to add + * @param value Value of the attribute to add; may be null, in which case + * attribute will be removed if it already exists. + * + * @return New instance of {@link ContextAttributes} that has specified change. + */ public abstract ContextAttributes withSharedAttribute(Object key, Object value); + /** + * Fluent factory method for creating a new instance with specified set of + * shared attributes. + * Any shared attributes that already exist will be replaced + * + * @param attributes Map of shared attributes to add, replacing any existing ones. + * + * @return New instance of {@link ContextAttributes} that has specified shared attributes. + */ public abstract ContextAttributes withSharedAttributes(Map attributes); public abstract ContextAttributes withoutSharedAttribute(Object key); @@ -115,7 +148,7 @@ public ContextAttributes withSharedAttribute(Object key, Object value) Map m; // need to cover one special case, since EMPTY uses Immutable map: if (this == EMPTY) { - m = new HashMap(8); + m = new HashMap<>(8); } else { m = _copy(_shared); } diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java index 64b9a0a5f16..de4e065b182 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java @@ -453,6 +453,8 @@ public B annotationIntrospector(AnnotationIntrospector intr) { * @param attrs Default instance to use: may not be {@code null}. * * @return This Builder instance to allow call chaining + * + * @see ContextAttributes for details on constructing default attributes. */ public B defaultAttributes(ContextAttributes attrs) { _mapper.setDefaultAttributes(attrs); 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 5460cbc6f33..882d2d90a2f 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 @@ -341,12 +341,10 @@ protected Collection _deserializeFromArray(JsonParser p, Deserialization // [databind#631]: Assign current value, to be accessible by custom serializers p.assignCurrentValue(result); - JsonDeserializer valueDes = _valueDeserializer; // Let's offline handling of values with Object Ids (simplifies code here) - if (valueDes.getObjectIdReader() != null) { + if (_valueDeserializer.getObjectIdReader() != null) { return _deserializeWithObjectId(p, ctxt, result); } - final TypeDeserializer typeDeser = _valueTypeDeserializer; JsonToken t; while ((t = p.nextToken()) != JsonToken.END_ARRAY) { try { @@ -355,16 +353,21 @@ protected Collection _deserializeFromArray(JsonParser p, Deserialization if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); + value = null; } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); } + if (value == null) { - _tryToAddNull(p, ctxt, result); - continue; + value = _nullProvider.getNullValue(ctxt); + + // _skipNullValues is checked by _tryToAddNull. + if (value == null) { + _tryToAddNull(p, ctxt, result); + continue; + } } + result.add(value); /* 17-Dec-2017, tatu: should not occur at this level... @@ -400,8 +403,6 @@ protected final Collection handleNonArray(JsonParser p, DeserializationC if (!canWrap) { return (Collection) ctxt.handleUnexpectedToken(_containerType, p); } - JsonDeserializer valueDes = _valueDeserializer; - final TypeDeserializer typeDeser = _valueTypeDeserializer; Object value; @@ -411,15 +412,19 @@ protected final Collection handleNonArray(JsonParser p, DeserializationC if (_skipNullValues) { return result; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); + value = null; } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); } + if (value == null) { - _tryToAddNull(p, ctxt, result); - return result; + value = _nullProvider.getNullValue(ctxt); + + // _skipNullValues is checked by _tryToAddNull. + if (value == null) { + _tryToAddNull(p, ctxt, result); + return result; + } } } catch (Exception e) { boolean wrap = ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS); @@ -444,8 +449,6 @@ protected Collection _deserializeWithObjectId(JsonParser p, Deserializat // [databind#631]: Assign current value, to be accessible by custom serializers p.assignCurrentValue(result); - final JsonDeserializer valueDes = _valueDeserializer; - final TypeDeserializer typeDeser = _valueTypeDeserializer; CollectionReferringAccumulator referringAccumulator = new CollectionReferringAccumulator(_containerType.getContentType().getRawClass(), result); @@ -453,18 +456,21 @@ protected Collection _deserializeWithObjectId(JsonParser p, Deserializat 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); + value = null; } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); } - if (value == null && _skipNullValues) { - continue; + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + if (value == null && _skipNullValues) { + continue; + } } referringAccumulator.add(value); } catch (UnresolvedForwardReference reference) { @@ -481,6 +487,22 @@ protected Collection _deserializeWithObjectId(JsonParser p, Deserializat return result; } + /** + * Deserialize the content of the collection. + * If _valueTypeDeserializer is null, use _valueDeserializer.deserialize; if non-null, + * use _valueDeserializer.deserializeWithType to deserialize value. + * This method only performs deserialization and does not consider _skipNullValues, _nullProvider, etc. + * @since 2.19.1 + */ + protected Object _deserializeNoNullChecks(JsonParser p,DeserializationContext ctxt) + throws IOException + { + if (_valueTypeDeserializer == null) { + return _valueDeserializer.deserialize(p, ctxt); + } + return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); + } + /** * {@code java.util.TreeSet} (and possibly other {@link Collection} types) does not * allow addition of {@code null} values, so isolate handling here. 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 260cb6e7468..e0e4bb2a474 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 @@ -269,9 +269,6 @@ public EnumMap deserialize(JsonParser p, DeserializationContext ctxt, // [databind#631]: Assign current value, to be accessible by custom deserializers p.assignCurrentValue(result); - final JsonDeserializer valueDes = _valueDeserializer; - final TypeDeserializer typeDeser = _valueTypeDeserializer; - String keyStr; if (p.isExpectedStartObjectToken()) { keyStr = p.nextFieldName(); @@ -311,11 +308,17 @@ public EnumMap deserialize(JsonParser p, DeserializationContext ctxt, if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); + value = null; } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); + } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } } } catch (Exception e) { return wrapAndThrow(ctxt, e, result, keyStr); @@ -405,11 +408,17 @@ public EnumMap _deserializeUsingProperties(JsonParser p, DeserializationCon if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (_valueTypeDeserializer == null) { - value = _valueDeserializer.deserialize(p, ctxt); + value = null; } else { - value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); + value = _deserializeNoNullChecks(p, ctxt); + } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } } } catch (Exception e) { wrapAndThrow(ctxt, e, _containerType.getRawClass(), keyName); @@ -426,4 +435,20 @@ public EnumMap _deserializeUsingProperties(JsonParser p, DeserializationCon return null; } } + + /** + * Deserialize the content of the map. + * If _valueTypeDeserializer is null, use _valueDeserializer.deserialize; if non-null, + * use _valueDeserializer.deserializeWithType to deserialize value. + * This method only performs deserialization and does not consider _skipNullValues, _nullProvider, etc. + * @since 2.19.2 + */ + protected Object _deserializeNoNullChecks(JsonParser p, DeserializationContext ctxt) + throws IOException + { + if (_valueTypeDeserializer == null) { + return _valueDeserializer.deserialize(p, ctxt); + } + return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); + } } 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 26521b58afc..d69a3e1ef4f 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 @@ -139,12 +139,9 @@ public EnumSetDeserializer withResolved(JsonDeserializer deser, @Override public boolean isCachable() { // One caveat: content deserializer should prevent caching - if (_enumType.getValueHandler() != null) { - return false; - } - return true; + return _enumType.getValueHandler() == null; } - + @Override // since 2.12 public LogicalType logicalType() { return LogicalType.Collection; 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 97010cea701..98875853a6b 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 @@ -343,9 +343,11 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, if (ignorals != null) { Set ignoresToAdd = ignorals.findIgnoredForDeserialization(); if (!ignoresToAdd.isEmpty()) { - ignored = (ignored == null) ? new HashSet() : new HashSet(ignored); - for (String str : ignoresToAdd) { - ignored.add(str); + if (ignored == null) { + ignored = new HashSet<>(ignoresToAdd); + } else { + ignored = new HashSet(ignored); + ignored.addAll(ignoresToAdd); } } } @@ -500,8 +502,6 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, @SuppressWarnings("unchecked") public final Class getMapClass() { return (Class>) _containerType.getRawClass(); } - @Override public JavaType getValueType() { return _containerType; } - /* /********************************************************** /* Internal methods, non-merging deserialization @@ -511,12 +511,8 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, protected final Map _readAndBind(JsonParser p, DeserializationContext ctxt, Map result) throws IOException { - final KeyDeserializer keyDes = _keyDeserializer; - final JsonDeserializer valueDes = _valueDeserializer; - final TypeDeserializer typeDeser = _valueTypeDeserializer; - MapReferringAccumulator referringAccumulator = null; - boolean useObjectId = valueDes.getObjectIdReader() != null; + boolean useObjectId = _valueDeserializer.getObjectIdReader() != null; if (useObjectId) { referringAccumulator = new MapReferringAccumulator(_containerType.getContentType().getRawClass(), result); @@ -537,7 +533,7 @@ protected final Map _readAndBind(JsonParser p, DeserializationCon } for (; keyStr != null; keyStr = p.nextFieldName()) { - Object key = keyDes.deserializeKey(keyStr, ctxt); + Object key = _keyDeserializer.deserializeKey(keyStr, ctxt); // And then the value... JsonToken t = p.nextToken(); if ((_inclusionChecker != null) && _inclusionChecker.shouldIgnore(keyStr)) { @@ -551,12 +547,19 @@ protected final Map _readAndBind(JsonParser p, DeserializationCon if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); + value = null; } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); + } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } } + if (useObjectId) { referringAccumulator.put(key, value); } else { @@ -582,10 +585,8 @@ protected final Map _readAndBind(JsonParser p, DeserializationCon protected final Map _readAndBindStringKeyMap(JsonParser p, DeserializationContext ctxt, Map result) throws IOException { - final JsonDeserializer valueDes = _valueDeserializer; - final TypeDeserializer typeDeser = _valueTypeDeserializer; MapReferringAccumulator referringAccumulator = null; - boolean useObjectId = (valueDes.getObjectIdReader() != null); + boolean useObjectId = (_valueDeserializer.getObjectIdReader() != null); if (useObjectId) { referringAccumulator = new MapReferringAccumulator(_containerType.getContentType().getRawClass(), result); } @@ -617,12 +618,19 @@ protected final Map _readAndBindStringKeyMap(JsonParser p, Deseri if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); + value = null; } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } + if (useObjectId) { referringAccumulator.put(key, value); } else { @@ -649,9 +657,6 @@ public Map _deserializeUsingCreator(JsonParser p, Deserialization // null -> no ObjectIdReader for Maps (yet?) PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, null); - final JsonDeserializer valueDes = _valueDeserializer; - final TypeDeserializer typeDeser = _valueTypeDeserializer; - String key; if (p.isExpectedStartObjectToken()) { key = p.nextFieldName(); @@ -692,11 +697,17 @@ public Map _deserializeUsingCreator(JsonParser p, Deserialization if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); + value = null; } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); + } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } } } catch (Exception e) { wrapAndThrow(ctxt, e, _containerType.getRawClass(), key); @@ -726,7 +737,6 @@ public Map _deserializeUsingCreator(JsonParser p, Deserialization protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt, Map result) throws IOException { - final KeyDeserializer keyDes = _keyDeserializer; final JsonDeserializer valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; @@ -748,7 +758,7 @@ protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt, } for (; keyStr != null; keyStr = p.nextFieldName()) { - Object key = keyDes.deserializeKey(keyStr, ctxt); + Object key = _keyDeserializer.deserializeKey(keyStr, ctxt); // And then the value... JsonToken t = p.nextToken(); if ((_inclusionChecker != null) && _inclusionChecker.shouldIgnore(keyStr)) { @@ -772,11 +782,18 @@ protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt, } else { value = valueDes.deserializeWithType(p, ctxt, typeDeser, old); } - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } + if (value != old) { result.put(key, value); } @@ -839,11 +856,18 @@ protected final void _readAndUpdateStringKeyMap(JsonParser p, DeserializationCon } else { value = valueDes.deserializeWithType(p, ctxt, typeDeser, old); } - } else if (typeDeser == null) { - value = valueDes.deserialize(p, ctxt); } else { - value = valueDes.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } + if (value != old) { result.put(key, value); } @@ -853,6 +877,22 @@ protected final void _readAndUpdateStringKeyMap(JsonParser p, DeserializationCon } } + /** + * Deserialize the content of the map. + * If _valueTypeDeserializer is null, use _valueDeserializer.deserialize; if non-null, + * use _valueDeserializer.deserializeWithType to deserialize value. + * This method only performs deserialization and does not consider _skipNullValues, _nullProvider, etc. + * @since 2.19.2 + */ + protected Object _deserializeNoNullChecks(JsonParser p, DeserializationContext ctxt) + throws IOException + { + if (_valueTypeDeserializer == null) { + return _valueDeserializer.deserialize(p, ctxt); + } + return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); + } + /** * @since 2.14 */ @@ -895,11 +935,11 @@ private void handleUnresolvedReference(DeserializationContext ctxt, private final static class MapReferringAccumulator { private final Class _valueType; - private Map _result; + private final Map _result; /** * A list of {@link MapReferring} to maintain ordering. */ - private List _accumulator = new ArrayList(); + private final List _accumulator = new ArrayList(); public MapReferringAccumulator(Class valueType, Map result) { _valueType = valueType; 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 20a728e7512..d6e194c6347 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 @@ -201,7 +201,6 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) Object[] chunk = buffer.resetAndStart(); int ix = 0; JsonToken t; - final TypeDeserializer typeDeser = _elementTypeDeserializer; try { while ((t = p.nextToken()) != JsonToken.END_ARRAY) { @@ -212,12 +211,19 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = _elementDeserializer.deserialize(p, ctxt); + value = null; } else { - value = _elementDeserializer.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); + } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } } + if (ix >= chunk.length) { chunk = buffer.appendCompletedChunk(chunk); ix = 0; @@ -269,7 +275,6 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt, int ix = intoValue.length; Object[] chunk = buffer.resetAndStart(intoValue, ix); JsonToken t; - final TypeDeserializer typeDeser = _elementTypeDeserializer; try { while ((t = p.nextToken()) != JsonToken.END_ARRAY) { @@ -279,12 +284,19 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt, if (_skipNullValues) { continue; } - value = _nullProvider.getNullValue(ctxt); - } else if (typeDeser == null) { - value = _elementDeserializer.deserialize(p, ctxt); + value = null; } else { - value = _elementDeserializer.deserializeWithType(p, ctxt, typeDeser); + value = _deserializeNoNullChecks(p, ctxt); + } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } } + if (ix >= chunk.length) { chunk = buffer.appendCompletedChunk(chunk); ix = 0; @@ -320,7 +332,7 @@ protected Byte[] deserializeFromBase64(JsonParser p, DeserializationContext ctxt // But then need to convert to wrappers Byte[] result = new Byte[b.length]; for (int i = 0, len = b.length; i < len; ++i) { - result[i] = Byte.valueOf(b[i]); + result[i] = b[i]; } return result; } @@ -352,7 +364,7 @@ protected Object handleNonArray(JsonParser p, DeserializationContext ctxt) if (_skipNullValues) { return _emptyValue; } - value = _nullProvider.getNullValue(ctxt); + value = null; } else { if (p.hasToken(JsonToken.VALUE_STRING)) { String textValue = p.getText(); @@ -375,12 +387,17 @@ protected Object handleNonArray(JsonParser p, DeserializationContext ctxt) // if coercion failed, we can still add it to a list } - if (_elementTypeDeserializer == null) { - value = _elementDeserializer.deserialize(p, ctxt); - } else { - value = _elementDeserializer.deserializeWithType(p, ctxt, _elementTypeDeserializer); + value = _deserializeNoNullChecks(p, ctxt); + } + + if (value == null) { + value = _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + return _emptyValue; } } + // Ok: bit tricky, since we may want T[], not just Object[] Object[] result; @@ -392,5 +409,20 @@ protected Object handleNonArray(JsonParser p, DeserializationContext ctxt) result[0] = value; return result; } -} + /** + * Deserialize the content of the map. + * If _elementTypeDeserializer is null, use _elementDeserializer.deserialize; if non-null, + * use _elementDeserializer.deserializeWithType to deserialize value. + * This method only performs deserialization and does not consider _skipNullValues, _nullProvider, etc. + * @since 2.19.2 + */ + protected Object _deserializeNoNullChecks(JsonParser p, DeserializationContext ctxt) + throws IOException + { + if (_elementTypeDeserializer == null) { + return _elementDeserializer.deserialize(p, ctxt); + } + return _elementDeserializer.deserializeWithType(p, ctxt, _elementTypeDeserializer); + } +} 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 77a8e76e272..4fa863bee16 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 @@ -763,7 +763,7 @@ protected final int _parseIntPrimitive(DeserializationContext ctxt, String text) { try { if (text.length() > 9) { - ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length()); + _streamReadConstraints(ctxt).validateIntegerLength(text.length()); long l = NumberInput.parseLong(text); if (_intOverflow(l)) { Number v = (Number) ctxt.handleWeirdStringValue(Integer.TYPE, text, @@ -837,7 +837,7 @@ protected final Integer _parseInteger(DeserializationContext ctxt, String text) { try { if (text.length() > 9) { - ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length()); + _streamReadConstraints(ctxt).validateIntegerLength(text.length()); long l = NumberInput.parseLong(text); if (_intOverflow(l)) { return (Integer) ctxt.handleWeirdStringValue(Integer.class, text, @@ -916,7 +916,7 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct */ protected final long _parseLongPrimitive(DeserializationContext ctxt, String text) throws IOException { - ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length()); + _streamReadConstraints(ctxt).validateIntegerLength(text.length()); try { return NumberInput.parseLong(text); } catch (IllegalArgumentException iae) { } @@ -982,7 +982,7 @@ protected final Long _parseLong(JsonParser p, DeserializationContext ctxt, */ protected final Long _parseLong(DeserializationContext ctxt, String text) throws IOException { - ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length()); + _streamReadConstraints(ctxt).validateIntegerLength(text.length()); try { return NumberInput.parseLong(text); } catch (IllegalArgumentException iae) { } @@ -1069,7 +1069,7 @@ protected final float _parseFloatPrimitive(DeserializationContext ctxt, String t { // 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate: if (NumberInput.looksLikeValidNumber(text)) { - ctxt.getParser().streamReadConstraints().validateFPLength(text.length()); + _streamReadConstraints(ctxt).validateFPLength(text.length()); try { return NumberInput.parseFloat(text, false); } catch (IllegalArgumentException iae) { } @@ -1087,7 +1087,7 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext { // 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate: if (NumberInput.looksLikeValidNumber(text)) { - ctxt.getParser().streamReadConstraints().validateFPLength(text.length()); + p.streamReadConstraints().validateFPLength(text.length()); try { return NumberInput.parseFloat(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER)); } catch (IllegalArgumentException iae) { } @@ -2328,4 +2328,11 @@ protected Number _nonNullNumber(Number n) { } return n; } + + // @since 2.19.3: NPE check for older code that doesn't get JsonParser + protected StreamReadConstraints _streamReadConstraints(DeserializationContext ctxt) { + JsonParser p = ctxt.getParser(); + // 29-Jun-2020, tatu: Should not be null, but let's be defensive + return (p == null) ? StreamReadConstraints.defaults() : p.streamReadConstraints(); + } } 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 043ad2c1e12..aafdc802307 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 @@ -40,14 +40,14 @@ public final class StringArrayDeserializer /** * Value serializer to use, if not the standard one (which is inlined) */ - protected JsonDeserializer _elementDeserializer; + private final JsonDeserializer _elementDeserializer; /** * Handler we need for dealing with null values as elements * * @since 2.9 */ - protected final NullValueProvider _nullProvider; + private final NullValueProvider _nullProvider; /** * Specific override for this instance (from proper, or global per-type overrides) @@ -56,7 +56,7 @@ public final class StringArrayDeserializer * * @since 2.7 */ - protected final Boolean _unwrapSingle; + private final Boolean _unwrapSingle; /** * Marker flag set if the _nullProvider indicates that all null @@ -64,14 +64,14 @@ public final class StringArrayDeserializer * * @since 2.9 */ - protected final boolean _skipNullValues; + private final boolean _skipNullValues; public StringArrayDeserializer() { this(null, null, null); } @SuppressWarnings("unchecked") - protected StringArrayDeserializer(JsonDeserializer deser, + private StringArrayDeserializer(JsonDeserializer deser, NullValueProvider nuller, Boolean unwrapSingle) { super(String[].class); _elementDeserializer = (JsonDeserializer) deser; @@ -162,10 +162,17 @@ public String[] deserialize(JsonParser p, DeserializationContext ctxt) throws IO if (_skipNullValues) { continue; } - value = (String) _nullProvider.getNullValue(ctxt); } else { value = _parseString(p, ctxt, _nullProvider); } + + if (value == null) { + value = (String) _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } } if (ix >= chunk.length) { chunk = buffer.appendCompletedChunk(chunk); @@ -184,7 +191,7 @@ public String[] deserialize(JsonParser p, DeserializationContext ctxt) throws IO /** * Offlined version used when we do not use the default deserialization method. */ - protected final String[] _deserializeCustom(JsonParser p, DeserializationContext ctxt, + private String[] _deserializeCustom(JsonParser p, DeserializationContext ctxt, String[] old) throws IOException { final ObjectBuffer buffer = ctxt.leaseObjectBuffer(); @@ -219,13 +226,22 @@ protected final String[] _deserializeCustom(JsonParser p, DeserializationContext if (_skipNullValues) { continue; } - value = (String) _nullProvider.getNullValue(ctxt); + value = null; } else { value = deser.deserialize(p, ctxt); } } else { value = deser.deserialize(p, ctxt); } + + if (value == null) { + value = (String) _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } + if (ix >= chunk.length) { chunk = buffer.appendCompletedChunk(chunk); ix = 0; @@ -283,10 +299,17 @@ public String[] deserialize(JsonParser p, DeserializationContext ctxt, if (_skipNullValues) { return NO_STRINGS; } - value = (String) _nullProvider.getNullValue(ctxt); } else { value = _parseString(p, ctxt, _nullProvider); } + + if (value == null) { + value = (String) _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } } if (ix >= chunk.length) { chunk = buffer.appendCompletedChunk(chunk); 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 a008a6017ca..acfb21f32a6 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 @@ -37,20 +37,20 @@ public final class StringCollectionDeserializer * Value deserializer to use, if NOT the standard one * (if it is, will be null). */ - protected final JsonDeserializer _valueDeserializer; + private final JsonDeserializer _valueDeserializer; // // Instance construction settings: /** * Instantiator used in case custom handling is needed for creation. */ - protected final ValueInstantiator _valueInstantiator; + private final ValueInstantiator _valueInstantiator; /** * Deserializer that is used iff delegate-based creator is * to be used for deserializing from JSON Object. */ - protected final JsonDeserializer _delegateDeserializer; + private final JsonDeserializer _delegateDeserializer; // NOTE: no PropertyBasedCreator, as JSON Arrays have no properties @@ -67,7 +67,7 @@ public StringCollectionDeserializer(JavaType collectionType, } @SuppressWarnings("unchecked") - protected StringCollectionDeserializer(JavaType collectionType, + private StringCollectionDeserializer(JavaType collectionType, ValueInstantiator valueInstantiator, JsonDeserializer delegateDeser, JsonDeserializer valueDeser, NullValueProvider nuller, Boolean unwrapSingle) @@ -78,7 +78,7 @@ protected StringCollectionDeserializer(JavaType collectionType, _delegateDeserializer = (JsonDeserializer) delegateDeser; } - protected StringCollectionDeserializer withResolved(JsonDeserializer delegateDeser, + private StringCollectionDeserializer withResolved(JsonDeserializer delegateDeser, JsonDeserializer valueDeser, NullValueProvider nuller, Boolean unwrapSingle) { @@ -107,6 +107,7 @@ public LogicalType logicalType() { /* Validation, post-processing /********************************************************** */ + @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException @@ -212,10 +213,18 @@ public Collection deserialize(JsonParser p, DeserializationContext ctxt, if (_skipNullValues) { continue; } - value = (String) _nullProvider.getNullValue(ctxt); } else { value = _parseString(p, ctxt, _nullProvider); } + + if (value == null) { + value = (String) _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } + result.add(value); } } catch (Exception e) { @@ -245,13 +254,22 @@ private Collection deserializeUsingCustom(JsonParser p, DeserializationC if (_skipNullValues) { continue; } - value = (String) _nullProvider.getNullValue(ctxt); + value = null; } else { value = deser.deserialize(p, ctxt); } } else { value = deser.deserialize(p, ctxt); } + + if (value == null) { + value = (String) _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + continue; + } + } + result.add(value); } } catch (Exception e) { @@ -296,7 +314,7 @@ private final Collection handleNonArray(JsonParser p, DeserializationCon if (_skipNullValues) { return result; } - value = (String) _nullProvider.getNullValue(ctxt); + value = null; } else { if (p.hasToken(JsonToken.VALUE_STRING)) { String textValue = p.getText(); @@ -325,6 +343,15 @@ private final Collection handleNonArray(JsonParser p, DeserializationCon throw JsonMappingException.wrapWithPath(e, result, result.size()); } } + + if (value == null) { + value = (String) _nullProvider.getNullValue(ctxt); + + if (value == null && _skipNullValues) { + return result; + } + } + result.add(value); return result; } 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 aef8152da6e..2f04c84daed 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java @@ -77,7 +77,7 @@ public final class AnnotatedClass final protected Class _primaryMixIn; /** - * Flag that indicates whether (fulll) annotation resolution should + * Flag that indicates whether (full) annotation resolution should * occur: starting with 2.11 is disabled for JDK container types. * * @since 2.11 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 cb41ca9953d..8ad07a53a8a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -501,6 +501,40 @@ protected void collectAll() _collected = true; } + /** + * [databind#5215] JsonAnyGetter Serializer behavior change from 2.18.4 to 2.19.0 + * Put anyGetter in the end, before actual sorting further down {@link POJOPropertiesCollector#_sortProperties(Map)} + */ + private Map _putAnyGettersInTheEnd( + Map sortedProps) + { + AnnotatedMember anyAccessor; + + if (_anyGetters != null) { + anyAccessor = _anyGetters.getFirst(); + } else if (_anyGetterField != null) { + anyAccessor = _anyGetterField.getFirst(); + } else { + return sortedProps; + } + + // Here we'll use insertion-order preserving map, since possible alphabetic + // sorting already done earlier + Map newAll = new LinkedHashMap<>(sortedProps.size() * 2); + POJOPropertyBuilder anyGetterProp = null; + for (POJOPropertyBuilder prop : sortedProps.values()) { + if (prop.hasFieldOrGetter(anyAccessor)) { + anyGetterProp = prop; + } else { + newAll.put(prop.getName(), prop); + } + } + if (anyGetterProp != null) { + newAll.put(anyGetterProp.getName(), anyGetterProp); + } + return newAll; + } + /* /********************************************************************** /* Property introspection: Fields @@ -1390,6 +1424,12 @@ protected void _renameProperties(Map props) Map.Entry entry = it.next(); POJOPropertyBuilder prop = entry.getValue(); + // 10-Apr-2025: [databind#4628] skip properties that are marked to be ignored + // TODO: we are using implicit name, is that ok? + if (_ignoredPropertyNames != null && _ignoredPropertyNames.contains(prop.getName())) { + continue; + } + Collection l = prop.findExplicitNames(); // no explicit names? Implicit one is fine as is @@ -1582,14 +1622,16 @@ protected void _sortProperties(Map props) Map all; // Need to (re)sort alphabetically? if (sortAlpha) { - all = new TreeMap(); + all = new TreeMap<>(); } else { - all = new LinkedHashMap(size+size); + all = new LinkedHashMap<>(size+size); } - + // First, handle sorting caller expects: for (POJOPropertyBuilder prop : props.values()) { all.put(prop.getName(), prop); } + all = _putAnyGettersInTheEnd(all); + Map ordered = new LinkedHashMap<>(size+size); // Ok: primarily by explicit order if (propertyOrder != null) { @@ -1808,7 +1850,7 @@ protected boolean _replaceCreatorProperty(List creatorPrope POJOPropertyBuilder prop) { final AnnotatedParameter ctorParam = prop.getConstructorParameter(); - if (creatorProperties != null) { + if (creatorProperties != null && ctorParam != null) { for (int i = 0, len = creatorProperties.size(); i < len; ++i) { POJOPropertyBuilder cprop = creatorProperties.get(i); if (cprop != null) { 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 e0c6f497df0..088a7f8b308 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java @@ -1,5 +1,6 @@ package com.fasterxml.jackson.databind.introspect; +import java.lang.reflect.Member; import java.util.*; import java.util.stream.Collectors; @@ -764,6 +765,24 @@ protected int _setterPriority(AnnotatedMethod m) return 2; } + // @since 2.19.2 + public boolean hasFieldOrGetter(AnnotatedMember member) { + return _hasAccessor(_fields, member) || _hasAccessor(_getters, member); + } + + private boolean _hasAccessor(Linked node, + AnnotatedMember memberToMatch) + { + // AnnotatedXxx are not canonical, but underlying JDK Members are: + final Member rawMemberToMatch = memberToMatch.getMember(); + for (; node != null; node = node.next) { + if (node.value.getMember() == rawMemberToMatch) { + return true; + } + } + return false; + } + /* /********************************************************** /* Implementations of refinement accessors @@ -877,19 +896,19 @@ public JsonProperty.Access withMember(AnnotatedMember member) { */ public void addField(AnnotatedField a, PropertyName name, boolean explName, boolean visible, boolean ignored) { - _fields = new Linked(a, _fields, name, explName, visible, ignored); + _fields = new Linked<>(a, _fields, name, explName, visible, ignored); } public void addCtor(AnnotatedParameter a, PropertyName name, boolean explName, boolean visible, boolean ignored) { - _ctorParameters = new Linked(a, _ctorParameters, name, explName, visible, ignored); + _ctorParameters = new Linked<>(a, _ctorParameters, name, explName, visible, ignored); } public void addGetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) { - _getters = new Linked(a, _getters, name, explName, visible, ignored); + _getters = new Linked<>(a, _getters, name, explName, visible, ignored); } public void addSetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) { - _setters = new Linked(a, _setters, name, explName, visible, ignored); + _setters = new Linked<>(a, _setters, name, explName, visible, ignored); } /** 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 262f618c2aa..8116c361e08 100644 --- a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java +++ b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java @@ -15,8 +15,8 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; /** - * Vanilla {@link com.fasterxml.jackson.databind.Module} implementation that allows registration - * of serializers and deserializers, bean serializer + * Simple {@link com.fasterxml.jackson.databind.Module} implementation that allows + * registration of serializers and deserializers, serializer * and deserializer modifiers, registration of subtypes and mix-ins * as well as some other commonly * needed aspects (addition of custom {@link AbstractTypeResolver}s, @@ -24,10 +24,11 @@ *

* NOTE: that [de]serializers are registered as "default" [de]serializers. * As a result, they will have lower priority than the ones indicated through annotations on - * both Class and property-associated annotations -- for example, + * both {@code Class} and property-associated annotations -- for example, * {@link com.fasterxml.jackson.databind.annotation.JsonDeserialize}.
- * In cases where both module-based [de]serializers and annotation-based [de]serializers are registered, - * the [de]serializer specified by the annotation will take precedence. + * In cases where both module-based [de]serializers and annotation-based + * [de]serializers are registered, the [de]serializer specified by annotations + * will take precedence. *

* NOTE: although it is not expected that sub-types should need to * override {@link #setupModule(SetupContext)} method, if they choose @@ -35,7 +36,7 @@ * to ensure that registration works as expected. *

* WARNING: when registering {@link JsonSerializer}s and {@link JsonDeserializer}s, - * only type erased {@code Class} is compared: this means that usually you should + * 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 @@ -130,14 +131,19 @@ public SimpleModule() { // note: generate different name for direct instantiation, sub-classing; // this to avoid collision in former case while still addressing // [databind#3110] - _name = (getClass() == SimpleModule.class) - ? "SimpleModule-"+MODULE_ID_SEQ.getAndIncrement() - : getClass().getName(); + _name = _generateName(getClass()); _version = Version.unknownVersion(); // 07-Jun-2021, tatu: [databind#3110] Not passed explicitly so... _hasExplicitName = false; } + // @since 2.19 + private static String _generateName(Class cls) { + return (cls == SimpleModule.class) + ? "SimpleModule-"+MODULE_ID_SEQ.getAndIncrement() + : cls.getName(); + } + /** * Convenience constructor that will default version to * {@link Version#unknownVersion()}. @@ -148,10 +154,21 @@ public SimpleModule(String name) { /** * Convenience constructor that will use specified Version, - * including name from {@link Version#getArtifactId()}. + * including name from {@link Version#getArtifactId()} + * (if available -- if not, will generate). */ public SimpleModule(Version version) { - this(version.getArtifactId(), version); + // 07-Apr-2025, tatu: [databind#5063]: check that we actually have a name; + // if not, generate it + String maybeName = version.getArtifactId(); + // Only considered explicit if name is non-null and not empty (i.e. not generated) + _hasExplicitName = (maybeName != null) && !maybeName.isEmpty(); + if (_hasExplicitName) { + _name = maybeName; + } else { + _name = _generateName(getClass()); + } + _version = version; } /** @@ -213,7 +230,7 @@ public SimpleModule(String name, Version version, @Override public Object getTypeId() { - // 07-Jun-2021, tatu: [databind#3110] Return Type Id if name was + // 07-Jun-2021, tatu: [databind#3110] Return name as Type Id if name was // explicitly given if (_hasExplicitName) { return _name; @@ -227,7 +244,8 @@ public Object getTypeId() return _name; } // And for what it is worth, this should usually do the same and we could - // in fact always just return `_name`. But leaving as-is for now. + // in fact always just return `_name`. But leaving as-is for now: + // will essentially return {@code getClass().getName()}. return super.getTypeId(); } 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 6cf1ed2f492..bd6234279e9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java +++ b/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java @@ -87,7 +87,8 @@ public ObjectNode with(String exprOrProperty) { .getClass().getName() + "`)"); } ObjectNode result = objectNode(); - return _put(exprOrProperty, result); + _put(exprOrProperty, result); + return result; } @Override 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 829d7c36e4f..1530ba939e1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java @@ -759,7 +759,8 @@ public void serializeAsOmittedField(Object bean, JsonGenerator gen, */ @Override public void serializeAsElement(Object bean, JsonGenerator gen, - SerializerProvider prov) throws Exception { + SerializerProvider prov) throws Exception + { // inlined 'get()' final Object value = (_accessorMethod == null) ? _field.get(bean) : _accessorMethod.invoke(bean, (Object[]) null); @@ -818,7 +819,8 @@ public void serializeAsElement(Object bean, JsonGenerator gen, */ @Override public void serializeAsPlaceholder(Object bean, JsonGenerator gen, - SerializerProvider prov) throws Exception { + SerializerProvider prov) throws Exception + { if (_nullSerializer != null) { _nullSerializer.serialize(null, gen, prov); } else { @@ -835,7 +837,8 @@ public void serializeAsPlaceholder(Object bean, JsonGenerator gen, // Also part of BeanProperty implementation @Override public void depositSchemaProperty(JsonObjectFormatVisitor v, - SerializerProvider provider) throws JsonMappingException { + SerializerProvider provider) throws JsonMappingException + { if (v != null) { if (isRequired()) { v.property(this); @@ -861,7 +864,8 @@ public void depositSchemaProperty(JsonObjectFormatVisitor v, @Override @Deprecated public void depositSchemaProperty(ObjectNode propertiesNode, - SerializerProvider provider) throws JsonMappingException { + SerializerProvider provider) throws JsonMappingException + { JavaType propType = getSerializationType(); // 03-Dec-2010, tatu: SchemaAware REALLY should use JavaType, but alas // it doesn't... @@ -891,7 +895,8 @@ public void depositSchemaProperty(ObjectNode propertiesNode, protected JsonSerializer _findAndAddDynamic( PropertySerializerMap map, Class type, - SerializerProvider provider) throws JsonMappingException { + SerializerProvider provider) throws JsonMappingException + { PropertySerializerMap.SerializerAndMapResult result; if (_nonTrivialBaseType != null) { JavaType t = provider.constructSpecializedType(_nonTrivialBaseType, @@ -910,14 +915,14 @@ protected JsonSerializer _findAndAddDynamic( /** * Method that can be used to access value of the property this Object * describes, from given bean instance. - *

- * Note: method is final as it should not need to be overridden -- rather, - * calling method(s) ({@link #serializeAsField}) should be overridden to - * change the behavior + *

+ * NOTE: was {@code final} until Jackson 2.19 */ - public final Object get(Object bean) throws Exception { - return (_accessorMethod == null) ? _field.get(bean) : _accessorMethod - .invoke(bean, (Object[]) null); + public Object get(Object bean) throws Exception + { + return (_accessorMethod == null) + ? _field.get(bean) + : _accessorMethod.invoke(bean, (Object[]) null); } /** 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 6763ee4fd4e..19086fae197 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/BeanUtil.java @@ -295,6 +295,8 @@ public static String stdManglePropertyName(final String basename, final int offs * "well-known" types for which there would be a datatype module; and if so, * return appropriate failure message to give to caller. * + * @return error message to use, or null if failure is not needed. + * * @since 2.19 */ public static String checkUnsupportedType(MapperConfig config, JavaType type) { @@ -312,6 +314,11 @@ public static String checkUnsupportedType(MapperConfig config, JavaType type) if (type.isTypeOrSubTypeOf(Throwable.class)) { return null; } + failFeature = MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES; + final boolean fail = (config == null) || config.isEnabled(failFeature); + if (!fail) { + return null; + } typeName = "Java 8 date/time"; moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"; } else if (isJodaTimeClass(className)) { @@ -332,7 +339,7 @@ public static String checkUnsupportedType(MapperConfig config, JavaType type) typeName, ClassUtil.getTypeDescription(type), moduleName); if (failFeature != null) { str = String.format("%s (or disable `MapperFeature.%s`)", - str, MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS.name()); + str, failFeature.name()); } return str; } diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonSerDeser188Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonSerDeser188Test.java index 5fae3134722..6407074c59d 100644 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonSerDeser188Test.java +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonSerDeser188Test.java @@ -48,9 +48,10 @@ public void serialize(String value, JsonGenerator jgen, SerializerProvider provi } } - @SuppressWarnings("serial") static class PrefixStringDeserializer extends StdScalarDeserializer { + private static final long serialVersionUID = 1L; + protected PrefixStringDeserializer() { super(String.class); } diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java index e647ec5bc69..df36027efd0 100644 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java @@ -18,8 +18,10 @@ record RecordWithIgnore(int id, @JsonIgnore String name) { record RecordWithIgnoreJsonProperty(int id, @JsonIgnore @JsonProperty("name") String name) { } + record RecordWithIgnoreJsonPropertyDifferentName(int id, @JsonIgnore @JsonProperty("name2") String name) { + } + record RecordWithIgnoreAccessor(int id, String name) { - @JsonIgnore @Override public String name() { @@ -65,8 +67,16 @@ public void testSerializeJsonIgnoreAndJsonPropertyRecord() throws Exception { @Test public void testDeserializeJsonIgnoreAndJsonPropertyRecord() throws Exception { - RecordWithIgnoreJsonProperty value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", RecordWithIgnoreJsonProperty.class); - assertEquals(new RecordWithIgnoreJsonProperty(123, "Bob"), value); + RecordWithIgnoreJsonProperty value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", + RecordWithIgnoreJsonProperty.class); + assertEquals(new RecordWithIgnoreJsonProperty(123, null), value); + } + + @Test + public void testDeserializeJsonIgnoreRecordWithDifferentName() throws Exception { + RecordWithIgnoreJsonPropertyDifferentName value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", + RecordWithIgnoreJsonPropertyDifferentName.class); + assertEquals(new RecordWithIgnoreJsonPropertyDifferentName(123, null), value); } /* @@ -77,10 +87,11 @@ public void testDeserializeJsonIgnoreAndJsonPropertyRecord() throws Exception { @Test public void testSerializeJsonIgnoreAccessorRecord() throws Exception { - String json = MAPPER.writeValueAsString(new RecordWithIgnoreAccessor(123, "Bob")); - assertEquals("{\"id\":123}", json); + assertEquals("{\"id\":123}", + MAPPER.writeValueAsString(new RecordWithIgnoreAccessor(123, "Bob"))); } + // [databind#4628] @Test public void testDeserializeJsonIgnoreAccessorRecord() throws Exception { RecordWithIgnoreAccessor expected = new RecordWithIgnoreAccessor(123, null); diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithReadOnlyTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithReadOnlyTest.java index f7803757aa8..56da3dca581 100644 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithReadOnlyTest.java +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithReadOnlyTest.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; public class RecordWithReadOnlyTest extends DatabindTestUtil { @@ -53,6 +55,27 @@ public RecordWithReadOnlyAllAndNoArgConstructor() { } } + + static class ReadOnly5049Pojo + { + protected String a, b; + + ReadOnly5049Pojo( + @JsonProperty(value = "a", access = JsonProperty.Access.READ_ONLY) String a, + @JsonProperty(value = "b", access = JsonProperty.Access.READ_ONLY) String b) { + this.a = a; + this.b = b; + } + + public String getA() { return a; } + public String getB() { return b; } + } + + record ReadOnly5049Record( + @JsonProperty(value = "a", access = JsonProperty.Access.READ_ONLY) String a, + @JsonProperty(value = "b", access = JsonProperty.Access.READ_ONLY) String b) { + } + private final ObjectMapper MAPPER = newJsonMapper(); /* @@ -79,25 +102,41 @@ public void testDeserializeReadOnlyProperty() throws Exception { /********************************************************************** */ + // [databind#4826] @Test public void testSerializeReadOnlyNamedProperty() throws Exception { String json = MAPPER.writeValueAsString(new RecordWithReadOnlyNamedProperty(123, "Bob")); assertEquals(a2q("{'id':123,'name':'Bob'}"), json); } - /** - * Currently documents a bug where a property was NOT ignored during deserialization - * if given an explicit name. - * Also reproducible in 2.14.x. - */ + // [databind#4826] @Test public void testDeserializeReadOnlyNamedProperty() throws Exception { RecordWithReadOnlyNamedProperty value = MAPPER.readValue(a2q("{'id':123,'name':'Bob'}"), RecordWithReadOnlyNamedProperty.class); + assertEquals(new RecordWithReadOnlyNamedProperty(123, null), value); + } + + // [databind#5049] + @Test + void testRoundtripPOJO5049() throws Exception + { + String json = MAPPER.writeValueAsString(new ReadOnly5049Pojo("hello", "world")); + ReadOnly5049Pojo pojo = MAPPER.readerFor(ReadOnly5049Pojo.class).readValue(json); + assertNotNull(pojo); + assertNull(pojo.a); + assertNull(pojo.b); + } - // BUG: should be `null` instead of "Bob" - // 01-Apr-2025, tatu: Should be in "tofix", then? - assertEquals(new RecordWithReadOnlyNamedProperty(123, "Bob"), value); + // [databind#5049] + @Test + void testRoundtripRecord5049() throws Exception + { + String json = MAPPER.writeValueAsString(new ReadOnly5049Record("hello", "world")); + ReadOnly5049Record record = MAPPER.readValue(json, ReadOnly5049Record.class); + assertNotNull(record); + assertNull(record.a()); + assertNull(record.b()); } /* diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordUnwrapped5115Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordUnwrapped5115Test.java new file mode 100644 index 00000000000..f8533def537 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordUnwrapped5115Test.java @@ -0,0 +1,82 @@ +package com.fasterxml.jackson.databind.tofix; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// [databind#5115] @JsonUnwrapped can't handle name collision #5115 +public class RecordUnwrapped5115Test + extends DatabindTestUtil +{ + record FooRecord5115(int a, int b) { } + record BarRecordFail5115(@JsonUnwrapped FooRecord5115 a, int c) { } + record BarRecordPass5115(@JsonUnwrapped FooRecord5115 foo, int c) { } + + static class FooPojo5115 { + public int a; + public int b; + } + + static class BarPojo5115 { + @JsonUnwrapped + public FooPojo5115 a; + public int c; + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + @Test + void unwrappedPojoShouldRoundTrip() throws Exception + { + BarPojo5115 input = new BarPojo5115(); + input.a = new FooPojo5115(); + input.c = 4; + input.a.a = 1; + input.a.b = 2; + + String json = MAPPER.writeValueAsString(input); + BarPojo5115 output = MAPPER.readValue(json, BarPojo5115.class); + + assertEquals(4, output.c); + assertEquals(1, output.a.a); + assertEquals(2, output.a.b); + } + + @Test + void unwrappedRecordShouldRoundTripPass() throws Exception + { + BarRecordPass5115 input = new BarRecordPass5115(new FooRecord5115(1, 2), 3); + + // Serialize + String json = MAPPER.writeValueAsString(input); + + // Deserialize (currently fails) + BarRecordPass5115 output = MAPPER.readValue(json, BarRecordPass5115.class); + + // Should match after bug is fixed + assertEquals(input, output); + } + + @JacksonTestFailureExpected + @Test + void unwrappedRecordShouldRoundTrip() throws Exception + { + BarRecordFail5115 input = new BarRecordFail5115(new FooRecord5115(1, 2), 3); + + // Serialize + String json = MAPPER.writeValueAsString(input); + + // Once the bug is fixed, this assertion will pass and the + // @JacksonTestFailureExpected annotation can be removed. + BarRecordFail5115 output = MAPPER.readValue(json, BarRecordFail5115.class); + + // Should match after bug is fixed + assertEquals(input, output); + } + +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordWithJsonIgnoredMethod5184Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordWithJsonIgnoredMethod5184Test.java new file mode 100644 index 00000000000..d9199d31403 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordWithJsonIgnoredMethod5184Test.java @@ -0,0 +1,91 @@ +package com.fasterxml.jackson.databind.tofix; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class RecordWithJsonIgnoredMethod5184Test + extends DatabindTestUtil +{ + record TestData5184(@JsonProperty("test_property") String value) { + @JsonIgnore + public Optional getValue() { + return Optional.ofNullable(value); + } + } + + record TestData5184Alternate(@JsonProperty("test_property") String value) { + @JsonIgnore + public Optional optionalValue() { + return Optional.ofNullable(value); + } + } + + static final class TestData5184Class { + private final String value; + + public TestData5184Class(@JsonProperty("test_property") String value) { + this.value = value; + } + + @JsonIgnore + public Optional getValue() { + return Optional.ofNullable(value); + } + } + + private static final ObjectMapper MAPPER = newJsonMapper(); + + @JacksonTestFailureExpected + @Test + void should_deserialize_json_to_test_data() throws Exception { + String json = """ + {"test_property":"test value"} + """; + + var testData = MAPPER.readValue(json, TestData5184.class); + + assertThat(testData.value()).isEqualTo("test value"); + } + + @Test + void should_deserialize_json_to_test_data_class() throws Exception { + String json = """ + {"test_property":"test value"} + """; + + var testData = MAPPER.readValue(json, TestData5184Class.class); + + assertThat(testData.getValue()).contains("test value"); + } + + @Test + void should_deserialize_json_to_test_data_alternate() throws Exception { + String json = """ + {"test_property":"test value"} + """; + + var testData = MAPPER.readValue(json, TestData5184Alternate.class); + + assertThat(testData.value()).isEqualTo("test value"); + } + + @Test + void should_not_deserialize_wrong_json_model_to_test_data() throws Exception { + String json = """ + {"value":"test value"} + """; + + TestData5184 testData = MAPPER.readValue(json, TestData5184.class); + + assertThat(testData.value()).isNull(); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordWithReadOnly5049Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordWithReadOnly5049Test.java deleted file mode 100644 index 0e38e822583..00000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/tofix/RecordWithReadOnly5049Test.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.fasterxml.jackson.databind.tofix; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; -import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -public class RecordWithReadOnly5049Test extends DatabindTestUtil -{ - record ReadOnly5049Record( - @JsonProperty(value = "a", access = JsonProperty.Access.READ_ONLY) String a, - @JsonProperty(value = "b", access = JsonProperty.Access.READ_ONLY) String b) { - } - - static class ReadOnly5049Pojo - { - protected String a, b; - - ReadOnly5049Pojo( - @JsonProperty(value = "a", access = JsonProperty.Access.READ_ONLY) String a, - @JsonProperty(value = "b", access = JsonProperty.Access.READ_ONLY) String b) { - this.a = a; - this.b = b; - } - - public String getA() { return a; } - public String getB() { return b; } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - @Test - void testRoundtripPOJO() throws Exception - { - String json = MAPPER.writeValueAsString(new ReadOnly5049Pojo("hello", "world")); - //System.err.println("JSON/pojo: "+json); - ReadOnly5049Pojo pojo = MAPPER.readerFor(ReadOnly5049Pojo.class).readValue(json); - assertNotNull(pojo); - assertNull(pojo.a); - assertNull(pojo.b); - } - - @JacksonTestFailureExpected - @Test - void testRoundtripRecord() throws Exception - { - // json = MAPPER.writeValueAsString(new ReadOnly5049Record("hello", "world")); - String json = "{\"a\":\"hello\",\"b\":\"world\"}"; - ReadOnly5049Record record = MAPPER.readValue(json, ReadOnly5049Record.class); - assertNotNull(record); - assertNull(record.a()); - assertNull(record.b()); - } -} diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java index a249682b769..3dd48db6db8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java @@ -385,6 +385,16 @@ public void testJsonFactoryLinkage() assertSame(m, f.getCodec()); } + @Test + public void testAutoDetectClasses() throws Exception + { + ObjectMapper m = new ObjectMapper(); + final String JSON = "{ \"x\" : 3 }"; + + Bean bean = m.readObject(JSON); + assertNotNull(bean); + } + @Test public void testProviderConfig() throws Exception { diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/JacksonTypesDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/JacksonTypesDeserTest.java index 922cd34a041..45b3cc8ce2b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/JacksonTypesDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/JacksonTypesDeserTest.java @@ -82,6 +82,50 @@ public void testTokenBufferWithSample() throws Exception } } + @Test + public void testTokenBufferWithSequenceWithAutoDetectClass() throws Exception + { + final ObjectMapper mapper = jsonMapperBuilder() + .disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) + .build(); + + // and then sequence of other things + JsonParser p = mapper.createParser("[ 32, [ 1 ], \"abc\", { \"a\" : true } ]"); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + TokenBuffer buf = mapper.readObject(p); + + // check manually... + JsonParser bufParser = buf.asParser(); + assertToken(JsonToken.VALUE_NUMBER_INT, bufParser.nextToken()); + assertEquals(32, bufParser.getIntValue()); + assertNull(bufParser.nextToken()); + + // then bind to another + buf = mapper.readObject(p); + bufParser = buf.asParser(); + assertToken(JsonToken.START_ARRAY, bufParser.nextToken()); + assertToken(JsonToken.VALUE_NUMBER_INT, bufParser.nextToken()); + assertEquals(1, bufParser.getIntValue()); + assertToken(JsonToken.END_ARRAY, bufParser.nextToken()); + assertNull(bufParser.nextToken()); + + // third one, with automatic binding + buf = mapper.readObject(p); + String str = mapper.readObject(buf.asParser()); + assertEquals("abc", str); + + // and ditto for last one + buf = mapper.readValue(p, TokenBuffer.class); + Map map = mapper.readObject(buf.asParser()); + assertEquals(1, map.size()); + assertEquals(Boolean.TRUE, map.get("a")); + + assertEquals(JsonToken.END_ARRAY, p.nextToken()); + assertNull(p.nextToken()); + } + @SuppressWarnings("resource") @Test public void testTokenBufferWithSequence() throws Exception diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CollectionDeserializer5139Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CollectionDeserializer5139Test.java new file mode 100644 index 00000000000..725e57ce7af --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CollectionDeserializer5139Test.java @@ -0,0 +1,54 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// For [databind#5139] +public class CollectionDeserializer5139Test +{ + static class Dst { + private List list; + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + } + + @Test + public void nullsFailTest() { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL)) + .build(); + + assertThrows( + InvalidNullException.class, + () -> mapper.readValue("{\"list\":[\"\"]}", new TypeReference(){}) + ); + } + + @Test + public void nullsSkipTest() throws Exception { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) + .build(); + + Dst dst = mapper.readValue("{\"list\":[\"\"]}", new TypeReference() {}); + + assertTrue(dst.getList().isEmpty()); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumMapDeserializer5165Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumMapDeserializer5165Test.java new file mode 100644 index 00000000000..adf48cd02a5 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumMapDeserializer5165Test.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.util.EnumMap; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.opentest4j.AssertionFailedError; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// For [databind#5165] +public class EnumMapDeserializer5165Test +{ + public enum MyEnum { + FOO + } + + static class Dst { + private EnumMap map; + + public EnumMap getMap() { + return map; + } + + public void setMap(EnumMap map) { + this.map = map; + } + } + + @Test + public void nullsFailTest() { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL)) + .build(); + + assertThrows( + InvalidNullException.class, + () -> mapper.readValue("{\"map\":{\"FOO\":\"\"}}", new TypeReference(){}) + ); + } + + @Test + public void nullsSkipTest() throws Exception { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) + .build(); + + Dst dst = mapper.readValue("{\"map\":{\"FOO\":\"\"}}", new TypeReference() {}); + + assertTrue(dst.getMap().isEmpty()); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeserializer5165Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeserializer5165Test.java new file mode 100644 index 00000000000..82f1cec0081 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/MapDeserializer5165Test.java @@ -0,0 +1,54 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// For [databind#5165] +public class MapDeserializer5165Test +{ + static class Dst { + private Map map; + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } + + @Test + public void nullsFailTest() { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL)) + .build(); + + assertThrows( + InvalidNullException.class, + () -> mapper.readValue("{\"map\":{\"key\":\"\"}}", new TypeReference(){}) + ); + } + + @Test + public void nullsSkipTest() throws Exception { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) + .build(); + + Dst dst = mapper.readValue("{\"map\":{\"key\":\"\"}}", new TypeReference() {}); + + assertTrue(dst.getMap().isEmpty()); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/ObjectArrayDeserializer5165Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/ObjectArrayDeserializer5165Test.java new file mode 100644 index 00000000000..7e77004fb1a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/ObjectArrayDeserializer5165Test.java @@ -0,0 +1,45 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// For [databind#5165] +public class ObjectArrayDeserializer5165Test +{ + static class Dst { + public Integer[] array; + } + + @Test + public void nullsFailTest() { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL)) + .build(); + + // NOTE! Relies on default coercion of "" into `null` for `Integer`s... + assertThrows( + InvalidNullException.class, + () -> mapper.readValue("{\"array\":[\"\"]}", Dst.class) + ); + } + + @Test + public void nullsSkipTest() throws Exception { + ObjectMapper mapper = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) + .build(); + + Dst dst = mapper.readValue("{\"array\":[\"\"]}", Dst.class); + // NOTE! Relies on default coercion of "" into `null` for `Integer`s... + assertEquals(0, dst.array.length, "Null values should be skipped"); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/StringArrayDeserializer5165Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/StringArrayDeserializer5165Test.java new file mode 100644 index 00000000000..fd18fa68bae --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/StringArrayDeserializer5165Test.java @@ -0,0 +1,81 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; + +import com.fasterxml.jackson.core.JsonParser; + +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// For [databind#5165] +public class StringArrayDeserializer5165Test +{ + static class Dst { + public String[] array; + } + + // Custom deserializer that converts empty strings to null + static class EmptyStringToNullDeserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + + public EmptyStringToNullDeserializer() { + super(String.class); + } + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + if (value != null && value.isEmpty()) { + return null; + } + return value; + } + } + + private ObjectMapper createMapperWithCustomDeserializer() { + SimpleModule module = new SimpleModule() + .addDeserializer(String.class, new EmptyStringToNullDeserializer()); + + return JsonMapper.builder() + .addModule(module) + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL)) + .build(); + } + + @Test + public void nullsFailTest() { + ObjectMapper mapper = createMapperWithCustomDeserializer(); + + assertThrows( + InvalidNullException.class, + () -> mapper.readValue("{\"array\":[\"\"]}", Dst.class) + ); + } + + @Test + public void nullsSkipTest() throws Exception { + SimpleModule module = new SimpleModule() + .addDeserializer(String.class, new EmptyStringToNullDeserializer()); + + ObjectMapper mapper = JsonMapper.builder() + .addModule(module) + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) + .build(); + + Dst dst = mapper.readValue("{\"array\":[\"\"]}", Dst.class); + + assertEquals(0, dst.array.length, "Null values should be skipped"); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/StringCollectionDeserializer5165Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/StringCollectionDeserializer5165Test.java new file mode 100644 index 00000000000..11cbf28229c --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/StringCollectionDeserializer5165Test.java @@ -0,0 +1,80 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.io.IOException; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// For [databind#5165] +public class StringCollectionDeserializer5165Test +{ + static class Dst { + public List list; + } + + // Custom deserializer that converts empty strings to null + static class EmptyStringToNullDeserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + + public EmptyStringToNullDeserializer() { + super(String.class); + } + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + if (value != null && value.isEmpty()) { + return null; + } + return value; + } + } + + private ObjectMapper createMapperWithCustomDeserializer() { + SimpleModule module = new SimpleModule() + .addDeserializer(String.class, new EmptyStringToNullDeserializer()); + + return JsonMapper.builder() + .addModule(module) + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL)) + .build(); + } + + @Test + public void nullsFailTest() { + ObjectMapper mapper = createMapperWithCustomDeserializer(); + + assertThrows( + InvalidNullException.class, + () -> mapper.readValue("{\"list\":[\"\"]}", Dst.class) + ); + } + + @Test + public void nullsSkipTest() throws Exception { + SimpleModule module = new SimpleModule() + .addDeserializer(String.class, new EmptyStringToNullDeserializer()); + + ObjectMapper mapper = JsonMapper.builder() + .addModule(module) + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) + .build(); + + Dst dst = mapper.readValue("{\"list\":[\"\"]}", Dst.class); + + assertTrue(dst.list.isEmpty(), "Null values should be skipped"); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/interop/Java8Datetime4533Test.java b/src/test/java/com/fasterxml/jackson/databind/interop/Java8Datetime4533Test.java new file mode 100644 index 00000000000..a317cf32583 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/interop/Java8Datetime4533Test.java @@ -0,0 +1,119 @@ +package com.fasterxml.jackson.databind.interop; + +import java.time.LocalDate; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +public class Java8Datetime4533Test + extends DatabindTestUtil +{ + private final ObjectMapper MAPPER = newJsonMapper(); + + private final ObjectMapper LENIENT_MAPPER = JsonMapper.builder() + .disable(MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES) + .disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS) + .build(); + + @Test + public void testPreventSerialization() throws Exception + { + // [databind#4533]: prevent accidental serialization of Java 8 date/time types + // as POJOs, without Java 8 date/time module: + _testPreventSerialization(java.time.LocalDateTime.now()); + _testPreventSerialization(java.time.LocalDate.now()); + _testPreventSerialization(java.time.LocalTime.now()); + _testPreventSerialization(java.time.OffsetDateTime.now()); + _testPreventSerialization(java.time.ZonedDateTime.now()); + } + + private void _testPreventSerialization(Object value) throws Exception + { + try { + String json = MAPPER.writeValueAsString(value); + fail("Should not pass, wrote out as\n: "+json); + } catch (com.fasterxml.jackson.databind.exc.InvalidDefinitionException e) { + verifyException(e, "Java 8 date/time type `"+value.getClass().getName() + +"` not supported by default"); + verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\""); + verifyException(e, "(or disable `MapperFeature.%s`)", + MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES.name()); + } + } + + @Test + public void testPreventDeserialization() throws Exception + { + // [databind#4533]: prevent accidental deserialization of Java 8 date/time types + // as POJOs, without Java 8 date/time module: + _testPreventDeserialization(java.time.LocalDateTime.class); + _testPreventDeserialization(java.time.LocalDate.class); + _testPreventDeserialization(java.time.LocalTime.class); + _testPreventDeserialization(java.time.OffsetDateTime.class); + _testPreventDeserialization(java.time.ZonedDateTime.class); + } + + private void _testPreventDeserialization(Class value) throws Exception + { + try { + Object result = MAPPER.readValue(" 0 ", value); + fail("Not expecting to pass, resulted in: "+result); + } catch (com.fasterxml.jackson.databind.exc.InvalidDefinitionException e) { + verifyException(e, "Java 8 date/time type `"+value.getName() + +"` not supported by default"); + verifyException(e, "add Module \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\""); + verifyException(e, "(or disable `MapperFeature.%s`)", + MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES.name()); + } + } + + @Test + public void testLenientSerailization() throws Exception + { + _testLenientSerialization(java.time.LocalDateTime.now()); + _testLenientSerialization(java.time.LocalDate.now()); + _testLenientSerialization(java.time.LocalTime.now()); + _testLenientSerialization(java.time.OffsetDateTime.now()); + + // Except ZonedDateTime serialization fails with ... + try { + _testLenientSerialization(java.time.ZonedDateTime.now()); + fail("Should not pass, wrote out as\n: "+java.time.ZonedDateTime.now()); + } catch (JsonMappingException e) { + verifyException(e, "Class com.fasterxml.jackson.databind.ser.BeanPropertyWriter"); + verifyException(e, "with modifiers \"public\""); + } + } + + private void _testLenientSerialization(Object value) throws Exception + { + String json = LENIENT_MAPPER.writeValueAsString(value); + assertThat(json).isNotNull(); + } + + @Test + public void testAllowDeserializationWithFeature() throws Exception + { + _testAllowDeserializationLenient(java.time.LocalDateTime.class); + _testAllowDeserializationLenient(java.time.LocalDate.class); + _testAllowDeserializationLenient(java.time.LocalTime.class); + _testAllowDeserializationLenient(java.time.OffsetDateTime.class); + _testAllowDeserializationLenient(java.time.ZonedDateTime.class); + } + + private void _testAllowDeserializationLenient(Class target) throws Exception { + JacksonException e = assertThrows(JacksonException.class, () -> + LENIENT_MAPPER.readValue("{}", target)); + Assertions.assertThat(e).hasMessageContaining("Cannot construct instance of `"+target.getName()+"`"); + } + +} 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 ad44e22c39b..93359dd2fa4 100644 --- a/src/test/java/com/fasterxml/jackson/databind/misc/AccessFixTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/misc/AccessFixTest.java @@ -41,7 +41,11 @@ public void testCauseOfThrowableIgnoral() throws Exception _testCauseOfThrowableIgnoral(mapper); } catch (UnsupportedOperationException e) { // JDK 21+ fail? - verifyException(e, "Security Manager is deprecated"); + verifyException(e, + // JDK 21, 22, 23 + "Security Manager is deprecated", + // JDK 24 + "Setting a Security Manager is not supported"); } finally { if (setSucceeded) { System.setSecurityManager(origSecMan); diff --git a/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleTest.java b/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleTest.java index 58cd7418a64..5198c232d93 100644 --- a/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/module/SimpleModuleTest.java @@ -235,6 +235,19 @@ public void serialize(Test3787Bean value, JsonGenerator gen, SerializerProvider } } + // For [databind#5063] + static class Module5063A extends SimpleModule { + public Module5063A() { + super(Version.unknownVersion()); + } + } + + static class Module5063B extends SimpleModule { + public Module5063B() { + super(Version.unknownVersion()); + } + } + /* /********************************************************** /* Unit tests; first, verifying need for custom handlers @@ -624,4 +637,15 @@ public void testAddModuleWithDeserializerTwiceThenOnlyLatestIsKept_reverseOrder( assertEquals("I am A", result.value); } + + // For [databind#5063] + @Test + public void testDuplicateModules5063() { + ObjectMapper mapper = JsonMapper.builder() + .addModule(new Module5063A()) + .addModule(new Module5063B()) + .build(); + Set modules = mapper.getRegisteredModuleIds(); + assertEquals(2, modules.size()); + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java index e82f7a2e4ac..f5ceafdc281 100644 --- a/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java @@ -291,11 +291,11 @@ public void testNullKeyChecking() assertThrows(NullPointerException.class, () -> src.set(null, BooleanNode.TRUE)); assertThrows(NullPointerException.class, () -> src.replace(null, BooleanNode.TRUE)); - + assertThrows(NullPointerException.class, () -> src.setAll(Collections.singletonMap(null, MAPPER.createArrayNode()))); } - + @Test public void testRemove() { @@ -334,6 +334,25 @@ public void testValidWithObject() throws Exception assertEquals("{\"prop\":{}}", MAPPER.writeValueAsString(root)); } + // for [databind#5099] + @Test + public void testValidWith() throws Exception + { + ObjectNode root = MAPPER.createObjectNode(); + assertEquals("{}", MAPPER.writeValueAsString(root)); + + @SuppressWarnings("deprecation") + ObjectNode withResult = root.with( "with" ); + withResult.put( "key", "value" ); + + ObjectNode withObjectResult = root.withObject( "withObject" ); + withObjectResult.put( "key", "value" ); + + assertEquals("{\"key\":\"value\"}", MAPPER.writeValueAsString(withObjectResult)); + assertEquals("{\"key\":\"value\"}", MAPPER.writeValueAsString(withResult)); + assertEquals(withResult, withObjectResult); + } + @Test public void testValidWithArray() throws Exception { diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterOrdering5215Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterOrdering5215Test.java new file mode 100644 index 00000000000..0e38fca74ac --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterOrdering5215Test.java @@ -0,0 +1,67 @@ +package com.fasterxml.jackson.databind.ser; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// For [databind#5215]: Any-getter should be sorted last, by default +public class AnyGetterOrdering5215Test + extends DatabindTestUtil +{ + static class DynaBean { + public String l; + public String j; + public String a; + + protected Map extensions = new LinkedHashMap<>(); + + @JsonAnyGetter + public Map getExtensions() { + return extensions; + } + + @JsonAnySetter + public void addExtension(String name, Object value) { + extensions.put(name, value); + } + } + + /* + /********************************************************************** + /* Test methods + /********************************************************************** + */ + + private final ObjectMapper MAPPER = JsonMapper.builder() + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) + .build(); + + @Test + public void testDynaBean() throws Exception + { + DynaBean b = new DynaBean(); + b.a = "1"; + b.j = "2"; + b.l = "3"; + b.addExtension("z", "5"); + b.addExtension("b", "4"); + assertEquals(a2q("{" + + "'a':'1'," + + "'j':'2'," + + "'l':'3'," + + "'b':'4'," + + "'z':'5'}"), MAPPER.writeValueAsString(b)); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/EnumAsMapKeyTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/EnumAsMapKeyTest.java index a1d3c259c88..951b5dd5ab4 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/EnumAsMapKeyTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/EnumAsMapKeyTest.java @@ -46,6 +46,7 @@ static enum MyEnum594 { static class MyStuff594 { public Map stuff = new EnumMap(MyEnum594.class); + protected MyStuff594() { } public MyStuff594(String value) { stuff.put(MyEnum594.VALUE_WITH_A_REALLY_LONG_NAME_HERE, value); } @@ -135,8 +136,11 @@ public void testCustomEnumMapKeySerializer() throws Exception { // [databind#594] @Test public void testJsonValueForEnumMapKey() throws Exception { - assertEquals(a2q("{'stuff':{'longValue':'foo'}}"), - MAPPER.writeValueAsString(new MyStuff594("foo"))); + final String JSON = a2q("{'stuff':{'longValue':'foo'}}"); + assertEquals(JSON, MAPPER.writeValueAsString(new MyStuff594("foo"))); + MyStuff594 result = MAPPER.readValue(JSON, MyStuff594.class); + assertNotNull(result); + assertEquals("foo", result.stuff.get(MyEnum594.VALUE_WITH_A_REALLY_LONG_NAME_HERE)); } // [databind#2129] diff --git a/src/test/java/com/fasterxml/jackson/databind/tofix/DeserializationProblemHandler4656Test.java b/src/test/java/com/fasterxml/jackson/databind/tofix/DeserializationProblemHandler4656Test.java new file mode 100644 index 00000000000..63f1451296a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/tofix/DeserializationProblemHandler4656Test.java @@ -0,0 +1,61 @@ +package com.fasterxml.jackson.databind.tofix; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.*; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class DeserializationProblemHandler4656Test extends DatabindTestUtil +{ + // For [databind#4656] + static class Person4656 { + public String id; + public String name; + public Long age; + } + + static class ProblemHandler4656 extends DeserializationProblemHandler + { + protected static final String NUMBER_LONG_KEY = "$numberLong"; + + @Override + public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, + JsonToken t, JsonParser p, String failureMsg) throws IOException + { + if (targetType.getRawClass().equals(Long.class) && t == JsonToken.START_OBJECT) { + JsonNode tree = p.readValueAsTree(); + if (tree.get(NUMBER_LONG_KEY) != null) { + try { + return Long.parseLong(tree.get(NUMBER_LONG_KEY).asText()); + } catch (NumberFormatException e) { } + } + } + return NOT_HANDLED; + } + } + + // For [databind#4656] + @JacksonTestFailureExpected + @Test + public void testIssue4656() throws Exception { + ObjectMapper mapper = JsonMapper.builder() + .addHandler(new ProblemHandler4656()) + .build(); + final String json = "{\"id\": \"12ab\", \"name\": \"Bob\", \"age\": {\"$numberLong\": \"10\"}}"; + Person4656 person = mapper.readValue(json, Person4656.class); + assertNotNull(person); + assertEquals("12ab", person.id); + assertEquals("Bob", person.name); + assertEquals(10L, person.age); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/tofix/EnumSetDeserializer5165Test.java b/src/test/java/com/fasterxml/jackson/databind/tofix/EnumSetDeserializer5165Test.java new file mode 100644 index 00000000000..49542266a66 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/tofix/EnumSetDeserializer5165Test.java @@ -0,0 +1,96 @@ +package com.fasterxml.jackson.databind.tofix; + +import java.io.IOException; +import java.util.EnumSet; + +import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// For [databind#5165] +public class EnumSetDeserializer5165Test +{ + public enum MyEnum { + FOO + } + + static class Dst { + private EnumSet set; + + public EnumSet getSet() { + return set; + } + + public void setSet(EnumSet set) { + this.set = set; + } + } + + // Custom deserializer that converts empty strings to null + static class EmptyStringToNullDeserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + + public EmptyStringToNullDeserializer() { + super(MyEnum.class); + } + + @Override + public MyEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + if (value != null && value.isEmpty()) { + return null; + } + return MyEnum.valueOf(value); + } + } + + private ObjectMapper createMapperWithCustomDeserializer() { + SimpleModule module = new SimpleModule(); + module.addDeserializer(MyEnum.class, new EmptyStringToNullDeserializer()); + + return JsonMapper.builder() + .addModule(module) + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.FAIL)) + .build(); + } + + @JacksonTestFailureExpected + @Test + public void nullsFailTest() { + ObjectMapper mapper = createMapperWithCustomDeserializer(); + + assertThrows( + InvalidNullException.class, + () -> mapper.readValue("{\"set\":[\"\"]}", new TypeReference(){}) + ); + } + + @JacksonTestFailureExpected + @Test + public void nullsSkipTest() throws Exception { + SimpleModule module = new SimpleModule(); + module.addDeserializer(MyEnum.class, new EmptyStringToNullDeserializer()); + + ObjectMapper mapper = JsonMapper.builder() + .addModule(module) + .defaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) + .build(); + + Dst dst = mapper.readValue("{\"set\":[\"FOO\",\"\"]}", new TypeReference() {}); + + assertTrue(dst.getSet().isEmpty(), "Null values should be skipped"); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/tofix/ReaderForUpdating5281Test.java b/src/test/java/com/fasterxml/jackson/databind/tofix/ReaderForUpdating5281Test.java new file mode 100644 index 00000000000..7b86af890fe --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/tofix/ReaderForUpdating5281Test.java @@ -0,0 +1,46 @@ +package com.fasterxml.jackson.databind.tofix; + +import com.fasterxml.jackson.annotation.JsonMerge; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; + +// [databind#5281] Reading into existing instance uses creator property setup instead of accessor #5281 +public class ReaderForUpdating5281Test + extends DatabindTestUtil +{ + public static class ArrayListHolder { + // Works when annotated with... + // @JsonMerge + Collection values; + + public ArrayListHolder(String... values) { + this.values = new ArrayList<>(); + this.values.addAll(Arrays.asList(values)); + } + + public void setValues(Collection values) { + this.values = values; + } + } + + @JacksonTestFailureExpected + @Test + public void readsIntoCreator() throws Exception { + ObjectMapper mapper = JsonMapper.builder().build(); + + ArrayListHolder holder = mapper.readerForUpdating(new ArrayListHolder("A")) + .readValue("{ \"values\" : [ \"A\", \"B\" ]}"); + + assertThat(holder.values).hasSize(3) + .containsAll(Arrays.asList("A", "A", "B")); + } +}