package com.fasterxml.jackson.databind.module;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
Vanilla Module
implementation that allows registration of serializers and deserializers, bean serializer and deserializer modifiers, registration of subtypes and mix-ins as well as some other commonly needed aspects (addition of custom AbstractTypeResolver
s, ValueInstantiator
s). NOTE: although it is not expected that sub-types should need to override setupModule(SetupContext)
method, if they choose to do so they MUST call super.setupModule(context);
to ensure that registration works as expected.
WARNING: when registering JsonSerializer
s and JsonDeserializer
s, only type erased Class
is compared: this means that usually you should NOT use this implementation for registering structured types such as Collection
s or Map
s: this because parametric type information will not be considered and you may end up having "wrong" handler for your type. What you need to do, instead, is to implement Deserializers
and/or Serializers
callbacks to match full type signatures (with JavaType
).
/**
* Vanilla {@link Module} implementation that allows registration
* of serializers and deserializers, bean serializer
* and deserializer modifiers, registration of subtypes and mix-ins
* as well as some other commonly
* needed aspects (addition of custom {@link AbstractTypeResolver}s,
* {@link com.fasterxml.jackson.databind.deser.ValueInstantiator}s).
*<p>
* NOTE: although it is not expected that sub-types should need to
* override {@link #setupModule(SetupContext)} method, if they choose
* to do so they MUST call {@code super.setupModule(context);}
* to ensure that registration works as expected.
*<p>
* WARNING: when registering {@link JsonSerializer}s and {@link JsonDeserializer}s,
* only type erased {@code Class} is compared: this means that usually you should
* NOT use this implementation for registering structured types such as
* {@link java.util.Collection}s or {@link java.util.Map}s: this because parametric
* type information will not be considered and you may end up having "wrong" handler
* for your type.
* What you need to do, instead, is to implement {@link com.fasterxml.jackson.databind.deser.Deserializers}
* and/or {@link com.fasterxml.jackson.databind.ser.Serializers} callbacks to match full type
* signatures (with {@link JavaType}).
*/
public class SimpleModule
extends com.fasterxml.jackson.databind.Module
implements java.io.Serializable
{
private static final long serialVersionUID = 1L; // 2.5.0
// 16-Jun-2021, tatu: For [databind#3110], generate actual unique ids
// for SimpleModule instances (System.identityHashCode(...) is close
// but not quite it...
private static final AtomicInteger MODULE_ID_SEQ = new AtomicInteger(1);
protected final String _name;
protected final Version _version;
Flag that indicates whether module was given an explicit name or not. Distinction is used to determine whether method getTypeId()
should return name (yes, if explicit) or null
(if no explicit name was passed). Since: 2.13
/**
* Flag that indicates whether module was given an explicit name
* or not. Distinction is used to determine whether method
* {@link #getTypeId()} should return name (yes, if explicit) or
* {@code null} (if no explicit name was passed).
*
* @since 2.13
*/
protected final boolean _hasExplicitName;
protected SimpleSerializers _serializers = null;
protected SimpleDeserializers _deserializers = null;
protected SimpleSerializers _keySerializers = null;
protected SimpleKeyDeserializers _keyDeserializers = null;
Lazily-constructed resolver used for storing mappings from
abstract classes to more specific implementing classes
(which may be abstract or concrete)
/**
* Lazily-constructed resolver used for storing mappings from
* abstract classes to more specific implementing classes
* (which may be abstract or concrete)
*/
protected SimpleAbstractTypeResolver _abstractTypes = null;
Lazily-constructed resolver used for storing mappings from
abstract classes to more specific implementing classes
(which may be abstract or concrete)
/**
* Lazily-constructed resolver used for storing mappings from
* abstract classes to more specific implementing classes
* (which may be abstract or concrete)
*/
protected SimpleValueInstantiators _valueInstantiators = null;
Since: 2.2
/**
* @since 2.2
*/
protected BeanDeserializerModifier _deserializerModifier = null;
Since: 2.2
/**
* @since 2.2
*/
protected BeanSerializerModifier _serializerModifier = null;
Lazily-constructed map that contains mix-in definitions, indexed
by target class, value being mix-in to apply.
/**
* Lazily-constructed map that contains mix-in definitions, indexed
* by target class, value being mix-in to apply.
*/
protected HashMap<Class<?>, Class<?>> _mixins = null;
Set of subtypes to register, if any.
/**
* Set of subtypes to register, if any.
*/
protected LinkedHashSet<NamedType> _subtypes = null;
Since: 2.3
/**
* @since 2.3
*/
protected PropertyNamingStrategy _namingStrategy = null;
/*
/**********************************************************
/* Life-cycle: creation
/**********************************************************
*/
Constructors that should only be used for non-reusable
convenience modules used by app code: "real" modules should
use actual name and version number information.
/**
* Constructors that should only be used for non-reusable
* convenience modules used by app code: "real" modules should
* use actual name and version number information.
*/
public SimpleModule() {
// can't chain when making reference to 'this'
// 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();
_version = Version.unknownVersion();
// 07-Jun-2021, tatu: [databind#3110] Not passed explicitly so...
_hasExplicitName = false;
}
Convenience constructor that will default version to Version.unknownVersion()
. /**
* Convenience constructor that will default version to
* {@link Version#unknownVersion()}.
*/
public SimpleModule(String name) {
this(name, Version.unknownVersion());
}
Convenience constructor that will use specified Version, including name from Version.getArtifactId()
. /**
* Convenience constructor that will use specified Version,
* including name from {@link Version#getArtifactId()}.
*/
public SimpleModule(Version version) {
this(version.getArtifactId(), version);
}
Constructor to use for actual reusable modules.
ObjectMapper may use name as identifier to notice attempts
for multiple registrations of the same module (although it
does not have to).
Params: - name – Unique name of the module
- version – Version of the module
/**
* Constructor to use for actual reusable modules.
* ObjectMapper may use name as identifier to notice attempts
* for multiple registrations of the same module (although it
* does not have to).
*
* @param name Unique name of the module
* @param version Version of the module
*/
public SimpleModule(String name, Version version) {
_name = name;
_version = version;
// 07-Jun-2021, tatu: [databind#3110] Is passed explicitly (may be `null`)
_hasExplicitName = true;
}
Since: 2.1
/**
* @since 2.1
*/
public SimpleModule(String name, Version version,
Map<Class<?>,JsonDeserializer<?>> deserializers) {
this(name, version, deserializers, null);
}
Since: 2.1
/**
* @since 2.1
*/
public SimpleModule(String name, Version version,
List<JsonSerializer<?>> serializers) {
this(name, version, null, serializers);
}
Since: 2.1
/**
* @since 2.1
*/
public SimpleModule(String name, Version version,
Map<Class<?>,JsonDeserializer<?>> deserializers,
List<JsonSerializer<?>> serializers)
{
_name = name;
// 07-Jun-2021, tatu: [databind#3110] Is passed explicitly (may be `null`)
_hasExplicitName = true;
_version = version;
if (deserializers != null) {
_deserializers = new SimpleDeserializers(deserializers);
}
if (serializers != null) {
_serializers = new SimpleSerializers(serializers);
}
}
Since instances are likely to be custom, implementation returns
null
if (but only if!) this class is directly instantiated;
but class name (default impl) for sub-classes.
/**
* Since instances are likely to be custom, implementation returns
* <code>null</code> if (but only if!) this class is directly instantiated;
* but class name (default impl) for sub-classes.
*/
@Override
public Object getTypeId()
{
// 07-Jun-2021, tatu: [databind#3110] Return Type Id if name was
// explicitly given
if (_hasExplicitName) {
return _name;
}
// Otherwise behavior same as with 2.12: no registration id for "throw-away"
// instances (to avoid bogus conflicts if user just instantiates SimpleModule)
// Note: actually... always returning `supet.getTypeId()` should be fine since
// that would return generated id? Let's do that actually.
if (getClass() == SimpleModule.class) {
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.
return super.getTypeId();
}
/*
/**********************************************************
/* Simple setters to allow overriding
/**********************************************************
*/
Resets all currently configured serializers.
/**
* Resets all currently configured serializers.
*/
public void setSerializers(SimpleSerializers s) {
_serializers = s;
}
Resets all currently configured deserializers.
/**
* Resets all currently configured deserializers.
*/
public void setDeserializers(SimpleDeserializers d) {
_deserializers = d;
}
Resets all currently configured key serializers.
/**
* Resets all currently configured key serializers.
*/
public void setKeySerializers(SimpleSerializers ks) {
_keySerializers = ks;
}
Resets all currently configured key deserializers.
/**
* Resets all currently configured key deserializers.
*/
public void setKeyDeserializers(SimpleKeyDeserializers kd) {
_keyDeserializers = kd;
}
Resets currently configured abstract type mappings
/**
* Resets currently configured abstract type mappings
*/
public void setAbstractTypes(SimpleAbstractTypeResolver atr) {
_abstractTypes = atr;
}
Resets all currently configured value instantiators
/**
* Resets all currently configured value instantiators
*/
public void setValueInstantiators(SimpleValueInstantiators svi) {
_valueInstantiators = svi;
}
Since: 2.2
/**
* @since 2.2
*/
public SimpleModule setDeserializerModifier(BeanDeserializerModifier mod) {
_deserializerModifier = mod;
return this;
}
Since: 2.2
/**
* @since 2.2
*/
public SimpleModule setSerializerModifier(BeanSerializerModifier mod) {
_serializerModifier = mod;
return this;
}
Since: 2.3
/**
* @since 2.3
*/
protected SimpleModule setNamingStrategy(PropertyNamingStrategy naming) {
_namingStrategy = naming;
return this;
}
/*
/**********************************************************
/* Configuration methods, adding serializers
/**********************************************************
*/
Method for adding serializer to handle type that the serializer claims to handle (see JsonSerializer.handledType()
). WARNING! Type matching only uses type-erased Class
and should NOT be used when registering serializers for generic types like Collection
and Map
.
/**
* Method for adding serializer to handle type that the serializer claims to handle
* (see {@link JsonSerializer#handledType()}).
*<p>
* WARNING! Type matching only uses type-erased {@code Class} and should NOT
* be used when registering serializers for generic types like
* {@link java.util.Collection} and {@link java.util.Map}.
*/
public SimpleModule addSerializer(JsonSerializer<?> ser)
{
_checkNotNull(ser, "serializer");
if (_serializers == null) {
_serializers = new SimpleSerializers();
}
_serializers.addSerializer(ser);
return this;
}
Method for adding serializer to handle values of specific type.
WARNING! Type matching only uses type-erased Class
and should NOT be used when registering serializers for generic types like Collection
and Map
.
/**
* Method for adding serializer to handle values of specific type.
*<p>
* WARNING! Type matching only uses type-erased {@code Class} and should NOT
* be used when registering serializers for generic types like
* {@link java.util.Collection} and {@link java.util.Map}.
*/
public <T> SimpleModule addSerializer(Class<? extends T> type, JsonSerializer<T> ser)
{
_checkNotNull(type, "type to register serializer for");
_checkNotNull(ser, "serializer");
if (_serializers == null) {
_serializers = new SimpleSerializers();
}
_serializers.addSerializer(type, ser);
return this;
}
public <T> SimpleModule addKeySerializer(Class<? extends T> type, JsonSerializer<T> ser)
{
_checkNotNull(type, "type to register key serializer for");
_checkNotNull(ser, "key serializer");
if (_keySerializers == null) {
_keySerializers = new SimpleSerializers();
}
_keySerializers.addSerializer(type, ser);
return this;
}
/*
/**********************************************************
/* Configuration methods, adding deserializers
/**********************************************************
*/
Method for adding deserializer to handle specified type.
WARNING! Type matching only uses type-erased Class
and should NOT be used when registering serializers for generic types like Collection
and Map
.
/**
* Method for adding deserializer to handle specified type.
*<p>
* WARNING! Type matching only uses type-erased {@code Class} and should NOT
* be used when registering serializers for generic types like
* {@link java.util.Collection} and {@link java.util.Map}.
*/
public <T> SimpleModule addDeserializer(Class<T> type, JsonDeserializer<? extends T> deser)
{
_checkNotNull(type, "type to register deserializer for");
_checkNotNull(deser, "deserializer");
if (_deserializers == null) {
_deserializers = new SimpleDeserializers();
}
_deserializers.addDeserializer(type, deser);
return this;
}
public SimpleModule addKeyDeserializer(Class<?> type, KeyDeserializer deser)
{
_checkNotNull(type, "type to register key deserializer for");
_checkNotNull(deser, "key deserializer");
if (_keyDeserializers == null) {
_keyDeserializers = new SimpleKeyDeserializers();
}
_keyDeserializers.addDeserializer(type, deser);
return this;
}
/*
/**********************************************************
/* Configuration methods, type mapping
/**********************************************************
*/
Lazily-constructed resolver used for storing mappings from
abstract classes to more specific implementing classes
(which may be abstract or concrete)
/**
* Lazily-constructed resolver used for storing mappings from
* abstract classes to more specific implementing classes
* (which may be abstract or concrete)
*/
public <T> SimpleModule addAbstractTypeMapping(Class<T> superType,
Class<? extends T> subType)
{
_checkNotNull(superType, "abstract type to map");
_checkNotNull(subType, "concrete type to map to");
if (_abstractTypes == null) {
_abstractTypes = new SimpleAbstractTypeResolver();
}
// note: addMapping() will verify arguments
_abstractTypes = _abstractTypes.addMapping(superType, subType);
return this;
}
Method for adding set of subtypes to be registered with ObjectMapper
this is an alternative to using annotations in super type to indicate subtypes. /**
* Method for adding set of subtypes to be registered with
* {@link ObjectMapper}
* this is an alternative to using annotations in super type to indicate subtypes.
*/
public SimpleModule registerSubtypes(Class<?> ... subtypes)
{
if (_subtypes == null) {
_subtypes = new LinkedHashSet<>();
}
for (Class<?> subtype : subtypes) {
_checkNotNull(subtype, "subtype to register");
_subtypes.add(new NamedType(subtype));
}
return this;
}
Method for adding set of subtypes (along with type name to use) to be registered with ObjectMapper
this is an alternative to using annotations in super type to indicate subtypes. /**
* Method for adding set of subtypes (along with type name to use) to be registered with
* {@link ObjectMapper}
* this is an alternative to using annotations in super type to indicate subtypes.
*/
public SimpleModule registerSubtypes(NamedType ... subtypes)
{
if (_subtypes == null) {
_subtypes = new LinkedHashSet<>();
}
for (NamedType subtype : subtypes) {
_checkNotNull(subtype, "subtype to register");
_subtypes.add(subtype);
}
return this;
}
Method for adding set of subtypes (along with type name to use) to be registered with ObjectMapper
this is an alternative to using annotations in super type to indicate subtypes. Since: 2.9
/**
* Method for adding set of subtypes (along with type name to use) to be registered with
* {@link ObjectMapper}
* this is an alternative to using annotations in super type to indicate subtypes.
*
* @since 2.9
*/
public SimpleModule registerSubtypes(Collection<Class<?>> subtypes)
{
if (_subtypes == null) {
_subtypes = new LinkedHashSet<>();
}
for (Class<?> subtype : subtypes) {
_checkNotNull(subtype, "subtype to register");
_subtypes.add(new NamedType(subtype));
}
return this;
}
/*
/**********************************************************
/* Configuration methods, add other handlers
/**********************************************************
*/
Method for registering ValueInstantiator
to use when deserializing instances of type beanType
.
Instantiator is
registered when module is registered for ObjectMapper
.
/**
* Method for registering {@link ValueInstantiator} to use when deserializing
* instances of type <code>beanType</code>.
*<p>
* Instantiator is
* registered when module is registered for <code>ObjectMapper</code>.
*/
public SimpleModule addValueInstantiator(Class<?> beanType, ValueInstantiator inst)
{
_checkNotNull(beanType, "class to register value instantiator for");
_checkNotNull(inst, "value instantiator");
if (_valueInstantiators == null) {
_valueInstantiators = new SimpleValueInstantiators();
}
_valueInstantiators = _valueInstantiators.addValueInstantiator(beanType, inst);
return this;
}
Method for specifying that annotations define by mixinClass
should be "mixed in" with annotations that targetType
has (as if they were directly included on it!).
Mix-in annotations are
registered when module is registered for ObjectMapper
.
/**
* Method for specifying that annotations define by <code>mixinClass</code>
* should be "mixed in" with annotations that <code>targetType</code>
* has (as if they were directly included on it!).
*<p>
* Mix-in annotations are
* registered when module is registered for <code>ObjectMapper</code>.
*/
public SimpleModule setMixInAnnotation(Class<?> targetType, Class<?> mixinClass)
{
_checkNotNull(targetType, "target type");
_checkNotNull(mixinClass, "mixin class");
if (_mixins == null) {
_mixins = new HashMap<Class<?>, Class<?>>();
}
_mixins.put(targetType, mixinClass);
return this;
}
/*
/**********************************************************
/* Module impl
/**********************************************************
*/
@Override
public String getModuleName() {
return _name;
}
Standard implementation handles registration of all configured
customizations: it is important that sub-classes call this
implementation (usually before additional custom logic)
if they choose to override it; otherwise customizations
will not be registered.
/**
* Standard implementation handles registration of all configured
* customizations: it is important that sub-classes call this
* implementation (usually before additional custom logic)
* if they choose to override it; otherwise customizations
* will not be registered.
*/
@Override
public void setupModule(SetupContext context)
{
if (_serializers != null) {
context.addSerializers(_serializers);
}
if (_deserializers != null) {
context.addDeserializers(_deserializers);
}
if (_keySerializers != null) {
context.addKeySerializers(_keySerializers);
}
if (_keyDeserializers != null) {
context.addKeyDeserializers(_keyDeserializers);
}
if (_abstractTypes != null) {
context.addAbstractTypeResolver(_abstractTypes);
}
if (_valueInstantiators != null) {
context.addValueInstantiators(_valueInstantiators);
}
if (_deserializerModifier != null) {
context.addBeanDeserializerModifier(_deserializerModifier);
}
if (_serializerModifier != null) {
context.addBeanSerializerModifier(_serializerModifier);
}
if (_subtypes != null && _subtypes.size() > 0) {
context.registerSubtypes(_subtypes.toArray(new NamedType[_subtypes.size()]));
}
if (_namingStrategy != null) {
context.setNamingStrategy(_namingStrategy);
}
if (_mixins != null) {
for (Map.Entry<Class<?>,Class<?>> entry : _mixins.entrySet()) {
context.setMixInAnnotations(entry.getKey(), entry.getValue());
}
}
}
@Override
public Version version() { return _version; }
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
Since: 2.9
/**
* @since 2.9
*/
protected void _checkNotNull(Object thingy, String type)
{
if (thingy == null) {
throw new IllegalArgumentException(String.format(
"Cannot pass `null` as %s", type));
}
}
}