diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 28d846c09d..086a71291f 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -30,7 +30,7 @@ jobs: dry-run: false language: jvm - name: Upload Crash - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3790515c0b..5af78ea1cf 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,12 +27,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 diff --git a/.github/workflows/dep_build_v2.yml b/.github/workflows/dep_build_v2.yml index 9b20934a9e..69dc7be980 100644 --- a/.github/workflows/dep_build_v2.yml +++ b/.github/workflows/dep_build_v2.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: distribution: 'temurin' java-version: ${{ matrix.java_version }} diff --git a/.github/workflows/dep_build_v3.yml b/.github/workflows/dep_build_v3.yml index bd8d0ab5a0..84767f14a0 100644 --- a/.github/workflows/dep_build_v3.yml +++ b/.github/workflows/dep_build_v3.yml @@ -24,7 +24,7 @@ jobs: with: ref: master - name: Set up JDK - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: distribution: 'temurin' java-version: ${{ matrix.java_version }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 522390eb72..f5825ce2b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,7 +40,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: distribution: 'temurin' java-version: ${{ matrix.java_version }} @@ -72,7 +72,7 @@ jobs: run: ./mvnw -B -q -ff -ntp test - name: Publish code coverage if: ${{ matrix.release_build && github.event_name != 'pull_request' }} - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./target/site/jacoco/jacoco.xml diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 3309d0361e..9b64535bcc 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1853,6 +1853,10 @@ wrongwrong (@k163377) * Reported #4878: When serializing a Map via Converter(StdDelegatingSerializer), a NullPointerException is thrown due to missing key serializer (2.18.3) + * Contributed fix for #4444: The `KeyDeserializer` specified in the class with + `@JsonDeserialize(keyUsing = ...)` is overwritten by the `KeyDeserializer` + specified in the `ObjectMapper`. + (2.18.3) Bernd Ahlers (@bernd) * Reported #4742: Deserialization with Builder, External type id, `@JsonCreator` failing @@ -1900,3 +1904,12 @@ Konstantin Maliuga (@badoken) Lars Benedetto (@lbenedetto) * Contributed #4676: Support other enum naming strategies than camelCase (2.19.0) + +Floris Westerman (@FWest98) + * Reported #4934: `DeserializationContext.readTreeAsValue()` handles null nodes + differently from `ObjectMapper.treeToValue()` + (2.19.0) + +Joren Inghelbrecht (@jin-harmoney) + * Contributed #4953: Allow clearing all caches to avoid classloader leaks + (2.19.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index ad140a0090..c62adf39a7 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -43,9 +43,17 @@ Project: jackson-databind #4869: Add `JsonNode.values()` to replace `elements()` #4896: Coercion shouldn't be necessary for Enums specifying an empty string (reported by @joaocanaverde-blue) +#4934: `DeserializationContext.readTreeAsValue()` handles null nodes + differently from `ObjectMapper.treeToValue()` + (reported by Floris W) +#4953: Allow clearing all caches to avoid classloader leaks + (contributed by Joren I) 2.18.3 (not yet released) +#4444: The `KeyDeserializer` specified in the class with `@JsonDeserialize(keyUsing = ...)` + is overwritten by the `KeyDeserializer` specified in the `ObjectMapper`. + (fix by @wrongwrong) #4827: Subclassed Throwable deserialization fails since v2.18.0 - no creator index for property 'cause' (reported by @nilswieber) @@ -64,8 +72,12 @@ Project: jackson-databind #4908: Deserialization behavior change with @JsonCreator and @ConstructorProperties between 2.17 and 2.18 (reported by Gustavo B) +#4917: `BigDecimal` deserialization issue when using `@JsonCreator` + (reported by @dbachdev) #4922: Failing `@JsonMerge` with a custom Map (reported by @nlisker) +#4932: Conversion of `MissingNode` throws `JsonProcessingException` + (reported by @ludgerb) 2.18.2 (27-Nov-2024) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java index 24b6e81c3f..5ec930af1e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java @@ -707,6 +707,19 @@ public final KeyDeserializer findKeyDeserializer(JavaType keyType, return kd; } + /** + * Method that will drop all dynamically constructed deserializers (ones that + * are counted as result value for {@link DeserializerCache#cachedDeserializersCount}). + * This can be used to remove memory usage (in case some deserializers are + * only used once or so), or to force re-construction of deserializers after + * configuration changes for mapper than owns the provider. + + * @since 2.19 + */ + public void flushCachedDeserializers() { + _cache.flushCachedDeserializers(); + } + /* /********************************************************** /* Public API, ObjectId handling @@ -989,9 +1002,9 @@ public T readValue(JsonParser p, JavaType type) throws IOException { return reportBadDefinition(type, "Could not find JsonDeserializer for type "+ClassUtil.getTypeDescription(type)); } - return (T) deser.deserialize(p, this); + return (T) _readValue(p, deser); } - + /** * Convenience method that may be used by composite or container deserializers, * for reading one-off values for the composite type, taking into account @@ -1023,7 +1036,7 @@ public T readPropertyValue(JsonParser p, BeanProperty prop, JavaType type) t "Could not find JsonDeserializer for type %s (via property %s)", ClassUtil.getTypeDescription(type), ClassUtil.nameOf(prop))); } - return (T) deser.deserialize(p, this); + return (T) _readValue(p, deser); } /** @@ -1074,7 +1087,7 @@ public JsonNode readTree(JsonParser p) throws IOException { */ public T readTreeAsValue(JsonNode n, Class targetType) throws IOException { - if (n == null) { + if (n == null || n.isMissingNode()) { return null; } try (TreeTraversingParser p = _treeAsTokens(n)) { @@ -1098,7 +1111,7 @@ public T readTreeAsValue(JsonNode n, Class targetType) throws IOException */ public T readTreeAsValue(JsonNode n, JavaType targetType) throws IOException { - if (n == null) { + if (n == null || n.isMissingNode()) { return null; } try (TreeTraversingParser p = _treeAsTokens(n)) { @@ -1116,6 +1129,20 @@ private TreeTraversingParser _treeAsTokens(JsonNode n) throws IOException return p; } + /** + * Helper method that should handle special cases for deserialization; most + * notably handling {@code null} (and possibly absent values). + * + * @since 2.19 + */ + private Object _readValue(JsonParser p, JsonDeserializer deser) throws IOException + { + if (p.hasToken(JsonToken.VALUE_NULL)) { + return deser.getNullValue(this); + } + return deser.deserialize(p, this); + } + /* /********************************************************** /* Methods for problem handling diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java index a12e2e95a4..a1f2837fbc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java @@ -371,15 +371,15 @@ public enum DeserializationFeature implements ConfigFeature /** * Feature that can be enabled to allow JSON empty String - * value ("") to be bound as `null` for POJOs and other structured + * value ({@code ""}) to be bound as {@code null} for POJOs and other structured * values ({@link java.util.Map}s, {@link java.util.Collection}s). - * If disabled, standard POJOs can only be bound from JSON `null` or + * If disabled, standard POJOs can only be bound from JSON {@code null} or * JSON Object (standard meaning that no custom deserializers or * constructors are defined; both of which can add support for other * kinds of JSON values); if enabled, empty JSON String can be taken * to be equivalent of JSON null. *

- * NOTE: this does NOT apply to scalar values such as booleans, numbers + * NOTE: this does NOT apply to scalar values such as Strings, booleans, numbers * and date/time types; * whether these can be coerced depends on * {@link MapperFeature#ALLOW_COERCION_OF_SCALARS}. @@ -390,13 +390,13 @@ public enum DeserializationFeature implements ConfigFeature /** * Feature that can be enabled to allow empty JSON Array - * value (that is, [ ]) to be bound to POJOs (and - * with 2.9, other values too) as `null`. - * If disabled, standard POJOs can only be bound from JSON `null` or + * value (that is, {@code[ ]} to be bound to POJOs (and + * with 2.9, other values too) as {@code null}. + * If disabled, standard POJOs can only be bound from JSON {@code null} or * JSON Object (standard meaning that no custom deserializers or * constructors are defined; both of which can add support for other * kinds of JSON values); if enabled, empty JSON Array will be taken - * to be equivalent of JSON null. + * to be equivalent of JSON {@code null}. *

* Feature is disabled by default. * diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 13636d7e77..01cf16cd12 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -4789,6 +4789,27 @@ public void acceptJsonFormatVisitor(JavaType type, JsonFormatVisitorWrapper visi _serializerProvider(getSerializationConfig()).acceptJsonFormatVisitor(type, visitor); } + /* + /********************************************************** + /* Extended Public API: caches + /********************************************************** + */ + + /** + * Method that will clear all caches this mapper owns. + *

+ * This method should not be needed in normal operation, but may be + * useful to avoid class-loader memory leaks when reloading applications. + * + * @since 2.19 + */ + public void clearCaches() { + _rootDeserializers.clear(); + _typeFactory.clearCache(); + _deserializationContext.flushCachedDeserializers(); + _serializerProvider.flushCachedSerializers(); + } + /* /********************************************************** /* Internal factory methods for type ids, overridable @@ -4916,6 +4937,9 @@ protected Object _readValue(DeserializationConfig cfg, JsonParser p, result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt); } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = null; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = null; } else { // pointing to event other than null result = ctxt.readRootValue(p, valueType, _findRootDeserializer(ctxt, valueType), null); } @@ -4940,6 +4964,9 @@ protected Object _readMapAndClose(JsonParser p0, JavaType valueType) result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt); } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = null; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = null; } else { result = ctxt.readRootValue(p, valueType, _findRootDeserializer(ctxt, valueType), null); diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java index 2069b56cb2..fa45a4029c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java @@ -2094,6 +2094,9 @@ protected Object _bind(JsonParser p, Object valueToUpdate) throws IOException } } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = valueToUpdate; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = valueToUpdate; } else { // pointing to event other than null result = ctxt.readRootValue(p, _valueType, _findRootDeserializer(ctxt), _valueToUpdate); } @@ -2120,6 +2123,9 @@ protected Object _bindAndClose(JsonParser p0) throws IOException } } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = _valueToUpdate; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = _valueToUpdate; } else { result = ctxt.readRootValue(p, _valueType, _findRootDeserializer(ctxt), _valueToUpdate); } diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java index a16e5cbeff..88f7d1b300 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java @@ -260,11 +260,9 @@ public enum SerializationFeature implements ConfigFeature * representation may mean either simple number, or an array of numbers, * depending on type. *

- * Note: whether {@link java.util.Map} keys are serialized as Strings - * or not is controlled using {@link #WRITE_DATE_KEYS_AS_TIMESTAMPS}. - *

- * Feature is enabled by default, so that period/duration are by default - * serialized as timestamps. + * Feature is enabled by default in Jackson 2.x, so that period/duration values + * are by default serialized as timestamps. + * It will be disabled by default in Jackson 3.x. * * @since 2.5 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java index c1172cb2d5..a5817b6957 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java @@ -561,6 +561,9 @@ public abstract WritableObjectId findObjectId(Object forPojo, * Note that serializers produced should NOT handle polymorphic serialization * aspects; separate {@link TypeSerializer} is to be constructed by caller * if and as necessary. + *

+ * Note: this call will also contextualize serializer (call + * {@code serializer.createContextual()}) before returning it. * * @throws JsonMappingException if there are fatal problems with * accessing suitable serializer; including that of not @@ -603,7 +606,8 @@ public JsonSerializer findValueSerializer(Class valueType, BeanProper * This is necessary for accurate handling of external type information, * to handle polymorphic types. *

- * Note: this call will also contextualize serializer before returning it. + * Note: this call will also contextualize serializer (call + * {@code serializer.createContextual()}) before returning it. * * @param property When creating secondary serializers, property for which * serializer is needed: annotations of the property (or bean that contains it) @@ -635,9 +639,10 @@ public JsonSerializer findValueSerializer(JavaType valueType, BeanProper } /** - * Method variant used when we do NOT want contextualization to happen; it will need - * to be handled at a later point, but caller wants to be able to do that - * as needed; sometimes to avoid infinite loops + * Serializer lookup variant used when we do NOT want contextualization to happen; + * while contextualization MUST be handled at some point (many serializers will not be + * in usable state before contextualization), but caller wants to be able to do that + * later; sometimes to avoid infinite loops. * * @since 2.5 */ @@ -664,9 +669,10 @@ public JsonSerializer findValueSerializer(Class valueType) throws Jso } /** - * Method variant used when we do NOT want contextualization to happen; it will need - * to be handled at a later point, but caller wants to be able to do that - * as needed; sometimes to avoid infinite loops + * Serializer lookup variant used when we do NOT want contextualization to happen; + * while contextualization MUST be handled at some point (many serializers will not be + * in usable state before contextualization), but caller wants to be able to do that + * later; sometimes to avoid infinite loops. * * @since 2.5 */ @@ -698,8 +704,10 @@ public JsonSerializer findValueSerializer(JavaType valueType) * certain that this is the primary property value serializer. * * @param valueType Type of values to serialize - * @param property Property that is being handled; will never be null, and its - * type has to match valueType parameter. + * @param property Property that directly refers to value being serialized (optional, + * may be {@code null} for root level serializers). + * Should not be null if property is known. If not null, + * its type must match {@code valueType} argument. * * @since 2.3 */ @@ -1272,6 +1280,24 @@ public final void defaultSerializeNull(JsonGenerator gen) throws IOException } } + /* + /******************************************************** + /* Cache manipulation + /******************************************************** + */ + + /** + * Method that will drop all serializers currently cached by this provider. + * This can be used to remove memory usage (in case some serializers are + * only used once or so), or to force re-construction of serializers after + * configuration changes for mapper than owns the provider. + + * @since 2.19 + */ + public void flushCachedSerializers() { + _serializerCache.flush(); + } + /* /******************************************************** /* Error reporting diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index 25601f88c4..1a71004792 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1244,10 +1244,12 @@ public KeyDeserializer createKeyDeserializer(DeserializationContext ctxt, throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); - BeanDescription beanDesc = null; - KeyDeserializer deser = null; - if (_factoryConfig.hasKeyDeserializers()) { - beanDesc = config.introspectClassAnnotations(type); + final BeanDescription beanDesc = config.introspectClassAnnotations(type); + + // [databind#2452]: Support `@JsonDeserialize(keyUsing = ...)` + KeyDeserializer deser = findKeyDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); + + if (deser == null && _factoryConfig.hasKeyDeserializers()) { for (KeyDeserializers d : _factoryConfig.keyDeserializers()) { deser = d.findKeyDeserializer(type, config, beanDesc); if (deser != null) { @@ -1258,17 +1260,10 @@ public KeyDeserializer createKeyDeserializer(DeserializationContext ctxt, // the only non-standard thing is this: if (deser == null) { - // [databind#2452]: Support `@JsonDeserialize(keyUsing = ...)` - if (beanDesc == null) { - beanDesc = config.introspectClassAnnotations(type.getRawClass()); - } - deser = findKeyDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); - if (deser == null) { - if (type.isEnumType()) { - deser = _createEnumKeyDeserializer(ctxt, type); - } else { - deser = StdKeyDeserializers.findStringBasedKeyDeserializer(config, type); - } + if (type.isEnumType()) { + deser = _createEnumKeyDeserializer(ctxt, type); + } else { + deser = StdKeyDeserializers.findStringBasedKeyDeserializer(config, type); } } // and then post-processing diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java index b42d80c0d0..00405ecd74 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java @@ -194,11 +194,6 @@ protected Object _parse(String key, DeserializationContext ctxt) throws Exceptio case TYPE_DOUBLE: return _parseDouble(key); case TYPE_LOCALE: - try { - return _deser._deserialize(key, ctxt); - } catch (IllegalArgumentException e) { - return _weirdKey(ctxt, key, e); - } case TYPE_CURRENCY: try { return _deser._deserialize(key, ctxt); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java index b930aa857f..119760d055 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java @@ -553,16 +553,6 @@ public int cachedSerializersCount() { return _serializerCache.size(); } - /** - * Method that will drop all serializers currently cached by this provider. - * This can be used to remove memory usage (in case some serializers are - * only used once or so), or to force re-construction of serializers after - * configuration changes for mapper than owns the provider. - */ - public void flushCachedSerializers() { - _serializerCache.flush(); - } - /* /********************************************************** /* Extended API called by ObjectMapper: other diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java index 5a485029d3..5d9182a189 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java @@ -27,7 +27,8 @@ public class ToEmptyObjectSerializer extends StdSerializer { - protected ToEmptyObjectSerializer(Class raw) { + // `public` since 2.19 + public ToEmptyObjectSerializer(Class raw) { super(raw, false); } diff --git a/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java b/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java new file mode 100644 index 0000000000..b919808518 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java @@ -0,0 +1,55 @@ +package com.fasterxml.jackson.databind; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +public class DeserializationContextTest extends DatabindTestUtil +{ + // Not testing "no nulls for primitives", so + private final ObjectMapper MAPPER = jsonMapperBuilder() + .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .build(); + + static class Bean4934 { + public String value; + } + + // [databind#4934] + @Test + public void testTreeAsValueFromNulls() throws Exception + { + final JsonNodeFactory nodeF = MAPPER.getNodeFactory(); + try (JsonParser p = MAPPER.createParser("abc")) { + DeserializationContext ctxt = MAPPER.readerFor(String.class).createDeserializationContext(p); + + assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), Boolean.class)); + assertEquals(Boolean.FALSE, ctxt.readTreeAsValue(nodeF.nullNode(), Boolean.TYPE)); + assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), String.class)); + + assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), Bean4934.class)); + } + } + + // [databind#4934] + @Test + public void testTreeAsValueFromMissing() throws Exception + { + final JsonNodeFactory nodeF = MAPPER.getNodeFactory(); + try (JsonParser p = MAPPER.createParser("abc")) { + DeserializationContext ctxt = MAPPER.readerFor(String.class).createDeserializationContext(p); + + // Absent becomes `null` for now as well + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Boolean.class)); + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Boolean.TYPE)); + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), String.class)); + + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Bean4934.class)); + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java index 4fb454862d..a249682b76 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java @@ -409,6 +409,30 @@ public void testProviderConfig() throws Exception assertEquals(4, m._deserializationContext._cache.cachedDeserializersCount()); } + @Test + public void testClearCaches() throws Exception + { + ObjectMapper m = new ObjectMapper(); + + // Serialize and deserialize to fill caches + final String JSON = "{ \"x\" : 3 }"; + Bean bean = m.readValue(JSON, Bean.class); + m.writeValueAsString("test"); + + // Caches should not be empty + assertNotEquals(0, m._deserializationContext._cache.cachedDeserializersCount()); + assertNotEquals(0, m._rootDeserializers.size()); + assertNotEquals(0, m._serializerProvider.cachedSerializersCount()); + + // Clear caches + m.clearCaches(); + + // Caches should be empty + assertEquals(0, m._deserializationContext._cache.cachedDeserializersCount()); + assertEquals(0, m._rootDeserializers.size()); + assertEquals(0, m._serializerProvider.cachedSerializersCount()); + } + // For [databind#689] @Test public void testCustomDefaultPrettyPrinter() throws Exception diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java new file mode 100644 index 0000000000..a12ccb5621 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java @@ -0,0 +1,67 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.io.IOException; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// [databind#4444] +public class CustomMapKeyDeserializer4444Test extends DatabindTestUtil +{ + @JsonDeserialize(keyUsing = ForClass.class) + static class MyKey { + private final String value; + + MyKey(String value) { + this.value = value; + } + } + + static class ForClass extends KeyDeserializer { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return new MyKey(key + "-class"); + } + } + + static class ForMapper extends KeyDeserializer { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return new MyKey(key + "-mapper"); + } + } + + // It is not declared as new TypeReference<> because it causes a compile error in Java 8. + TypeReference> typeRef = new TypeReference>() { + }; + + @Test + void withoutForClass() throws Exception { + ObjectMapper mapper = newJsonMapper(); + Map result = mapper.readValue("{\"foo\":null}", typeRef); + + assertEquals("foo-class", result.keySet().stream().findFirst().get().value); + } + + // The KeyDeserializer set by the annotation must not be overwritten by the KeyDeserializer set in the mapper. + @Test + void withForClass() throws Exception { + SimpleModule sm = new SimpleModule(); + sm.addKeyDeserializer(MyKey.class, new ForMapper()); + + ObjectMapper mapper = jsonMapperBuilder().addModule(sm).build(); + Map result = mapper.readValue("{\"foo\":null}", typeRef); + + assertEquals("foo-class", result.keySet().stream().findFirst().get().value); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java index dc4dcee6b0..4b446ab285 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java @@ -4,6 +4,7 @@ import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -81,6 +82,107 @@ static class NestedBigDecimalHolder2784 { public BigDecimalHolder2784 holder; } + static class DeserializationIssue4917 { + public DecimalHolder4917 decimalHolder; + public double number; + } + + static class DeserializationIssue4917V2 { + public DecimalHolder4917 decimalHolder; + public int number; + } + + static class DeserializationIssue4917V3 { + public BigDecimal decimal; + public double number; + } + + static class DecimalHolder4917 { + public BigDecimal value; + + private DecimalHolder4917(BigDecimal value) { + this.value = value; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + static DecimalHolder4917 of(BigDecimal value) { + return new DecimalHolder4917(value); + } + } + + static class Point { + private Double x; + private Double y; + + public Double getX() { + return x; + } + + public void setX(Double x) { + this.x = x; + } + + public Double getY() { + return y; + } + + public void setY(Double y) { + this.y = y; + } + } + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true) + @JsonSubTypes(@JsonSubTypes.Type(value = CenterResult.class, name = "center")) + static abstract class Result { + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } + + static class CenterResult extends Result { + private Point center; + + private Double radius; + + public Double getRadius() { + return radius; + } + + public void setRadius(Double radius) { + this.radius = radius; + } + + public Point getCenter() { + return center; + } + + public void setCenter(Point center) { + this.center = center; + } + } + + static class Root { + private Result[] results; + + public Result[] getResults() { + return results; + } + + public void setResults(Result[] results) { + this.results = results; + } + } + /* /********************************************************************** /* Helper classes, serializers/deserializers/resolvers @@ -415,4 +517,52 @@ public void bigDecimal4694FromBytes() throws Exception assertEquals(BIG_DEC, MAPPER.readValue(b, 0, b.length, BigDecimal.class)); } + // [databind#4917] + @Test + public void bigDecimal4917() throws Exception + { + DeserializationIssue4917 issue = MAPPER.readValue( + a2q("{'decimalHolder':100.00,'number':50}"), + DeserializationIssue4917.class); + assertEquals(new BigDecimal("100.00"), issue.decimalHolder.value); + assertEquals(50.0, issue.number); + } + + @Test + public void bigDecimal4917V2() throws Exception + { + DeserializationIssue4917V2 issue = MAPPER.readValue( + a2q("{'decimalHolder':100.00,'number':50}"), + DeserializationIssue4917V2.class); + assertEquals(new BigDecimal("100.00"), issue.decimalHolder.value); + assertEquals(50, issue.number); + } + + @Test + public void bigDecimal4917V3() throws Exception + { + DeserializationIssue4917V3 issue = MAPPER.readValue( + a2q("{'decimal':100.00,'number':50}"), + DeserializationIssue4917V3.class); + assertEquals(new BigDecimal("100.00"), issue.decimal); + assertEquals(50, issue.number); + } + + // https://github.com/FasterXML/jackson-core/issues/1397 + @Test + public void issue1397() throws Exception { + final String dataString = a2q("{ 'results': [ { " + + "'radius': 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, " + + "'type': 'center', " + + "'center': { " + + "'x': -11.0, " + + "'y': -2.0 } } ] }"); + + Root object = MAPPER.readValue(dataString, Root.class); + + CenterResult result = (CenterResult) Arrays.stream(object.getResults()).findFirst().get(); + + assertEquals(-11.0d, result.getCenter().getX()); + assertEquals(-2.0d, result.getCenter().getY()); + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java index a6a233fdfa..41bf08eb6e 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java @@ -140,15 +140,6 @@ static class LcEnumIntrospector extends JacksonAnnotationIntrospector { private static final long serialVersionUID = 1L; - @Override - public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] names) { - // kinda sorta wrong, but for testing's sake... - for (int i = 0, len = enumValues.length; i < len; ++i) { - names[i] = enumValues[i].name().toLowerCase(); - } - return names; - } - @Override public String[] findEnumValues(MapperConfig config, AnnotatedClass annotatedClass, Enum[] enumValues, String[] names) { diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java index bf2e50b20a..8b22ed8af1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java @@ -54,7 +54,7 @@ public void testUnknownClassAsSubtype() throws Exception ObjectReader reader = MAPPER.readerFor(BaseUnknownWrapper.class) .without(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); BaseUnknownWrapper w = reader.readValue(a2q - ("{'value':{'clazz':'com.foobar.Nothing'}}'")); + ("{'value':{'clazz':'com.foobar.Nothing'}}")); assertNotNull(w); } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java index 9c50912155..53a3081d18 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java @@ -268,7 +268,7 @@ public void testUnknownClassAsSubtype() throws Exception ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); BaseWrapper w = mapper.readValue(a2q - ("{'value':{'clazz':'com.foobar.Nothing'}}'"), + ("{'value':{'clazz':'com.foobar.Nothing'}}"), BaseWrapper.class); assertNotNull(w); assertNull(w.value); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java index 2aa842fd11..5a2161f30d 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java @@ -611,7 +611,7 @@ public Orange(@JsonProperty("name") String name, @JsonProperty("name") String co public void testIssue3008() throws Exception { ObjectReader r = MAPPER.readerFor(Box3008.class); - Box3008 deserOrangeBox = r.readValue("{\"type\":null,\"fruit\":null}}"); + Box3008 deserOrangeBox = r.readValue("{\"type\":null,\"fruit\":null}"); assertNull(deserOrangeBox.fruit); assertNull(deserOrangeBox.type); // error: "expected null, but was:" } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java index 05aaa15ec0..f2e7667365 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java @@ -112,7 +112,7 @@ public void testExternalTypeId() throws Exception // [databind#1198] @Test public void testFails() throws Exception { - String json = "{ \"name\": \"foo\", \"attack\":\"right\" } }"; + String json = "{ \"name\": \"foo\", \"attack\":\"right\" } "; Character character = MAPPER.readValue(json, Character.class); @@ -124,7 +124,7 @@ public void testFails() throws Exception { // [databind#1198] @Test public void testWorks() throws Exception { - String json = "{ \"name\": \"foo\", \"preferredAttack\": \"KICK\", \"attack\":\"right\" } }"; + String json = "{ \"name\": \"foo\", \"preferredAttack\": \"KICK\", \"attack\":\"right\" }"; Character character = MAPPER.readValue(json, Character.class); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java index ff7a46e8fb..69bbd6a38e 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java @@ -104,9 +104,9 @@ public static Orange getOrange(@JsonProperty("name") String name, @JsonProperty( private static final Orange orange = new Orange("Orange", "orange"); private static final Box orangeBox = new Box("orange", orange); private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}"; - private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}"; - private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}"; - private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}"; + private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}"; + private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}"; + private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}"; private static final Apple apple = new Apple("Apple", 16); private static Box appleBox = new Box("apple", apple); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java index e66352a5cc..e08ad6d171 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java @@ -101,9 +101,9 @@ public Orange(String name, String c) { private static final Orange orange = new Orange("Orange", "orange"); private static final Box orangeBox = new Box("orange", orange); private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}"; - private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}"; - private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}"; - private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}"; + private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}"; + private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}"; + private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}"; private static final Apple apple = new Apple("Apple", 16); private static final Box appleBox = new Box("apple", apple); diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java b/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java index 6af9695a48..1840d7d6c1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java +++ b/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java @@ -1,6 +1,6 @@ package com.fasterxml.jackson.databind.misc; -import java.io.*; +import java.io.IOException; import org.junit.jupiter.api.Test; @@ -24,7 +24,7 @@ public class TestBlocking * quite yet. */ @Test - public void testEagerAdvance() throws IOException + public void testEagerAdvance() throws Exception { ObjectMapper mapper = jsonMapperBuilder() .disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) diff --git a/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java b/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java index 05b6c37a35..ea0a93358c 100644 --- a/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java @@ -419,4 +419,15 @@ public void testValueToTree() throws Exception assertEquals(wrapRootMapper.readValue(expected, Map.class), wrapRootMapper.readValue(wrapRootMapper.writeValueAsString(value), Map.class)); assertEquals(wrapRootMapper.readValue(expected, Map.class), wrapRootMapper.readValue(wrapRootMapper.valueToTree(value).toString(), Map.class)); } + + // [databind#4932]: handling of `MissingNode` wrt conversions + @Test + public void treeToValueWithMissingNode4932() throws Exception { + assertNull(MAPPER.treeToValue(MAPPER.nullNode(), Object.class)); + assertNull(MAPPER.treeToValue(MAPPER.missingNode(), Object.class)); + + ObjectReader r = MAPPER.readerFor(Object.class); + assertNull(r.treeToValue(MAPPER.nullNode(), Object.class)); + assertNull(r.treeToValue(MAPPER.missingNode(), Object.class)); + } }