Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ No changes since 3.1
#6246: Creator properties not renamed for `@JsonUnwrapped` with prefix/suffix
with `MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES`
(fix by @cowtowncoder, w/ Claude code)
#6248: `@JsonView` on `@JsonAnySetter` not honored when deserializing
(fix by @pjfanning, w/ Claude code)
#6249: Avoid NPE when parser reports `VALUE_STRING` with `null` text
(fix by @pjfanning, w/ Claude code)
#6252: `@JsonIgnoreProperties` not honored for `EnumMap` properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,10 +568,14 @@ 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
// (b) one of properties has view(s) to included in defined, OR
// (c) "any setter" has view(s) defined
if (!_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
return true;
}
if ((_anySetter != null) && _anySetter.hasViews()) {
return true;
}
for (SettableBeanProperty prop : props) {
if (prop.hasViews()) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -677,19 +677,32 @@ private SettableAnyProperty _resolveAnySetter(DeserializationContext ctxt,
for (SettableBeanProperty prop : creatorProps) {
AnnotatedMember member = prop.getMember();
if (member != null && Boolean.TRUE.equals(ctxt.getAnnotationIntrospector().hasAnySetter(ctxt.getConfig(), member))) {
return constructAnySetter(ctxt, beanDescRef, member);
return _withAnySetterViews(ctxt, member,
constructAnySetter(ctxt, beanDescRef, member));
}
}
}
// else find the regular method/field level any-setter
AnnotatedMember anySetter = beanDescRef.get().findAnySetterAccessor();
if (anySetter != null) {
return constructAnySetter(ctxt, beanDescRef, anySetter);
return _withAnySetterViews(ctxt, anySetter,
constructAnySetter(ctxt, beanDescRef, anySetter));
}
// not found, that's fine, too
return null;
}

// Only explicit `@JsonView` on the any-setter accessor is considered
// (not class-level default views) to retain behavior for un-annotated any-setters
private SettableAnyProperty _withAnySetterViews(DeserializationContext ctxt,
AnnotatedMember mutator, SettableAnyProperty anySetter)
{
if (anySetter != null) {
anySetter.setViews(ctxt.getAnnotationIntrospector().findViews(ctxt.getConfig(), mutator));
}
return anySetter;
}

