-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Deserializer Discovery 2.x
This page describes the process of discovering JsonDeserializer
s for POJO (aka Bean) types in Jackson 2.x
It serves as the background/context for low-level POJO Property Introspection work.
JsonDeserializer
s are needed for reading JSON (and other supported formats) from JsonParser
and constructing desired Java Objects. Discovery process is initiated by 3 entities:
-
ObjectMapper
to locate deserializer to use for target type indicated forreadValue()
method (andreadValues()
,convertValue()
) -
ObjectReader
(similar toObjectMapper
) - Deserializers themselves, to locate "child deserializers": for example when deserializing
List
s, deserializer for elements contained is separate from deserializer forList
itself (and similarly for other structured types likejava.util.Map
s, Arrays, POJOs)
Discovery process for these cases is almost identical (and in fact, (1) and (2) are identical), differing only in that for (3), contextualization (via method DeserializationContext._handleSecondaryContextualization(...)
) passes referring property definition (BeanProperty
) whereas one does not exist for "root" values.
Two terms for deserializers (as defined by ResolvableDeserializer
and ContextualDeserializer
) are:
- Resolution is needed to handle cyclic dependencies between deserialializers: this is mostly relevant for
BeanDeserializer
. - Contextualization is needed to give initially context-free (only based on Type) deserializers access to annotations added to property (that is, on Field and/or Method and/or Constructor parameter).
Understanding this processing is not strictly necessary to understand call flow, but is useful to know since these are referenced in couple of places.
The first step to trigger discovery is ObjectMapper
(and other entities mentioned earlier) calling one of:
- Explicit
findXxxDeserializer()
method such asfindRootValueDeserializer()
- One of
readXxxValue()
convenience methods likereadValue()
which will need to call one offindXxxDeserializer()
methods
Either way method like findRootValueDeserializer()
is called; and this is the call path we will focus on.
Big part of actual discovery is handled by DeserializerCache
: the main entry point is method findRootValueDeserializer()
(or one of alternatives).
It will not only handle caching of constructed and resolved (but not contextualized) deserializers but also:
- Mapping of
abstract
Java types (as well asMap
andCollection
types) into concrete (forabstract
) and default (Map
,Collection
) implementation types - Introspecting
BeanDescription
(of typeBasicBeanDescription
) for type indicated - Handling annotations on Java types (Classes) that directly indicate
JsonDeserializer
to use (@JsonDeserialize(using = IMPL_CLASS)
) - Refinement of actual type to use (
@JsonDeserialize(as = CONCRETE_TYPE)
); and re-introspectingBeanDescription
for refined type - Use of "converting" deserializers indicated by annotation (and directly constructing
StdDelegatingDeserializer
as needed) - Calling appropriate method of configured
DeserializerFactory
to actually constructJsonDeserializer
to use - Resolution of
ResolveDeserializer
s, to avoidStackOverflowError
for cyclic types (put another way: allowing use of cyclic type definitions)
and last but not least:
- Calling type-specific method in
DeserializerFactory
as necessary to actually construct deserializer.
With given target JavaType
and introspected BeanDescription
, DeserializerCache
will call one of following methods of DeserializerFactory
(selected in following order)
-
createEnumDeserializer()
(ifJavaType.isEnumType()
returnstrue
) -
createArrayDeserializer()
(JavaType.isArrayType()
) -
createMapDeserializer()
/createMapLikeDeserializer()
("Map-like" types refer to Scala (f.ex) types that act likejava.util.Map
but do not implement that interface) -
createCollectionDeserializer()
/createCollectionLikeDeserializer()
("Collection-like" types similarly refer to Scala's collection types that work similar tojava.util.Collection
but do not implement it) -
createReferenceDeserializer()
for types likeAtomicReference<>
(JDK),Optional
(JDK 8+, Guava),Option
(Scala) -
createTreeDeserializer()
forJsonNode
(and subtypes) -
createBeanDeserializer()
if none of above matches
We will focus on the last case, in which general POJO (aka "Bean") deserializer is constructed.
Actual DeserializerFactory
used is BeanDeserializerFactory
, which extends BasicDeserializerFactory
(which implements DeserializerFactory
except for createBeanDeserializer()
).
Here we will focus on BeanDeserializerFactory.createBeanDeserializer()
implementation: it will be covered later on in this document.
High-level description has many branches off for different cases other than producing a standard POJO Deserializer (named BeanDeserializer
for historic reasons).
But let's focus on specific call path/sequence that results in a new BeanDeserializer
So when call is made like:
ObjectMapper mapper = new ObjectMapper();
MyValue value = mapper.readValue(jsonInput, MyValue.class);
we will get a call to DeserializationContext
method findRootValueDeserializer()
.
This will call DeserializerCache.findValueDeserializer()
which will try to locate a deserializer already created, and if failing to do so (which is the case the first time it gets called like above), proceed with discovery and construction of needed deserializer. After deserializer has been returned, it will be contextualized as necessary (by a call to ContextualDeserializer.createContextual()
-- we will not be going through contextualization process here).
- First thing done in this method is to look for already cached deserializers (see
_findCachedDeserializer()
; nothing special here). - Assuming no cached deserializer is found, method
_createAndCacheValueDeserializer()
is called-
_createAndCacheValueDeserializer()
handles synchronization aspects (by keeping track of in-process deserializers in its_incompleteDeserializers
Map), but it calls_createAndCache2()
for further processing.
-
-
_createAndCache2()
will:- delegate actual construction further to
_createDeserializer()
(which is covered in next section) - trigger deserializer (dependency) resolution if deserializer implements
ResolvableDeserializer
(in which case itsresolve()
method is called) - add deserializer into
_cachedDeserializers
if (but only if!) it is eligible for caching
- delegate actual construction further to
This is where most of the magic is found:
- First,
abstract
andCollection
andMap
types are mapped (to "concrete" and "default" types, respectively) - Once we have likely type to use,
BeanDescription
is obtained via Property Introspection throughDeserializationConfig.introspect(JavaType)
method (which in turn callsClassIntrospector.forDeserialization()
method)- !!! This is where Property Introspection occurs !!!
- Actual implementation type will be
BasicBeanDescription
- See the next section, "Detailed call path for
[Basic]BeanDescription
introspection" for details
- Once we have
BeanDescription
, we check if a Class-Annotated deserializer is found (by call tofindDeserializerFromAnnotation()
) and if so, create instance and return it. Check is made by a call to- Annotation checked is
@JsonDeserialize(using = DESER_IMPL.class)
, but lookup is via configuredAnnotationIntrospector
- Annotation checked is
- Otherwise annotations are checked to see if type mapping is needed (from more generic to more specialized type), via call to
modifyTypeByAnnotation()
- Annotation check is
@JsonDeserialize(as = SUBTYPE.class)
, viaAnnotationIntrospector.refineDeserializationType()
(which can refine main type itself as well as Content/Element types forCollection
andMap
types; as well as Key type forMap
s) - If type refinement (to more specific type) occurs, Property Introspection needs to be re-done for the new type -- that is, a new
BeanDescription
is introspected.
- Annotation check is
- Check is made to see if we have Builder-style deserializer (
@JsonDeserialize(builder = BUILDER_IMPL.class)
)- Check is via
BeanDescription.findPOJOBuilder()
- If builder-style construction found,
DeserializerFactory.createBuilderBasedDeserializer()
is called to construct deserializer
- Check is via
- If none found so far, check is made (via
BeanDescription.findDeserializationConverter()
) to see if@JsonDeserialize(converter = CONVERTER_IMPL.class)
is found; if so, aStdConvertingDeserializer
is constructed and returned - Otherwise, if none of above results in a
JsonDeserializer
being constructed,_createDeserializer2()
is called.
This method will create "regular" deserializers of 3 main types:
- Module-provided deserializers (both shared 3rd party modules and application-provided "custom" modules)
- Standard JDK type deserializers like
StringDeserializer
andObjectArrayDeserializer
- POJO (aka Bean) deserializers (
BeanDeserializer
)
Construction is delegated to DeserializerFactory
(API), implementation of which is BeanDeserializerFactory
(which extends BasicDeserializerFactory
, which implements most of DeserializerFactory
).
Factory methods in DeserializerFactory
and callback methods in Deserializers
(API provided by Module
s to provide custom deserializers) are divided into in about 10 different types. Specifically, DeserializerFactory
exposes following methods to call, checked in specified order:
-
createEnumDeserializer()
(if target type'sJavaType.isEnumType()
returns true) -
createArrayDeserializer()
(JavaType.isArrayType()
) -
createMapDeserializer()
/createMapLikeDeserializer()
(for truejava.util.Map
types / "Map-like" type such as Scala Maps that work like Maps but do not implementjava.util.Map
) -
createCollectionDeserializer()
/createCollectionLikeDeserializer()
(for truejava.util.Collection
/ "Collection-like", for Scala Collections etc) -
createReferenceDeserializer()
(JavaType.isReferenceType()
: true for JDKAtomicReference
, JDK8+/GuavaOptional
, ScalaOption
) -
createTreeDeserializer()
(forJsonNode
and its subtypes) -
createBeanDeserializer()
for everything else
Of these, all but last method are implemented by BasicDeserializerFactory
: the last method is implemented by BeanDeserializerFactory
.
DeserializerFactory
is configured with access to callbacks Module
s implement to provide custom deserializer implementations (this is case (1)); mapping to standard JDK type deserializers (case (2)); and method for actually construction BeanDeserializer
s otherwise (case (3)).
Processing for all createXxxDeserializer()
methods consists of following sequence:
- Check if there is Module-provided implementation (by calling matching callback method via
Deserializers
modules registers withDeserializerFactory
); if so, return - (for Structured types like Arrays,
Collection
(and "CollectionLike"),Map
(and "MapLike")) Discover content type, fetch content deserializer if available (via annotations, or static type) -- if not, will be left for Contextualization to provide - Construct Standard deserializer
- Allow
Module
s to modify resulting deserializer via registeredBeanDeserializerModifier
: type-dependant method likemodifyEnumDeserializer()
is called after constructing deserializer with a call tocreateEnumDeserializer()
and so on.
Of all actual factory methods -- all but one of which is defined in BasicDeserializerFactory
, the most interesting is createBeanDeserializer()
(defined in BeanDeserializerFactory
).
It uses all information contained in [Basic]BeanDescription
to figure out exact details of how to deserialize contents of the matching POJO type (class); this, most importantly, contains a POJOPropertiesCollector
to access everything relevant EXCEPT NOT Creator definitions!
With that, the logic of createBeanDeserializer()
is the same for all other createXxxDeserializer()
methods (as explained above), with some additions. So:
- Check if a module provides custom deserializer (method
_findCustomBeanDeserializer()
which calls Modules registered deserializers viaDeserializers.findBeanDeserializer()
): if so, applyBeanDeserializerModifier
(s) (if any), return - If no custom deserializer found, and target type is
Throwable
(or subtype), callbuildThrowableDeserializer()
to construct specialized variant ofBeanDeserializer
- Otherwise if target type is
abstract
, callmaterializeAbstractType()
which may provide deserializer (typically by one of registered modules such asMrBeanModule
) - If no deserializer found yet, see if one of "standard" deserializers -- ones handling recognized JDK/Jackson types (other than Enum/Array/Collection(Like)/Map(Like) types that are handled by different factory methods) -- and if so, construct one
- Verify that we have valid POJO type (
isPotentialBeanType()
); if not, returnnull
- Validate safety aspects wrt polymorphic handling (
_validateSubType()
) - If no implementation found yet and its valid case, call
buildBeanDeserializer()
for actual construction.
- Find
ValueInstantiator
withfindValueInstantiator()
-- from annotations if any, otherwise callBasicDeserializerFactory._constructDefaultValueInstantiator()
- !!! This is where Creator-handling is invoked !!!
- Create a
BeanDeserializerBuilder
with call toconstructBeanDeserializerBuilder()
(default impl simply creates one withBeanDescription
andDeserializationContext
) - Add properties to builder with
addBeanProps()
- Build actual
BeanDeserializer
by callingBeanDeserializerBuilder.build()
(or.buildAbstract()
)
This is finally where we start collection Creators (constructors and factory methods).
So, createDeserializer()
method calls DeserializationConfig.introspect(JavaType) method: this in turn calls
BasicClassIntrospector.forDeserialization()`:
- First, a special set of "simple" set of well-known types (primitive
int
,long
,Bolean
; matching wrappers;String
, JacksonJsonNode
) is checked with_findStdTypeDesc()
; if matching return pre-resolved description - If not found,
_findStdJdkCollectionDesc()
will handle minimal resolution for simple Collection types likeList
- In the usual case -- and in particular, general purpose POJOs, method
collectProperties()
is called.
BasicClassIntrospector.collectProperties()
:
- First gets
AnnotatedClass
via_resolveAnnotatedClass()
which simply callsAnnotatedClassResolver.resolve(config, type, r)
-- which in turn constructsAnnotatedClassResolver
and callsresolveFully
on it. This does annotation introspection and applies mix-in overlays, if any. - Then it gets appropriate
AccessorNamingStrategy
, depending on whether type is aRecord
or not (Records do not useget
orset
prefixes, typically - With these, a
POJOPropertiesCollector
is constructed via helper methodconstructPropertyCollector()
.
Upon construction, collector is initialized with AnnotatedClass
, for access to annotated Class, Methods, Fields and Constructors; as well as basic configuration (MapperConfig
, JavaType
, AccessorNamingStrategy
).
But actual resolution of logical properties is done lazily: most commonly when POJOPropertiesCollector.getProperties()
is called.