Repository navigation
Vanilla BeanDeserializer fast path never used with default settings in 3.x (due to MapperFeature.DEFAULT_VIEW_INCLUSION default change) #6219
Description
(filed via Claude Code)
Describe the bug
In 3.x, the optimized "vanilla" processing of BeanDeserializer (_vanillaDeserialize()) is never used with default mapper settings -- even for plain POJOs with no @JsonView annotations, no injectables, no Object Id and default constructor.
Cause is BeanDeserializerBuilder._anyViews():
protected boolean _anyViews(Collection<SettableBeanProperty> props)
{
// view processing must be enabled if:
// (a) fields are not included by default (when deserializing with view), OR
// (b) one of properties has view(s) to included in defined
if (!_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
return true;
}
...With DEFAULT_VIEW_INCLUSION disabled, every bean deserializer gets _needViewProcesing = true, and since BeanDeserializerBase computes
_vanillaProcessing = !_nonStandardCreation
&& (_injectables == null)
&& !_needViewProcesing
&& (_objectIdReader == null);_vanillaProcessing is always false. This logic predates 3.0, when the feature was enabled by default; but #1484 (via #4885) changed the default of DEFAULT_VIEW_INCLUSION to false for 3.0, so now all POJOs take the non-vanilla deserializeFromObject() path regardless of whether views are used at all.
Consequences:
- Default configuration never uses the vanilla fast path, including improvements to it like Use
JsonParser.nextNameMatchAndToken()in the vanillaBeanDeserializerloops #6193 (measured about +10% throughput forbyte[]input -- but only withDEFAULT_VIEW_INCLUSIONexplicitly enabled). - Afterburner (
SuperSonicBeanDeserializer,SuperSonicUnrolledDeserializer) and Blackbird (SuperSonicBeanDeserializer) check_vanillaProcessingand fall back tosuper.deserialize()when it isfalse, so their optimized deserialization appears to be bypassed with default settings as well.
Version Information
3.0.0 and later
Reproduction
For a plain POJO like
static class Point {
public int x, y;
}inspecting the root BeanDeserializer (e.g. via DeserializationContext.findRootValueDeserializer()):
- default
JsonMapper:_needViewProcesing = true,_vanillaProcessing = false JsonMapper.builder().enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build():_needViewProcesing = false,_vanillaProcessing = true
(BeanDeserializerVanillaTest added by #6193 has to enable DEFAULT_VIEW_INCLUSION explicitly to get vanilla processing.)
Expected behavior
Vanilla processing should be usable whenever no view is active during deserialization (and no other non-vanilla conditions apply). Views only matter if a view is active, and that is already checked at runtime in non-vanilla paths, e.g. BeanDeserializer:
if (_needViewProcesing) {
Class<?> view = ctxt.getActiveView();
if (view != null) {
return deserializeWithView(p, ctxt, bean, view);
}
}Possible approach: stop folding _needViewProcesing into _vanillaProcessing at construction time, and instead check !_needViewProcesing || ctxt.getActiveView() == null at the points where vanilla processing is chosen:
BeanDeserializerBase(flag computation and copy constructors)BeanDeserializer.deserialize()/_deserializeOther()BuilderBasedDeserializerBeanAsArrayDeserializer/BeanAsArrayBuilderDeserializer- Afterburner / Blackbird
SuperSonic*deserializers (jackson-modules-base)
(Note that simply changing _anyViews() would not be correct: with DEFAULT_VIEW_INCLUSION disabled, an active view excludes un-annotated properties, so view processing is needed whenever a view is active.)
Additional context
Found while reviewing #6193. Performance difference between vanilla and non-vanilla paths themselves has not yet been measured.
Activity