From a6d345c7b0cd4ac3761b01ba27ac9ae6624dfe04 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 25 Jan 2025 19:58:10 -0800 Subject: [PATCH 1/2] tiny clean up --- .../jackson/databind/convert/MapConversionsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/MapConversionsTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/MapConversionsTest.java index 5f4b4b7f4b..e90788ac3b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/convert/MapConversionsTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/convert/MapConversionsTest.java @@ -13,8 +13,6 @@ public class MapConversionsTest { - final ObjectMapper MAPPER = new ObjectMapper(); - enum AB { A, B; } static class Bean { @@ -22,7 +20,7 @@ static class Bean { public String B; } - // [Issue#287] + // [databind#287] @JsonSerialize(converter=RequestConverter.class) static class Request { @@ -48,6 +46,8 @@ public Map convert(final Request value) { /********************************************************** */ + private final ObjectMapper MAPPER = new ObjectMapper(); + /** * Test that verifies that we can go between couple of types of Maps... */ From 2e741764c7ad56beb015a384d1c57dfc32d66864 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 25 Jan 2025 20:28:02 -0800 Subject: [PATCH 2/2] Fix #4878: NPE for Map-converting serializer (#4926) --- release-notes/CREDITS-2.x | 7 +- release-notes/VERSION-2.x | 3 + .../databind/SerializerProvider.java.orig | 1579 ----------------- .../ser/std/StdDelegatingSerializer.java | 16 +- .../convert/MapConversion4878Test.java | 59 + 5 files changed, 79 insertions(+), 1585 deletions(-) delete mode 100644 src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java.orig create mode 100644 src/test/java/com/fasterxml/jackson/databind/convert/MapConversion4878Test.java diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index b0283d52f4..3d9e3c5bdb 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1844,9 +1844,12 @@ Maxim Valeev (@MaximValeev) (2.18.1) wrongwrong (@k163377) - * Contributed #4749: Fixed problem in StdDelegatingSerializer#serializeWithType where final serializer lookup was done - on the pre-converted value when _delegateSerializer was null + * Contributed #4749: Fixed problem in StdDelegatingSerializer#serializeWithType where final + serializer lookup was done on the pre-converted value when _delegateSerializer was null (2.18.1) + * Reported #4878: When serializing a Map via Converter(StdDelegatingSerializer), + a NullPointerException is thrown due to missing key serializer + (2.18.3) Bernd Ahlers (@bernd) * Reported #4742: Deserialization with Builder, External type id, `@JsonCreator` failing diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 078580cf28..27b95d63e6 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -18,6 +18,9 @@ Project: jackson-databind multiple constructors since 2.18 (reported by Tomáš P) (fix by Joo-Hyuk K, @cowtowncoder) +#4878: When serializing a Map via Converter(StdDelegatingSerializer), + a NullPointerException is thrown due to missing key serializer + (reported by @wrongwrong) #4908: Deserialization behavior change with @JsonCreator and @ConstructorProperties between 2.17 and 2.18 (reported by Gustavo B) diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java.orig b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java.orig deleted file mode 100644 index 549291695c..0000000000 --- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java.orig +++ /dev/null @@ -1,1579 +0,0 @@ -package com.fasterxml.jackson.databind; - -import java.io.IOException; -import java.text.DateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.ObjectIdGenerator; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.cfg.ContextAttributes; -import com.fasterxml.jackson.databind.cfg.DatatypeFeature; -import com.fasterxml.jackson.databind.cfg.DatatypeFeatures; -import com.fasterxml.jackson.databind.deser.ContextualDeserializer; -import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; -import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; -import com.fasterxml.jackson.databind.introspect.Annotated; -import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.ser.*; -import com.fasterxml.jackson.databind.ser.impl.FailingSerializer; -import com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap; -import com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer; -import com.fasterxml.jackson.databind.ser.impl.UnknownSerializer; -import com.fasterxml.jackson.databind.ser.impl.WritableObjectId; -import com.fasterxml.jackson.databind.ser.std.NullSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import com.fasterxml.jackson.databind.util.ClassUtil; -import com.fasterxml.jackson.databind.util.TokenBuffer; - -/** - * Class that defines API used by {@link ObjectMapper} and - * {@link JsonSerializer}s to obtain serializers capable of serializing - * instances of specific types; as well as the default implementation - * of the functionality. - *

- * Provider handles caching aspects of serializer handling; all construction - * details are delegated to {@link SerializerFactory} instance. - *

- * Object life-cycle is such that an initial instance ("blueprint") is created - * and referenced by {@link ObjectMapper} and {@link ObjectWriter} instances; - * but for actual usage, a configured instance is created by using - * a create method in sub-class - * {@link com.fasterxml.jackson.databind.ser.DefaultSerializerProvider}. - * Only this instance can be used for actual serialization calls; blueprint - * object is only to be used for creating instances. - */ -public abstract class SerializerProvider - extends DatabindContext -{ - /** - * Setting for determining whether mappings for "unknown classes" should be - * cached for faster resolution. Usually this isn't needed, but maybe it - * is in some cases? - */ - protected final static boolean CACHE_UNKNOWN_MAPPINGS = false; - - public final static JsonSerializer DEFAULT_NULL_KEY_SERIALIZER = - new FailingSerializer("Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)"); - - /** - * Placeholder serializer used when java.lang.Object typed property - * is marked to be serialized. - *
- * NOTE: starting with 2.6, this instance is NOT used for any other types, and - * separate instances are constructed for "empty" Beans. - *

- * NOTE: changed to protected for 2.3; no need to be publicly available. - */ - protected final static JsonSerializer DEFAULT_UNKNOWN_SERIALIZER = new UnknownSerializer(); - - /* - /********************************************************** - /* Configuration, general - /********************************************************** - */ - - /** - * Serialization configuration to use for serialization processing. - */ - protected final SerializationConfig _config; - - /** - * View used for currently active serialization, if any. - * Only set for non-blueprint instances. - */ - protected final Class _serializationView; - - /* - /********************************************************** - /* Configuration, factories - /********************************************************** - */ - - /** - * Factory used for constructing actual serializer instances. - * Only set for non-blueprint instances. - */ - protected final SerializerFactory _serializerFactory; - - /* - /********************************************************** - /* Helper objects for caching, reuse - /********************************************************** - */ - - /** - * Cache for doing type-to-value-serializer lookups. - */ - protected final SerializerCache _serializerCache; - - /** - * Lazily-constructed holder for per-call attributes. - * Only set for non-blueprint instances. - * - * @since 2.3 - */ - protected transient ContextAttributes _attributes; - - /* - /********************************************************** - /* Configuration, specialized serializers - /********************************************************** - */ - - /** - * Serializer that gets called for values of types for which no - * serializers can be constructed. - *

- * The default serializer will simply thrown an exception. - */ - protected JsonSerializer _unknownTypeSerializer = DEFAULT_UNKNOWN_SERIALIZER; - - /** - * Serializer used to output non-null keys of Maps (which will get - * output as JSON Objects), if not null; if null, us the standard - * default key serializer. - */ - protected JsonSerializer _keySerializer; - - /** - * Serializer used to output a null value. Default implementation - * writes nulls using {@link JsonGenerator#writeNull}. - */ - protected JsonSerializer _nullValueSerializer = NullSerializer.instance; - - /** - * Serializer used to (try to) output a null key, due to an entry of - * {@link java.util.Map} having null key. - * The default implementation will throw an exception if this happens; - * alternative implementation (like one that would write an Empty String) - * can be defined. - */ - protected JsonSerializer _nullKeySerializer = DEFAULT_NULL_KEY_SERIALIZER; - - /* - /********************************************************** - /* State, for non-blueprint instances: generic - /********************************************************** - */ - - /** - * For fast lookups, we will have a local non-shared read-only - * map that contains serializers previously fetched. - */ - protected final ReadOnlyClassToSerializerMap _knownSerializers; - - /** - * Lazily acquired and instantiated formatter object: initialized - * first time it is needed, reused afterwards. Used via instances - * (not blueprints), so that access need not be thread-safe. - */ - protected DateFormat _dateFormat; - - /** - * Flag set to indicate that we are using vanilla null value serialization - * - * @since 2.3 - */ - protected final boolean _stdNullValueSerializer; - - /* - /********************************************************** - /* Life-cycle - /********************************************************** - */ - - /** - * Constructor for creating master (or "blue-print") provider object, - * which is only used as the template for constructing per-binding - * instances. - */ - public SerializerProvider() - { - _config = null; - _serializerFactory = null; - _serializerCache = new SerializerCache(); - // Blueprints doesn't have access to any serializers... - _knownSerializers = null; - - _serializationView = null; - _attributes = null; - - // not relevant for blueprint instance, could set either way: - _stdNullValueSerializer = true; - } - - /** - * "Copy-constructor", used by sub-classes when creating actual non-blueprint - * instances to use. - * - * @param src Blueprint object used as the baseline for this instance - */ - protected SerializerProvider(SerializerProvider src, - SerializationConfig config, SerializerFactory f) - { - _serializerFactory = f; - _config = config; - - _serializerCache = src._serializerCache; - _unknownTypeSerializer = src._unknownTypeSerializer; - _keySerializer = src._keySerializer; - _nullValueSerializer = src._nullValueSerializer; - _nullKeySerializer = src._nullKeySerializer; - - _stdNullValueSerializer = (_nullValueSerializer == DEFAULT_NULL_KEY_SERIALIZER); - - _serializationView = config.getActiveView(); - _attributes = config.getAttributes(); - - /* Non-blueprint instances do have a read-only map; one that doesn't - * need synchronization for lookups. - */ - _knownSerializers = _serializerCache.getReadOnlyLookupMap(); - } - - /** - * Copy-constructor used when making a copy of a blueprint instance. - * - * @since 2.5 - */ - protected SerializerProvider(SerializerProvider src) - { - // since this is assumed to be a blue-print instance, many settings missing: - _config = null; - _serializationView = null; - _serializerFactory = null; - _knownSerializers = null; - - // and others initialized to default empty state - _serializerCache = new SerializerCache(); - - _unknownTypeSerializer = src._unknownTypeSerializer; - _keySerializer = src._keySerializer; - _nullValueSerializer = src._nullValueSerializer; - _nullKeySerializer = src._nullKeySerializer; - - _stdNullValueSerializer = src._stdNullValueSerializer; - } - - - /** - * @since 2.16 - */ - protected SerializerProvider(SerializerProvider src, SerializerCache serializerCache) - { - _serializerCache = serializerCache; - - _config = src._config; - _serializationView = src._serializationView; - _serializerFactory = src._serializerFactory; - _attributes = src._attributes; - - _knownSerializers = src._knownSerializers; - _unknownTypeSerializer = src._unknownTypeSerializer; - - _nullValueSerializer = src._nullValueSerializer; - _nullKeySerializer = src._nullKeySerializer; - _keySerializer = src._keySerializer; - _stdNullValueSerializer = src._stdNullValueSerializer; - } - - /* - /********************************************************** - /* Methods for configuring default settings - /********************************************************** - */ - - /** - * Method that can be used to specify serializer to use for serializing - * all non-null JSON property names, unless more specific key serializer - * is found (i.e. if not custom key serializer has been registered for - * Java type). - *

- * Note that key serializer registration are different from value serializer - * registrations. - */ - public void setDefaultKeySerializer(JsonSerializer ks) - { - if (ks == null) { - throw new IllegalArgumentException("Cannot pass null JsonSerializer"); - } - _keySerializer = ks; - } - - /** - * Method that can be used to specify serializer that will be - * used to write JSON values matching Java null values - * instead of default one (which simply writes JSON null). - *

- * Note that you can get finer control over serializer to use by overriding - * {@link #findNullValueSerializer}, which gets called once per each - * property. - */ - public void setNullValueSerializer(JsonSerializer nvs) - { - if (nvs == null) { - throw new IllegalArgumentException("Cannot pass null JsonSerializer"); - } - _nullValueSerializer = nvs; - } - - /** - * Method that can be used to specify serializer that will be - * used to write JSON property names matching null keys for Java - * Maps (which will otherwise throw an exception if try write such property name) - */ - public void setNullKeySerializer(JsonSerializer nks) - { - if (nks == null) { - throw new IllegalArgumentException("Cannot pass null JsonSerializer"); - } - _nullKeySerializer = nks; - } - - /* - /********************************************************** - /* DatabindContext implementation (and closely related but ser-specific) - /********************************************************** - */ - - /** - * Method for accessing configuration for the serialization processing. - */ - @Override - public final SerializationConfig getConfig() { return _config; } - - @Override - public final AnnotationIntrospector getAnnotationIntrospector() { - return _config.getAnnotationIntrospector(); - } - - @Override - public final TypeFactory getTypeFactory() { - return _config.getTypeFactory(); - } - - @Override // since 2.11 - public JavaType constructSpecializedType(JavaType baseType, Class subclass) - throws IllegalArgumentException - { - if (baseType.hasRawClass(subclass)) { - return baseType; - } - // Need little bit different handling due to [databind#2632]; pass `true` for - // "relaxed" type assingment checks. - return getConfig().getTypeFactory().constructSpecializedType(baseType, subclass, true); - } - - @Override - public final Class getActiveView() { return _serializationView; } - - @Override - public final boolean canOverrideAccessModifiers() { - return _config.canOverrideAccessModifiers(); - } - - @Override - public final boolean isEnabled(MapperFeature feature) { - return _config.isEnabled(feature); - } - - @Override // @since 2.14 - public final boolean isEnabled(DatatypeFeature feature) { - return _config.isEnabled(feature); - } - - @Override // @since 2.15 - public final DatatypeFeatures getDatatypeFeatures() { - return _config.getDatatypeFeatures(); - } - - @Override - public final JsonFormat.Value getDefaultPropertyFormat(Class baseType) { - return _config.getDefaultPropertyFormat(baseType); - } - - /** - * @since 2.8 - */ - public final JsonInclude.Value getDefaultPropertyInclusion(Class baseType) { - return _config.getDefaultPropertyInclusion(baseType); - } - - /** - * Method for accessing default Locale to use: convenience method for - *
-     *   getConfig().getLocale();
-     *
- */ - @Override - public Locale getLocale() { - return _config.getLocale(); - } - - /** - * Method for accessing default TimeZone to use: convenience method for - *
-     *   getConfig().getTimeZone();
-     *
- */ - @Override - public TimeZone getTimeZone() { - return _config.getTimeZone(); - } - - /* - /********************************************************** - /* Generic attributes (2.3+) - /********************************************************** - */ - - @Override - public Object getAttribute(Object key) { - return _attributes.getAttribute(key); - } - - @Override - public SerializerProvider setAttribute(Object key, Object value) - { - _attributes = _attributes.withPerCallAttribute(key, value); - return this; - } - - /* - /********************************************************** - /* Access to general configuration - /********************************************************** - */ - - /** - * Convenience method for checking whether specified serialization - * feature is enabled or not. - * Shortcut for: - *
-     *  getConfig().isEnabled(feature);
-     *
- */ - public final boolean isEnabled(SerializationFeature feature) { - return _config.isEnabled(feature); - } - - /** - * "Bulk" access method for checking that all features specified by - * mask are enabled. - * - * @since 2.3 - */ - public final boolean hasSerializationFeatures(int featureMask) { - return _config.hasSerializationFeatures(featureMask); - } - - /** - * Convenience method for accessing provider to find serialization filters used, - * equivalent to calling: - *
-     *   getConfig().getFilterProvider();
-     *
- */ - public final FilterProvider getFilterProvider() { - return _config.getFilterProvider(); - } - - /** - *

- * NOTE: current implementation simply returns `null` as generator is not yet - * assigned to this provider. - * - * @since 2.8 - */ - public JsonGenerator getGenerator() { - return null; - } - - /* - /********************************************************************** - /* Factory methods for getting appropriate TokenBuffer instances - /* (possibly overridden by backends for alternate data formats) - /********************************************************************** - */ - - /** - * Specialized factory method used when we are converting values and do not - * typically have or use "real" parsers or generators. - * - * @since 2.13 - */ - public TokenBuffer bufferForValueConversion(ObjectCodec oc) { - // false -> no native type/object ids - return new TokenBuffer(oc, false); - } - - /** - * Specialized factory method used when we are converting values and do not - * typically have or use "real" parsers or generators. - * - * @since 2.13 - */ - public final TokenBuffer bufferForValueConversion() { - return bufferForValueConversion(null); - } - - /* - /********************************************************** - /* Access to Object Id aspects - /********************************************************** - */ - - /** - * Method called to find the Object Id for given POJO, if one - * has been generated. Will always return a non-null Object; - * contents vary depending on whether an Object Id already - * exists or not. - */ - public abstract WritableObjectId findObjectId(Object forPojo, - ObjectIdGenerator generatorType); - - /* - /********************************************************** - /* General serializer locating functionality - /********************************************************** - */ - - /** - * Method called to get hold of a serializer for a value of given type; - * or if no such serializer can be found, a default handler (which - * may do a best-effort generic serialization or just simply - * throw an exception when invoked). - *

- * Note: this method is only called for non-null values; not for keys - * or null values. For these, check out other accessor methods. - *

- * Note that serializers produced should NOT handle polymorphic serialization - * aspects; separate {@link TypeSerializer} is to be constructed by caller - * if and as necessary. - * - * @throws JsonMappingException if there are fatal problems with - * accessing suitable serializer; including that of not - * finding any serializer - */ - @SuppressWarnings("unchecked") - public JsonSerializer findValueSerializer(Class valueType, BeanProperty property) - throws JsonMappingException - { - // Fast lookup from local lookup thingy works? - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - // If not, maybe shared map already has it? - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - // ... possibly as fully typed? - ser = _serializerCache.untypedValueSerializer(_config.constructType(valueType)); - if (ser == null) { - // If neither, must create - ser = _createAndCacheUntypedSerializer(valueType); - // Not found? Must use the unknown type serializer, which will report error later on - if (ser == null) { - ser = getUnknownTypeSerializer(valueType); - // Should this be added to lookups? - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - return ser; - } - } - } - } - // at this point, resolution has occured, but not contextualization - return (JsonSerializer) handleSecondaryContextualization(ser, property); - } - - /** - * Similar to {@link #findValueSerializer(Class,BeanProperty)}, but takes - * full generics-aware type instead of raw class. - * This is necessary for accurate handling of external type information, - * to handle polymorphic types. - *

- * Note: this call will also contextualize serializer before returning it. - * - * @param property When creating secondary serializers, property for which - * serializer is needed: annotations of the property (or bean that contains it) - * may be checked to create contextual serializers. - */ - @SuppressWarnings("unchecked") - public JsonSerializer findValueSerializer(JavaType valueType, BeanProperty property) - throws JsonMappingException - { - if (valueType == null) { - reportMappingProblem("Null passed for `valueType` of `findValueSerializer()`"); - } - // (see comments from above method) - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(valueType); - if (ser == null) { - ser = getUnknownTypeSerializer(valueType.getRawClass()); - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - return ser; - } - } - } - return (JsonSerializer) handleSecondaryContextualization(ser, property); - } - - /** - * Method variant used when we do NOT want contextualization to happen; it will need - * to be handled at a later point, but caller wants to be able to do that - * as needed; sometimes to avoid infinite loops - * - * @since 2.5 - */ - public JsonSerializer findValueSerializer(Class valueType) throws JsonMappingException - { - // (see comments from above method) - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(_config.constructType(valueType)); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(valueType); - if (ser == null) { - ser = getUnknownTypeSerializer(valueType); - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - } - } - } - } - return ser; - } - - /** - * Method variant used when we do NOT want contextualization to happen; it will need - * to be handled at a later point, but caller wants to be able to do that - * as needed; sometimes to avoid infinite loops - * - * @since 2.5 - */ - public JsonSerializer findValueSerializer(JavaType valueType) - throws JsonMappingException - { - // (see comments from above method) - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(valueType); - if (ser == null) { - ser = getUnknownTypeSerializer(valueType.getRawClass()); - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - } - } - } - return ser; - } - - /** - * Similar to {@link #findValueSerializer(JavaType, BeanProperty)}, but used - * when finding "primary" property value serializer (one directly handling - * value of the property). Difference has to do with contextual resolution, - * and method(s) called: this method should only be called when caller is - * certain that this is the primary property value serializer. - * - * @param valueType Type of values to serialize - * @param property Property that is being handled; will never be null, and its - * type has to match valueType parameter. - * - * @since 2.3 - */ - @SuppressWarnings("unchecked") - public JsonSerializer findPrimaryPropertySerializer(JavaType valueType, BeanProperty property) - throws JsonMappingException - { - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(valueType); - if (ser == null) { - ser = getUnknownTypeSerializer(valueType.getRawClass()); - // Should this be added to lookups? - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - return ser; - } - } - } - return (JsonSerializer) handlePrimaryContextualization(ser, property); - } - - /** - * See {@link #findPrimaryPropertySerializer(JavaType, BeanProperty)} - * - * @since 2.3 - */ - @SuppressWarnings("unchecked") - public JsonSerializer findPrimaryPropertySerializer(Class valueType, - BeanProperty property) - throws JsonMappingException - { - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(_config.constructType(valueType)); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(valueType); - if (ser == null) { - ser = getUnknownTypeSerializer(valueType); - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - return ser; - } - } - } - } - return (JsonSerializer) handlePrimaryContextualization(ser, property); - } - - /** - * Alternative to {@link #findPrimaryPropertySerializer(JavaType, BeanProperty)} called not - * for primary value, but "content" of such primary serializer: element of an array or - * {@link java.util.Collection}, value of {@link java.util.Map} entry and so on. - * This means that {@code property} passed (if any) does NOT represent value for which - * serializer is requested but its secondary type (or secondary type of that type, - * recursively). - *

- * Serializer returned SHOULD NOT handle type information; caller will (have to) add - * suitable wrapping if necessary. - *

- * Note: this call will also contextualize serializer (call {@code createContextual()} - * before returning it, if applicable (implements {@code ContextualSerializer}) - * - * @param valueType Type of values to serialize - * @param property Property that indirectly refers to value being serialized (optional, - * may be {@code null} for root level serializers) - * - * @since 2.11 - */ - @SuppressWarnings("unchecked") - public JsonSerializer findContentValueSerializer(JavaType valueType, BeanProperty property) - throws JsonMappingException - { - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(valueType); - if (ser == null) { - ser = getUnknownTypeSerializer(valueType.getRawClass()); - // Should this be added to lookups? - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - return ser; - } - } - } - return (JsonSerializer) handleSecondaryContextualization(ser, property); - } - - /** - * See {@link #findContentValueSerializer(JavaType, BeanProperty)}. - * - * @since 2.11 - */ - @SuppressWarnings("unchecked") - public JsonSerializer findContentValueSerializer(Class valueType, - BeanProperty property) - throws JsonMappingException - { - JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(valueType); - if (ser == null) { - ser = _serializerCache.untypedValueSerializer(_config.constructType(valueType)); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(valueType); - if (ser == null) { - ser = getUnknownTypeSerializer(valueType); - if (CACHE_UNKNOWN_MAPPINGS) { - _serializerCache.addAndResolveNonTypedSerializer(valueType, ser, this); - } - return ser; - } - } - } - } - return (JsonSerializer) handleSecondaryContextualization(ser, property); - } - - /** - * Method called to locate regular serializer, matching type serializer, - * and if both found, wrap them in a serializer that calls both in correct - * sequence. This method is currently only used for root-level serializer - * handling to allow for simpler caching. A call can always be replaced - * by equivalent calls to access serializer and type serializer separately. - * - * @param valueType Type for purpose of locating a serializer; usually dynamic - * runtime type, but can also be static declared type, depending on configuration - * @param cache Whether resulting value serializer should be cached or not; this is just - * a hint - * @param property When creating secondary serializers, property for which - * serializer is needed: annotations of the property (or bean that contains it) - * may be checked to create contextual serializers. - */ - public JsonSerializer findTypedValueSerializer(Class valueType, - boolean cache, BeanProperty property) - throws JsonMappingException - { - // Two-phase lookups; local non-shared cache, then shared: - JsonSerializer ser = _knownSerializers.typedValueSerializer(valueType); - if (ser != null) { - return ser; - } - // If not, maybe shared map already has it? - ser = _serializerCache.typedValueSerializer(valueType); - if (ser != null) { - return ser; - } - - // Well, let's just compose from pieces: - ser = findValueSerializer(valueType, property); - TypeSerializer typeSer = _serializerFactory.createTypeSerializer(_config, - _config.constructType(valueType)); - if (typeSer != null) { - typeSer = typeSer.forProperty(property); - ser = new TypeWrappedSerializer(typeSer, ser); - } - if (cache) { - _serializerCache.addTypedSerializer(valueType, ser); - } - return ser; - } - - /** - * Method called to locate regular serializer, matching type serializer, - * and if both found, wrap them in a serializer that calls both in correct - * sequence. This method is currently only used for root-level serializer - * handling to allow for simpler caching. A call can always be replaced - * by equivalent calls to access serializer and type serializer separately. - * - * @param valueType Declared type of value being serialized (which may not - * be actual runtime type); used for finding both value serializer and - * type serializer to use for adding polymorphic type (if any) - * @param cache Whether resulting value serializer should be cached or not; this is just - * a hint - * @param property When creating secondary serializers, property for which - * serializer is needed: annotations of the property (or bean that contains it) - * may be checked to create contextual serializers. - */ - public JsonSerializer findTypedValueSerializer(JavaType valueType, boolean cache, - BeanProperty property) - throws JsonMappingException - { - // Two-phase lookups; local non-shared cache, then shared: - JsonSerializer ser = _knownSerializers.typedValueSerializer(valueType); - if (ser != null) { - return ser; - } - // If not, maybe shared map already has it? - ser = _serializerCache.typedValueSerializer(valueType); - if (ser != null) { - return ser; - } - - // Well, let's just compose from pieces: - ser = findValueSerializer(valueType, property); - TypeSerializer typeSer = _serializerFactory.createTypeSerializer(_config, valueType); - if (typeSer != null) { - typeSer = typeSer.forProperty(property); - ser = new TypeWrappedSerializer(typeSer, ser); - } - if (cache) { - _serializerCache.addTypedSerializer(valueType, ser); - } - return ser; - } - - /** - * Method called to get the {@link TypeSerializer} to use for including Type Id necessary - * for serializing for the given Java class. - * Useful for schema generators. - * - * @since 2.6 - */ - public TypeSerializer findTypeSerializer(JavaType javaType) throws JsonMappingException { - return _serializerFactory.createTypeSerializer(_config, javaType); - } - - /** - * Method called to get the serializer to use for serializing - * non-null Map keys. Separation from regular - * {@link #findValueSerializer} method is because actual write - * method must be different (@link JsonGenerator#writeFieldName}; - * but also since behavior for some key types may differ. - *

- * Note that the serializer itself can be called with instances - * of any Java object, but not nulls. - */ - public JsonSerializer findKeySerializer(JavaType keyType, BeanProperty property) - throws JsonMappingException - { - JsonSerializer ser = _serializerFactory.createKeySerializer(this, keyType, _keySerializer); - // 25-Feb-2011, tatu: As per [JACKSON-519], need to ensure contextuality works here, too - return _handleContextualResolvable(ser, property); - } - - /** - * @since 2.7 - */ - public JsonSerializer findKeySerializer(Class rawKeyType, BeanProperty property) - throws JsonMappingException - { - return findKeySerializer(_config.constructType(rawKeyType), property); - } - - /* - /******************************************************** - /* Accessors for specialized serializers - /******************************************************** - */ - - /** - * @since 2.0 - */ - public JsonSerializer getDefaultNullKeySerializer() { - return _nullKeySerializer; - } - - /** - * @since 2.0 - */ - public JsonSerializer getDefaultNullValueSerializer() { - return _nullValueSerializer; - } - - /** - * Method called to find a serializer to serializes Map keys that are nulls, - * as JSON does not allow any non-String value as a key, including null. - * Note that type is completely based on declared type, - * since nulls in Java have no type and thus runtime type cannot be - * determined. - * - * @return JsonSerializer that handles the serialization of null keys, - * usually by throwing an exception or using an empty String, - * but other behaviors are also possible. - * - * @since 2.0 - */ - public JsonSerializer findNullKeySerializer(JavaType serializationType, - BeanProperty property) - throws JsonMappingException - { - return _nullKeySerializer; - } - - /** - * Method called to get the serializer to use for serializing null - * values for specified property. - *

- * Default implementation simply calls {@link #getDefaultNullValueSerializer()}; - * can be overridden to add custom null serialization for properties - * of certain type or name. This gives method full granularity to basically - * override null handling for any specific property or class of properties. - * - * @since 2.0 - */ - public JsonSerializer findNullValueSerializer(BeanProperty property) - throws JsonMappingException { - return _nullValueSerializer; - } - - /** - * Method called to get the serializer to use if provider - * cannot determine an actual type-specific serializer - * to use; typically when none of {@link SerializerFactory} - * instances are able to construct a serializer. - *

- * Typically, returned serializer will throw an exception, - * although alternatively {@link com.fasterxml.jackson.databind.ser.std.ToStringSerializer} - * could be returned as well. - * - * @param unknownType Type for which no serializer is found - */ - public JsonSerializer getUnknownTypeSerializer(Class unknownType) { - // 23-Apr-2015, tatu: Only return shared instance if nominal type is Object.class - if (unknownType == Object.class) { - return _unknownTypeSerializer; - } - // otherwise construct explicit instance with property handled type - return new UnknownSerializer(unknownType); - } - - /** - * Helper method called to see if given serializer is considered to be - * something returned by {@link #getUnknownTypeSerializer}, that is, something - * for which no regular serializer was found or constructed. - * - * @since 2.5 - */ - public boolean isUnknownTypeSerializer(JsonSerializer ser) { - if ((ser == _unknownTypeSerializer) || (ser == null)) { - return true; - } - // 23-Apr-2015, tatu: "empty" serializer is trickier; needs to consider - // error handling - if (isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS)) { - if (ser.getClass() == UnknownSerializer.class) { - return true; - } - } - return false; - } - - /* - /********************************************************** - /* Methods for creating instances based on annotations - /********************************************************** - */ - - /** - * Method that can be called to construct and configure serializer instance, - * either given a {@link Class} to instantiate (with default constructor), - * or an uninitialized serializer instance. - * Either way, serialize will be properly resolved - * (via {@link com.fasterxml.jackson.databind.ser.ResolvableSerializer}) and/or contextualized - * (via {@link com.fasterxml.jackson.databind.ser.ContextualSerializer}) as necessary. - * - * @param annotated Annotated entity that contained definition - * @param serDef Serializer definition: either an instance or class - */ - public abstract JsonSerializer serializerInstance(Annotated annotated, - Object serDef) - throws JsonMappingException; - - /** - * Method that can be called to construct and configure {@link JsonInclude} - * filter instance, - * given a {@link Class} to instantiate (with default constructor, by default). - * - * @param forProperty (optional) If filter is created for a property, that property; - * `null` if filter created via defaulting, global or per-type. - * - * @since 2.9 - */ - public abstract Object includeFilterInstance(BeanPropertyDefinition forProperty, - Class filterClass) - throws JsonMappingException; - - /** - * Follow-up method that may be called after calling {@link #includeFilterInstance}, - * to check handling of `null` values by the filter. - * - * @since 2.9 - */ - public abstract boolean includeFilterSuppressNulls(Object filter) - throws JsonMappingException; - - /* - /********************************************************** - /* Support for contextualization - /********************************************************** - */ - - /** - * Method called for primary property serializers (ones - * directly created to serialize values of a POJO property), - * to handle details of resolving - * {@link ContextualSerializer} with given property context. - * - * @param property Property for which the given primary serializer is used; never null. - * - * @since 2.3 - */ - public JsonSerializer handlePrimaryContextualization(JsonSerializer ser, - BeanProperty property) - throws JsonMappingException - { - if (ser != null) { - if (ser instanceof ContextualSerializer) { - ser = ((ContextualSerializer) ser).createContextual(this, property); - } - } - return ser; - } - - /** - * Method called for secondary property serializers (ones - * NOT directly created to serialize values of a POJO property - * but instead created as a dependant serializer -- such as value serializers - * for structured types, or serializers for root values) - * to handle details of resolving - * {@link ContextualDeserializer} with given property context. - * Given that these serializers are not directly related to given property - * (or, in case of root value property, to any property), annotations - * accessible may or may not be relevant. - * - * @param property Property for which serializer is used, if any; null - * when deserializing root values - * - * @since 2.3 - */ - public JsonSerializer handleSecondaryContextualization(JsonSerializer ser, - BeanProperty property) - throws JsonMappingException - { - if (ser != null) { - if (ser instanceof ContextualSerializer) { - ser = ((ContextualSerializer) ser).createContextual(this, property); - } - } - return ser; - } - - /* - /******************************************************** - /* Convenience methods for serializing using default methods - /******************************************************** - */ - - /** - * Convenience method that will serialize given value (which can be - * null) using standard serializer locating functionality. It can - * be called for all values including field and Map values, but usually - * field values are best handled calling - * {@link #defaultSerializeField} instead. - */ - public final void defaultSerializeValue(Object value, JsonGenerator gen) throws IOException - { - if (value == null) { - if (_stdNullValueSerializer) { // minor perf optimization - gen.writeNull(); - } else { - _nullValueSerializer.serialize(null, gen, this); - } - } else { - Class cls = value.getClass(); - findTypedValueSerializer(cls, true, null).serialize(value, gen, this); - } - } - - /** - * Convenience method that will serialize given field with specified - * value. Value may be null. Serializer is done using the usual - * null) using standard serializer locating functionality. - */ - public final void defaultSerializeField(String fieldName, Object value, JsonGenerator gen) - throws IOException - { - gen.writeFieldName(fieldName); - if (value == null) { - /* Note: can't easily check for suppression at this point - * any more; caller must check it. - */ - if (_stdNullValueSerializer) { // minor perf optimization - gen.writeNull(); - } else { - _nullValueSerializer.serialize(null, gen, this); - } - } else { - Class cls = value.getClass(); - findTypedValueSerializer(cls, true, null).serialize(value, gen, this); - } - } - - /** - * Method that will handle serialization of Date(-like) values, using - * {@link SerializationConfig} settings to determine expected serialization - * behavior. - * Note: date here means "full" date, that is, date AND time, as per - * Java convention (and not date-only values like in SQL) - */ - public final void defaultSerializeDateValue(long timestamp, JsonGenerator gen) - throws IOException - { - if (isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { - gen.writeNumber(timestamp); - } else { - gen.writeString(_dateFormat().format(new Date(timestamp))); - } - } - - /** - * Method that will handle serialization of Date(-like) values, using - * {@link SerializationConfig} settings to determine expected serialization - * behavior. - * Note: date here means "full" date, that is, date AND time, as per - * Java convention (and not date-only values like in SQL) - */ - public final void defaultSerializeDateValue(Date date, JsonGenerator gen) throws IOException - { - if (isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { - gen.writeNumber(date.getTime()); - } else { - gen.writeString(_dateFormat().format(date)); - } - } - - /** - * Method that will handle serialization of Dates used as {@link java.util.Map} keys, - * based on {@link SerializationFeature#WRITE_DATE_KEYS_AS_TIMESTAMPS} - * value (and if using textual representation, configured date format) - */ - public void defaultSerializeDateKey(long timestamp, JsonGenerator gen) throws IOException - { - if (isEnabled(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)) { - gen.writeFieldName(String.valueOf(timestamp)); - } else { - gen.writeFieldName(_dateFormat().format(new Date(timestamp))); - } - } - - /** - * Method that will handle serialization of Dates used as {@link java.util.Map} keys, - * based on {@link SerializationFeature#WRITE_DATE_KEYS_AS_TIMESTAMPS} - * value (and if using textual representation, configured date format) - */ - public void defaultSerializeDateKey(Date date, JsonGenerator gen) throws IOException - { - if (isEnabled(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)) { - gen.writeFieldName(String.valueOf(date.getTime())); - } else { - gen.writeFieldName(_dateFormat().format(date)); - } - } - - public final void defaultSerializeNull(JsonGenerator gen) throws IOException - { - if (_stdNullValueSerializer) { // minor perf optimization - gen.writeNull(); - } else { - _nullValueSerializer.serialize(null, gen, this); - } - } - - /* - /******************************************************** - /* Error reporting - /******************************************************** - */ - - /** - * Helper method called to indicate problem; default behavior is to construct and - * throw a {@link JsonMappingException}, but in future may collect more than one - * and only throw after certain number, or at the end of serialization. - * - * @since 2.8 - */ - public void reportMappingProblem(String message, Object... args) throws JsonMappingException { - throw mappingException(message, args); - } - - /** - * Helper method called to indicate problem in POJO (serialization) definitions or settings - * regarding specific Java type, unrelated to actual JSON content to map. - * Default behavior is to construct and throw a {@link JsonMappingException}. - * - * @since 2.9 - */ - @Override // since 2.18 - public T reportBadTypeDefinition(BeanDescription bean, - String msg, Object... msgArgs) throws JsonMappingException { - String beanDesc = (bean == null) ? "N/A" : ClassUtil.nameOf(bean.getBeanClass()); - msg = String.format("Invalid type definition for type %s: %s", - beanDesc, _format(msg, msgArgs)); - throw InvalidDefinitionException.from(getGenerator(), msg, bean, null); - } - - /** - * Helper method called to indicate problem in POJO (serialization) definitions or settings - * regarding specific property (of a type), unrelated to actual JSON content to map. - * Default behavior is to construct and throw a {@link JsonMappingException}. - * - * @since 2.9 - */ - public T reportBadPropertyDefinition(BeanDescription bean, BeanPropertyDefinition prop, - String message, Object... msgArgs) throws JsonMappingException { - message = _format(message, msgArgs); - String propName = "N/A"; - if (prop != null) { - propName = _quotedString(prop.getName()); - } - String beanDesc = "N/A"; - if (bean != null) { - beanDesc = ClassUtil.nameOf(bean.getBeanClass()); - } - message = String.format("Invalid definition for property %s (of type %s): %s", - propName, beanDesc, message); - throw InvalidDefinitionException.from(getGenerator(), message, bean, prop); - } - - @Override - public T reportBadDefinition(JavaType type, String msg) throws JsonMappingException { - throw InvalidDefinitionException.from(getGenerator(), msg, type); - } - - /** - * @since 2.9 - */ - public T reportBadDefinition(JavaType type, String msg, Throwable cause) - throws JsonMappingException { - throw InvalidDefinitionException.from(getGenerator(), msg, type) - .withCause(cause); - } - - /** - * @since 2.9 - */ - public T reportBadDefinition(Class raw, String msg, Throwable cause) - throws JsonMappingException { - throw InvalidDefinitionException.from(getGenerator(), msg, constructType(raw)) - .withCause(cause); - } - - /** - * Helper method called to indicate problem; default behavior is to construct and - * throw a {@link JsonMappingException}, but in future may collect more than one - * and only throw after certain number, or at the end of serialization. - * - * @since 2.8 - */ - public void reportMappingProblem(Throwable t, String message, Object... msgArgs) throws JsonMappingException { - message = _format(message, msgArgs); - throw JsonMappingException.from(getGenerator(), message, t); - } - - @Override - public JsonMappingException invalidTypeIdException(JavaType baseType, String typeId, - String extraDesc) { - String msg = String.format("Could not resolve type id '%s' as a subtype of %s", - typeId, ClassUtil.getTypeDescription(baseType)); - return InvalidTypeIdException.from(null, _colonConcat(msg, extraDesc), baseType, typeId); - } - - /* - /******************************************************** - /* Error reporting, deprecated methods - /******************************************************** - */ - - /** - * Factory method for constructing a {@link JsonMappingException}; - * usually only indirectly used by calling - * {@link #reportMappingProblem(String, Object...)}. - * - * @since 2.6 - * - * @deprecated Since 2.9 - */ - @Deprecated // since 2.9 - public JsonMappingException mappingException(String message, Object... msgArgs) { - return JsonMappingException.from(getGenerator(), _format(message, msgArgs)); - } - - /** - * Factory method for constructing a {@link JsonMappingException}; - * usually only indirectly used by calling - * {@link #reportMappingProblem(Throwable, String, Object...)} - * - * @since 2.8 - * - * @deprecated Since 2.9 - */ - @Deprecated // since 2.9 - protected JsonMappingException mappingException(Throwable t, String message, Object... msgArgs) { - return JsonMappingException.from(getGenerator(), _format(message, msgArgs), t); - } - - /* - /******************************************************** - /* Helper methods - /******************************************************** - */ - - protected void _reportIncompatibleRootType(Object value, JavaType rootType) throws IOException - { - // One special case: allow primitive/wrapper type coercion - if (rootType.isPrimitive()) { - Class wrapperType = ClassUtil.wrapperType(rootType.getRawClass()); - // If it's just difference between wrapper, primitive, let it slide - if (wrapperType.isAssignableFrom(value.getClass())) { - return; - } - } - reportBadDefinition(rootType, String.format( - "Incompatible types: declared root type (%s) vs %s", - rootType, ClassUtil.classNameOf(value))); - } - - /** - * Method that will try to find a serializer, either from cache - * or by constructing one; but will not return an "unknown" serializer - * if this cannot be done but rather returns null. - * - * @return Serializer if one can be found, null if not. - */ - protected JsonSerializer _findExplicitUntypedSerializer(Class runtimeType) - throws JsonMappingException - { - // Fast lookup from local lookup thingy works? - JsonSerializer ser = _knownSerializers.untypedValueSerializer(runtimeType); - if (ser == null) { - // If not, maybe shared map already has it? - ser = _serializerCache.untypedValueSerializer(runtimeType); - if (ser == null) { - ser = _createAndCacheUntypedSerializer(runtimeType); - } - } - /* 18-Sep-2014, tatu: This is unfortunate patch over related change - * that pushes creation of "unknown type" serializer deeper down - * in BeanSerializerFactory; as a result, we need to "undo" creation - * here. - */ - if (isUnknownTypeSerializer(ser)) { - return null; - } - return ser; - } - - /* - /********************************************************** - /* Low-level methods for actually constructing and initializing - /* serializers - /********************************************************** - */ - - /** - * Method that will try to construct a value serializer; and if - * one is successfully created, cache it for reuse. - */ - protected JsonSerializer _createAndCacheUntypedSerializer(Class rawType) - throws JsonMappingException - { - JavaType fullType = _config.constructType(rawType); - JsonSerializer ser; - try { - ser = _createUntypedSerializer(fullType); - } catch (IllegalArgumentException iae) { - // We better only expose checked exceptions, since those - // are what caller is expected to handle - reportBadDefinition(fullType, ClassUtil.exceptionMessage(iae)); - ser = null; // doesn't matter but compiler whines otherwise - } - - if (ser != null) { - // 21-Dec-2015, tatu: Best to cache for both raw and full-type key - _serializerCache.addAndResolveNonTypedSerializer(rawType, fullType, ser, this); - } - return ser; - } - - protected JsonSerializer _createAndCacheUntypedSerializer(JavaType type) - throws JsonMappingException - { - JsonSerializer ser; - try { - ser = _createUntypedSerializer(type); - } catch (IllegalArgumentException iae) { - // We better only expose checked exceptions, since those - // are what caller is expected to handle - ser = null; - reportMappingProblem(iae, ClassUtil.exceptionMessage(iae)); - } - - if (ser != null) { - // 21-Dec-2015, tatu: Should we also cache using raw key? - _serializerCache.addAndResolveNonTypedSerializer(type, ser, this); - } - return ser; - } - - /** - * @since 2.1 - */ - protected JsonSerializer _createUntypedSerializer(JavaType type) - throws JsonMappingException - { - /* 27-Mar-2015, tatu: Wish I knew exactly why/what, but [databind#738] - * can be prevented by synchronizing on cache (not on 'this', however, - * since there's one instance per serialization). - * Perhaps not-yet-resolved instance might be exposed too early to callers. - */ - // 13-Apr-2018, tatu: Problem does NOT occur any more with late 2.8.x and 2.9.x - // versions, likely due to concurrency fixes for `AnnotatedClass` introspection. - // This sync block could probably be removed; but to minimize any risk of - // regression sync block will only be removed from 3.0. - // 23-Oct-2019, tatu: Due to continuation of 2.x line, removed from 2.11 -// synchronized (_serializerCache) { - return (JsonSerializer)_serializerFactory.createSerializer(this, type); -// } - } - - /** - * Helper method called to resolve and contextualize given - * serializer, if and as necessary. - */ - @SuppressWarnings("unchecked") - protected JsonSerializer _handleContextualResolvable(JsonSerializer ser, - BeanProperty property) - throws JsonMappingException - { - if (ser instanceof ResolvableSerializer) { - ((ResolvableSerializer) ser).resolve(this); - } - return (JsonSerializer) handleSecondaryContextualization(ser, property); - } - - @SuppressWarnings("unchecked") - protected JsonSerializer _handleResolvable(JsonSerializer ser) - throws JsonMappingException - { - if (ser instanceof ResolvableSerializer) { - ((ResolvableSerializer) ser).resolve(this); - } - return (JsonSerializer) ser; - } - - /* - /********************************************************** - /* Internal methods - /********************************************************** - */ - - protected final DateFormat _dateFormat() - { - if (_dateFormat != null) { - return _dateFormat; - } - /* At this point, all timezone configuration should have occurred, with respect - * to default dateformat configuration. But we still better clone - * an instance as formatters are stateful, not thread-safe. - */ - DateFormat df = _config.getDateFormat(); - _dateFormat = df = (DateFormat) df.clone(); - // [databind#939]: 26-Sep-2015, tatu: With 2.6, formatter has been (pre)configured - // with TimeZone, so we should NOT try overriding it unlike with earlier versions - /* - TimeZone tz = getTimeZone(); - if (tz != df.getTimeZone()) { - df.setTimeZone(tz); - } - */ - return df; - } -} diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java index 36b1f59869..18383796c8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java @@ -1,5 +1,8 @@ package com.fasterxml.jackson.databind.ser.std; +import java.io.IOException; +import java.lang.reflect.Type; + import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.*; @@ -11,9 +14,6 @@ import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.Converter; -import java.io.IOException; -import java.lang.reflect.Type; - /** * Serializer implementation where given Java type is first converted * to an intermediate "delegate type" (using a configured @@ -114,6 +114,7 @@ public JsonSerializer createContextual(SerializerProvider provider, BeanPrope if (delegateType == null) { delegateType = _converter.getOutputType(provider.getTypeFactory()); } + // 02-Apr-2015, tatu: For "dynamic case", where type is only specified as // java.lang.Object (or missing generic), [databind#731] if (!delegateType.isJavaLangObject()) { @@ -277,10 +278,17 @@ protected Object convertValue(Object value) { * * @since 2.6 */ + @SuppressWarnings("unchecked") protected JsonSerializer _findSerializer(Object value, SerializerProvider serializers) throws JsonMappingException { // NOTE: will NOT call contextualization - return serializers.findValueSerializer(value.getClass()); + JsonSerializer ser = serializers.findValueSerializer(value.getClass()); + // ... so we need to do it separately + if (ser instanceof ContextualSerializer) { + // 25-Jan-2025, tatu: Should we hold on to `BeanProperty` from `createContextual`? + ser = (JsonSerializer)((ContextualSerializer) ser).createContextual(serializers, null); + } + return ser; } } diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/MapConversion4878Test.java b/src/test/java/com/fasterxml/jackson/databind/convert/MapConversion4878Test.java new file mode 100644 index 0000000000..3da412bec6 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/convert/MapConversion4878Test.java @@ -0,0 +1,59 @@ +package com.fasterxml.jackson.databind.convert; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.module.SimpleSerializers; +import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import com.fasterxml.jackson.databind.util.StdConverter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MapConversion4878Test extends DatabindTestUtil +{ + // [databind#4878] + static class MapWrapper4878 { + final Map value; + + MapWrapper4878(Map value) { + this.value = value; + } + } + + static class WrapperConverter4878 extends StdConverter { + @Override + public Object convert(MapWrapper4878 value) { + return value.value; + } + } + + @SuppressWarnings("serial") + static class Serializers4878 extends SimpleSerializers { + @Override + public JsonSerializer findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) { + Class rawClass = type.getRawClass(); + if (MapWrapper4878.class.isAssignableFrom(rawClass)) { + return new StdDelegatingSerializer(new WrapperConverter4878()); + } + return super.findSerializer(config, type, beanDesc); + } + } + + // [databind#4878] + @Test + public void testMapConverter() throws Exception + { + SimpleModule sm = new SimpleModule(); + sm.setSerializers(new Serializers4878()); + final ObjectMapper mapper = jsonMapperBuilder() + .addModule(sm) + .build(); + String json = mapper.writeValueAsString(new MapWrapper4878(Collections.singletonMap("a", 1))); + assertEquals(a2q("{'a':1}"), json); + } +}