private boolean _isSetterlessType(Class<?> rawType) {
// May also need to consider getters
// for Map/Collection properties; but with lowest precedence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.databind.util.ViewMatcher;

/**
* Class that represents a "wildcard" set method which can be used
Expand Down Expand Up @@ -44,6 +45,14 @@ public abstract class SettableAnyProperty
protected final TypeDeserializer _valueTypeDeserializer;
protected final KeyDeserializer _keyDeserializer;

/**
* Matcher for views (if any) this "any" property is visible in;
* {@code null} if no explicit views specified.
*
* @since 3.1.8
*/
protected ViewMatcher _viewMatcher;

/*
/**********************************************************************
/* Life-cycle
Expand Down Expand Up @@ -127,6 +136,24 @@ public static SettableAnyProperty constructForJsonNodeParameter(DeserializationC
// Abstract @since 2.14
public abstract SettableAnyProperty withValueDeserializer(ValueDeserializer<Object> deser);

/**
* @since 3.1.8
*/
public void setViews(Class<?>[] views) {
_viewMatcher = (views == null) ? null : ViewMatcher.construct(views);
}

/**
* Helper method for copying view information from original instance
* when constructing a new, modified instance.
*
* @since 3.1.8
*/
protected SettableAnyProperty _withViewsFrom(SettableAnyProperty src) {
_viewMatcher = src._viewMatcher;
return this;
}

public void fixAccess(DeserializationConfig config) {
_setter.fixAccess(
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
Expand All @@ -146,6 +173,18 @@ public void fixAccess(DeserializationConfig config) {

public String getPropertyName() { return _property.getName(); }

/**
* @since 3.1.8
*/
public boolean visibleInView(Class<?> activeView) {
return (_viewMatcher == null) || _viewMatcher.isVisibleForView(activeView);
}

/**
* @since 3.1.8
*/
public boolean hasViews() { return _viewMatcher != null; }

/**
* Accessor for parameterIndex.
* @return -1 if not a parameterized setter, otherwise index of parameter
Expand Down Expand Up @@ -329,7 +368,8 @@ protected void _set(DeserializationContext ctxt, Object instance, Object propNam
@Override
public SettableAnyProperty withValueDeserializer(ValueDeserializer<Object> deser) {
return new MethodAnyProperty(_property, _setter, _type,
_keyDeserializer, deser, _valueTypeDeserializer);
_keyDeserializer, deser, _valueTypeDeserializer)
._withViewsFrom(this);
}
}

Expand All @@ -354,7 +394,8 @@ public MapFieldAnyProperty(BeanProperty property,
public SettableAnyProperty withValueDeserializer(ValueDeserializer<Object> deser) {
return new MapFieldAnyProperty(_property, _setter, _type,
_keyDeserializer, deser, _valueTypeDeserializer,
_valueInstantiator);
_valueInstantiator)
._withViewsFrom(this);
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -473,7 +514,8 @@ public MapParameterAnyProperty(BeanProperty property, AnnotatedMember field, Jav
public SettableAnyProperty withValueDeserializer(ValueDeserializer<Object> deser)
{
return new MapParameterAnyProperty(_property, _setter, _type, _keyDeserializer, deser,
_valueTypeDeserializer, _valueInstantiator, _parameterIndex);
_valueTypeDeserializer, _valueInstantiator, _parameterIndex)
._withViewsFrom(this);
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@ protected Object _deserializeRecordForUpdate(JsonParser p,
}
// "Any property"?
if (_anySetter != null) {
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
try {
// 09-Feb-2026, tatu: as with Mutators, should never have non-Creator
// "any"-properties, so commento out
Expand Down Expand Up @@ -786,6 +789,9 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
}
// "any property"?
if (_anySetter != null) {
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
try {
// [databind#4639] Since 2.18.1 AnySetter might not part of the creator, but just some field.
if (_anySetter.isFieldType() ||
Expand Down Expand Up @@ -983,14 +989,7 @@ protected final Object deserializeWithView(JsonParser p, DeserializationContext
p.nextToken();
SettableBeanProperty prop = _propsByIndex[ix];
if (!prop.visibleInView(activeView)) {
// [databind#437]: fields in other views to be considered as unknown properties
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES)){
ctxt.reportInputMismatch(handledType(),
String.format("Input mismatch while deserializing %s. Property '%s' is not part of current active view '%s'" +
" (disable 'DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES' to allow)",
ClassUtil.nameOf(handledType()), prop.getName(), activeView.getName()));
}
p.skipChildren();
handlePropertyNotInView(p, ctxt, prop.getName(), activeView);
continue;
}
try {
Expand Down Expand Up @@ -1088,6 +1087,9 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c
handleUnknownVanilla(p, ctxt, bean, propName);
continue;
}
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
// Need to copy to a separate buffer first
TokenBuffer b2 = ctxt.bufferAsCopyOfValue(p);
tokens.writeName(propName);
Expand Down Expand Up @@ -1154,6 +1156,8 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c
tokens.copyCurrentStructure(p);
} else if (_anySetter == null) {
handleUnknownVanilla(p, ctxt, bean, propName);
} else if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
; // any-setter not visible in active view: value skipped
} else {
// Need to copy to a separate buffer first
TokenBuffer b2 = ctxt.bufferAsCopyOfValue(p);
Expand Down Expand Up @@ -1263,6 +1267,8 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri
} else {
p.skipChildren();
}
} else if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
; // any-setter not visible in active view: value skipped
} else {
// Need to copy to a separate buffer first
TokenBuffer b2 = ctxt.bufferAsCopyOfValue(p);
Expand Down Expand Up @@ -1383,6 +1389,9 @@ protected Object _deserializeWithExternalTypeId(JsonParser p, DeserializationCon
}
// if not, the usual fallback handling:
if (_anySetter != null) {
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
try {
_anySetter.deserializeAndSet(p, ctxt, bean, propName);
} catch (Exception e) {
Expand Down Expand Up @@ -1467,6 +1476,9 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D
}
// "any property"?
if (_anySetter != null) {
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
buffer.bufferAnyProperty(_anySetter, propName,
_anySetter.deserialize(p, ctxt));
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1783,6 +1783,9 @@ protected void handleUnknownVanilla(JsonParser p, DeserializationContext ctxt,
if (IgnorePropertiesUtil.shouldIgnore(propName, _ignorableProps, _includableProps)) {
handleIgnoredProperty(p, ctxt, beanOrBuilder, propName);
} else if (_anySetter != null) {
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
return;
}
try {
// should we consider return type of any setter?
_anySetter.deserializeAndSet(p, ctxt, beanOrBuilder, propName);
Expand All @@ -1795,6 +1798,53 @@ protected void handleUnknownVanilla(JsonParser p, DeserializationContext ctxt,
}
}

/**
* Helper method called before passing a property value to "any setter":
* if the any-setter is not visible in the active view (if any), value is
* skipped (or, if {@link DeserializationFeature#FAIL_ON_UNEXPECTED_VIEW_PROPERTIES}
* is enabled, an exception is thrown), same as with regular properties.
*
* @return {@code true} if value was skipped; {@code false} if it is to be
* passed to any-setter
*
* @since 3.1.8
*/
protected boolean _skipIfAnySetterNotInView(JsonParser p, DeserializationContext ctxt,
String propName)
throws JacksonException
{
if (_needViewProcesing) {
final Class<?> activeView = ctxt.getActiveView();
if ((activeView != null) && !_anySetter.visibleInView(activeView)) {
handlePropertyNotInView(p, ctxt, propName, activeView);
return true;
}
}
return false;
}

/**
* Method called when a property value is encountered for a property
* (or "any setter") not visible in the active view: value is skipped,
* unless {@link DeserializationFeature#FAIL_ON_UNEXPECTED_VIEW_PROPERTIES}
* is enabled, in which case an exception is thrown.
*
* @since 3.1.8
*/
protected void handlePropertyNotInView(JsonParser p, DeserializationContext ctxt,
String propName, Class<?> activeView)
throws JacksonException
{
// [databind#437]: fields in other views to be considered as unknown properties
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES)) {
ctxt.reportInputMismatch(handledType(),
String.format("Input mismatch while deserializing %s. Property '%s' is not part of current active view '%s'" +
" (disable 'DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES' to allow)",
ClassUtil.nameOf(handledType()), propName, activeView.getName()));
}
p.skipChildren();
}

/**
* Method called when a JSON property is encountered that has not matching
* setter, any-setter or field, and thus cannot be assigned.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p,
}
// "any" property?
if (_anySetter != null) {
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt));
continue;
}
Expand Down Expand Up @@ -591,14 +594,7 @@ protected final Object deserializeWithView(JsonParser p, DeserializationContext
p.nextToken();
SettableBeanProperty prop = _propertiesByIndex[ix];
if (!prop.visibleInView(activeView)) {
// [databind#437]: fields in other views to be considered as unknown properties
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES)){
ctxt.reportInputMismatch(handledType(),
String.format("Input mismatch while deserializing %s. Property '%s' is not part of current active view '%s'" +
" (disable 'DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES' to allow)",
ClassUtil.nameOf(handledType()), prop.getName(), activeView.getName()));
}
p.skipChildren();
handlePropertyNotInView(p, ctxt, prop.getName(), activeView);
continue;
}
try {
Expand Down Expand Up @@ -691,6 +687,9 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c
handleUnknownVanilla(p, ctxt, bean, propName);
continue;
}
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
try {
_anySetter.deserializeAndSet(p, ctxt, bean, propName);
} catch (Exception e) {
Expand Down Expand Up @@ -747,6 +746,9 @@ protected Object deserializeWithUnwrapped(JsonParser p,
handleUnknownVanilla(p, ctxt, builder, propName);
continue;
}
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
_anySetter.deserializeAndSet(p, ctxt, builder, propName);
}
tokens.writeEndObject();
Expand Down Expand Up @@ -833,6 +835,9 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p,
handleUnknownVanilla(p, ctxt, null, propName);
continue;
}
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt));
}
tokens.writeEndObject();
Expand Down Expand Up @@ -909,6 +914,9 @@ protected Object deserializeWithExternalTypeId(JsonParser p,
}
// if not, the usual fallback handling:
if (_anySetter != null) {
if (_skipIfAnySetterNotInView(p, ctxt, propName)) {
continue;
}
try {
_anySetter.deserializeAndSet(p, ctxt, bean, propName);
} catch (Exception e) {
Expand Down
Loading
Loading