TypeResolverProvider.java
package tools.jackson.databind.jsontype;
import java.util.Collection;
import com.fasterxml.jackson.annotation.*;
import tools.jackson.databind.*;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.introspect.Annotated;
import tools.jackson.databind.introspect.AnnotatedClass;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.jsontype.impl.NoOpTypeDeserializer;
import tools.jackson.databind.jsontype.impl.NoOpTypeSerializer;
import tools.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
/**
* Abstraction used for allowing construction and registration of custom
* {@link TypeResolverBuilder}s, used in turn for actual construction of
* {@link tools.jackson.databind.jsontype.TypeSerializer}s
* and {@link tools.jackson.databind.jsontype.TypeDeserializer}s
* for Polymorphic type handling.
* At this point contains both API and default implementation.
*
* @since 3.0
*/
public class TypeResolverProvider
implements java.io.Serializable
{
private static final long serialVersionUID = 3L;
protected final static StdTypeResolverBuilder NO_RESOLVER = StdTypeResolverBuilder.noTypeInfoBuilder();
/*
/**********************************************************************
/* Public API, for class
/**********************************************************************
*/
/**
* Method for checking if given class has annotations that indicate
* that specific type resolver is to be used for handling instances of given type.
* This includes not only
* instantiating resolver builder, but also configuring it based on
* relevant annotations (not including ones checked with a call to
* {@code findSubtypes()}
*
* @param baseType Base java type of value for which resolver is to be found
* @param classInfo Introspected annotation information for the class (type)
*
* @return Type resolver builder for given type, if one found; null if none
*/
public TypeSerializer findTypeSerializer(SerializationContext ctxt,
JavaType baseType, AnnotatedClass classInfo)
{
final SerializationConfig config = ctxt.getConfig();
TypeResolverBuilder<?> b = _findTypeResolver(config, classInfo, baseType);
// Ok: if there is no explicit type info handler, we may want to
// use a default. If so, config object knows what to use.
if (b == null) {
b = config.getDefaultTyper(baseType);
if (b == null) {
return null;
}
}
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByClass(config, classInfo);
// 10-Jun-2015, tatu: Since not created for Bean Property, no need for post-processing
// wrt EXTERNAL_PROPERTY
return b.buildTypeSerializer(ctxt, baseType, subtypes);
}
public TypeDeserializer findTypeDeserializer(DeserializationContext ctxt,
JavaType baseType, AnnotatedClass classInfo)
{
final DeserializationConfig config = ctxt.getConfig();
TypeResolverBuilder<?> b = _findTypeResolver(config, classInfo, baseType);
// Ok: if there is no explicit type info handler, we may want to
// use a default. If so, config object knows what to use.
if (b == null) {
b = config.getDefaultTyper(baseType);
if (b == null) {
return null;
}
}
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config, classInfo);
// May need to figure out default implementation, if none found yet
// (note: check for abstract type is not 100% mandatory, more of an optimization)
if ((b.getDefaultImpl() == null) && baseType.isAbstract()) {
JavaType defaultType = config.mapAbstractType(baseType);
if ((defaultType != null) && !defaultType.hasRawClass(baseType.getRawClass())) {
b = b.withDefaultImpl(defaultType.getRawClass());
}
}
return b.buildTypeDeserializer(ctxt, baseType, subtypes);
}
/*
/**********************************************************************
/* Public API, for property
/**********************************************************************
*/
public TypeSerializer findPropertyTypeSerializer(SerializationContext ctxt,
AnnotatedMember accessor, JavaType baseType)
{
TypeResolverBuilder<?> b = null;
final SerializationConfig config = ctxt.getConfig();
// As per definition of @JsonTypeInfo, check for annotation only for non-container types
if (!baseType.isContainerType() && !baseType.isReferenceType()) {
b = _findTypeResolver(config, accessor, baseType);
} else {
// [databind#1391]: For container/reference types, only check for @JsonTypeInfo(Id.NONE)
// to allow disabling of default typing (other type info settings apply to content)
TypeResolverBuilder<?> marker = _findTypeResolver(config, accessor, baseType);
if (marker == NO_RESOLVER) {
b = NO_RESOLVER;
}
// Otherwise b stays null to continue with default typing
}
// No annotation on property? Then base it on actual type (and further, default typing if need be)
if (b == null) {
return findTypeSerializer(ctxt, baseType, ctxt.introspectClassAnnotations(baseType));
}
// [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info
if (b == NO_RESOLVER) {
// 07-Dec-2025, tatu: Should we actually do this? (No test coverage yet)
//return NoOpTypeSerializer.instance();
return null;
}
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByClass(
config, accessor, baseType);
// 10-Jun-2015, tatu: Since not created for Bean Property, no need for post-processing
// wrt EXTERNAL_PROPERTY
return b.buildTypeSerializer(ctxt, baseType, subtypes);
}
public TypeDeserializer findPropertyTypeDeserializer(DeserializationContext ctxt,
AnnotatedMember accessor, JavaType baseType)
{
TypeResolverBuilder<?> b = null;
final DeserializationConfig config = ctxt.getConfig();
// As per definition of @JsonTypeInfo, check for annotation only for non-container types
if (!baseType.isContainerType() && !baseType.isReferenceType()) {
b = _findTypeResolver(config, accessor, baseType);
} else {
// [databind#1391]: For container/reference types, only check for @JsonTypeInfo(Id.NONE)
// to allow disabling of default typing (other type info settings apply to content)
TypeResolverBuilder<?> marker = _findTypeResolver(config, accessor, baseType);
if (marker == NO_RESOLVER) {
b = NO_RESOLVER;
}
// Otherwise b stays null to continue with default typing
}
// No annotation on property? Then base it on actual type (and further, default typing if need be)
if (b == null) {
return findTypeDeserializer(ctxt, baseType, ctxt.introspectClassAnnotations(baseType));
}
// [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info
if (b == NO_RESOLVER) {
// 07-Dec-2025, tatu: Should we actually do this? (No test coverage yet)
//return NoOpTypeDeserializer.forBaseType(ctxt, baseType);
return null;
}
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config,
accessor, baseType);
// May need to figure out default implementation, if none found yet
// (note: check for abstract type is not 100% mandatory, more of an optimization)
if ((b.getDefaultImpl() == null) && baseType.isAbstract()) {
JavaType defaultType = config.mapAbstractType(baseType);
if ((defaultType != null) && !defaultType.hasRawClass(baseType.getRawClass())) {
b = b.withDefaultImpl(defaultType.getRawClass());
}
}
return b.buildTypeDeserializer(ctxt, baseType, subtypes);
}
public TypeSerializer findPropertyContentTypeSerializer(SerializationContext ctxt,
AnnotatedMember accessor, JavaType containerType)
{
final JavaType contentType = containerType.getContentType();
// First: let's ensure property is a container type: caller should have
// verified but just to be sure
if (contentType == null) {
throw new IllegalArgumentException("Must call method with a container or reference type (got "+containerType+")");
}
final SerializationConfig config = ctxt.getConfig();
TypeResolverBuilder<?> b = _findTypeResolver(config, accessor, containerType);
// No annotation on property? Then base it on actual type (and further, default typing if need be)
if (b == null) {
return findTypeSerializer(ctxt, contentType,
ctxt.introspectClassAnnotations(contentType.getRawClass()));
}
// [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info
if (b == NO_RESOLVER) {
return NoOpTypeSerializer.instance();
}
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByClass(
config, accessor, contentType);
return b.buildTypeSerializer(ctxt, contentType, subtypes);
}
public TypeDeserializer findPropertyContentTypeDeserializer(DeserializationContext ctxt,
AnnotatedMember accessor, JavaType containerType)
{
final JavaType contentType = containerType.getContentType();
final DeserializationConfig config = ctxt.getConfig();
// First: let's ensure property is a container type: caller should have
// verified but just to be sure
if (contentType == null) {
throw new IllegalArgumentException("Must call method with a container or reference type (got "+containerType+")");
}
TypeResolverBuilder<?> b = _findTypeResolver(config, accessor, containerType);
if (b == null) {
return findTypeDeserializer(ctxt, contentType, ctxt.introspectClassAnnotations(contentType));
}
// [databind#1654]: Explicit `@JsonTypeInfo(Id.NONE)` should block class-level type info
if (b == NO_RESOLVER) {
return NoOpTypeDeserializer.forBaseType(ctxt, contentType);
}
Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypesByTypeId(config,
accessor, contentType);
// May need to figure out default implementation, if none found yet
// (note: check for abstract type is not 100% mandatory, more of an optimization)
if ((b.getDefaultImpl() == null) && contentType.isAbstract()) {
JavaType defaultType = config.mapAbstractType(contentType);
if ((defaultType != null) && !defaultType.hasRawClass(contentType.getRawClass())) {
b = b.withDefaultImpl(defaultType.getRawClass());
}
}
return b.buildTypeDeserializer(ctxt, contentType, subtypes);
}
/*
/**********************************************************************
/* Helper methods
/**********************************************************************
*/
protected TypeResolverBuilder<?> _findTypeResolver(MapperConfig<?> config,
Annotated ann, JavaType baseType)
{
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
JsonTypeInfo.Value typeInfo = ai.findPolymorphicTypeInfo(config, ann);
// First: maybe we have explicit type resolver?
TypeResolverBuilder<?> b;
Object customResolverOb = ai.findTypeResolverBuilder(config, ann);
if (customResolverOb != null) {
// 08-Mar-2018, tatu: Should `NONE` block custom one? Or not?
if ((typeInfo != null) && (typeInfo.getIdType() == JsonTypeInfo.Id.NONE)) {
return null;
}
if (customResolverOb instanceof Class<?>) {
@SuppressWarnings("unchecked")
Class<TypeResolverBuilder<?>> cls = (Class<TypeResolverBuilder<?>>) customResolverOb;
b = config.typeResolverBuilderInstance(ann, cls);
} else {
b = (TypeResolverBuilder<?>) customResolverOb;
}
} else { // if not, use standard one, but only if indicated by annotations
if (typeInfo == null) {
return null;
}
// bit special; must return 'marker' to block use of default typing:
if (typeInfo.getIdType() == JsonTypeInfo.Id.NONE) {
return NO_RESOLVER;
}
// 13-Aug-2011, tatu: One complication; external id
// only works for properties; so if declared for a Class, we will need
// to map it to "PROPERTY" instead of "EXTERNAL_PROPERTY"
if (ann instanceof AnnotatedClass) {
JsonTypeInfo.As inclusion = typeInfo.getInclusionType();
if (inclusion == JsonTypeInfo.As.EXTERNAL_PROPERTY) {
typeInfo = typeInfo.withInclusionType(JsonTypeInfo.As.PROPERTY);
}
}
b = _constructStdTypeResolverBuilder(config, typeInfo, baseType);
}
// Does it define a custom type id resolver?
Object customIdResolverOb = ai.findTypeIdResolver(config, ann);
TypeIdResolver idResolver = null;
if (customIdResolverOb != null) {
if (customIdResolverOb instanceof Class<?>) {
@SuppressWarnings("unchecked")
Class<TypeIdResolver> cls = (Class<TypeIdResolver>) customIdResolverOb;
idResolver = config.typeIdResolverInstance(ann, cls);
idResolver.init(baseType);
}
}
b = b.init(typeInfo, idResolver);
return b;
}
protected TypeResolverBuilder<?> _constructStdTypeResolverBuilder(MapperConfig<?> config,
JsonTypeInfo.Value typeInfo, JavaType baseType) {
return new StdTypeResolverBuilder(typeInfo);
}
}