`
+ (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 extends AnnotatedMember> 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"));
+ }
+}