Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing #1467: Support for @JsonUnwrapped in @JsonCreators #4271

Merged
merged 16 commits into from
Nov 13, 2024
Merged
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
4 changes: 4 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,10 @@ Mike Minicki (@martel)
* Reported #4788: `EnumFeature.WRITE_ENUMS_TO_LOWERCASE` overrides `@JsonProperty` values
(2.18.2)

Liam Feid (@fxshlein)
* Contributed #1467: Support `@JsonUnwrapped` with `@JsonCreator`
(2.19.0)

@SandeepGaur2016
* Contributed fix for #2461: Nested `@JsonUnwrapped` property names not correctly handled
(2.19.0)
Expand Down
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Project: jackson-databind

2.19.0 (not yet released)

#1467: Support `@JsonUnwrapped` with `@JsonCreator`
(implementation by Liam F)
#2461: Nested `@JsonUnwrapped` property names not correctly handled
(reported by @plovell)
(fix contributed by @SandeepGaur2016)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ public abstract class BasicDeserializerFactory
private final static Class<?> CLASS_MAP_ENTRY = Map.Entry.class;
private final static Class<?> CLASS_SERIALIZABLE = Serializable.class;

/**
* We need a placeholder for creator properties that don't have name
* but are marked with `@JsonWrapped` annotation.
*/
protected final static PropertyName UNWRAPPED_CREATOR_PARAM_NAME = new PropertyName("@JsonUnwrapped");

/*
/**********************************************************
/* Config
Expand Down Expand Up @@ -402,11 +396,7 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt,
}
NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param);
if (unwrapper != null) {
_reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
/*
properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
++explicitNameCount;
*/
properties[i] = constructCreatorProperty(ctxt, beanDesc, UnwrappedPropertyHandler.creatorParamName(i), i, param, null);
}
}

Expand Down Expand Up @@ -531,7 +521,7 @@ private void _addSelectedPropertiesBasedCreator(DeserializationContext ctxt,
// as that will not work with Creators well at all
NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(param);
if (unwrapper != null) {
_reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
properties[i] = constructCreatorProperty(ctxt, beanDesc, UnwrappedPropertyHandler.creatorParamName(i), i, param, null);
}
// Must be injectable or have name; without either won't work
if ((name == null) && (injectId == null)) {
Expand Down Expand Up @@ -598,17 +588,6 @@ private boolean _handleSingleArgumentCreator(CreatorCollector creators,
return false;
}

// 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
// of unwrapped values through creator properties, so fail fast
private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt,
BeanDescription beanDesc, AnnotatedParameter param)
throws JsonMappingException
{
ctxt.reportBadTypeDefinition(beanDesc,
"Cannot define Creator parameter %d as `@JsonUnwrapped`: combination not yet supported",
param.getIndex());
}

/**
* Method that will construct a property object that represents
* a logical property passed via Creator (constructor or static
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,11 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri
}
}

// We could still have some not-yet-set creator properties that are unwrapped.
// These have to be processed last, because 'tokens' contains all properties
// that remain after regular deserialization.
buffer = _unwrappedPropertyHandler.processUnwrappedCreatorProperties(p, ctxt, buffer, tokens);

// We hit END_OBJECT, so:
Object bean;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,6 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
_arrayDelegateDeserializer = src._arrayDelegateDeserializer;
_propertyBasedCreator = src._propertyBasedCreator;

_backRefs = src._backRefs;
_ignorableProps = src._ignorableProps;
Expand All @@ -319,17 +318,22 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp

_nonStandardCreation = src._nonStandardCreation;
UnwrappedPropertyHandler uph = src._unwrappedPropertyHandler;
PropertyBasedCreator pbc = src._propertyBasedCreator;

if (unwrapper != null) {
// delegate further unwraps, if any
if (uph != null) { // got handler, delegate
uph = uph.renameAll(unwrapper);
}
// and handle direct unwrapping as well:
if (pbc != null) {
pbc = pbc.renameAll(unwrapper);
}
_beanProperties = src._beanProperties.renameAll(unwrapper);
} else {
_beanProperties = src._beanProperties;
}
_propertyBasedCreator = pbc;
_unwrappedPropertyHandler = uph;
_needViewProcesing = src._needViewProcesing;
_serializationShape = src._serializationShape;
Expand Down Expand Up @@ -578,7 +582,13 @@ public void resolve(DeserializationContext ctxt) throws JsonMappingException
if (unwrapped == null) {
unwrapped = new UnwrappedPropertyHandler();
}
unwrapped.addProperty(prop);

if (prop instanceof CreatorProperty) {
unwrapped.addCreatorProperty(prop);
} else {
unwrapped.addProperty(prop);
}

// 12-Dec-2014, tatu: As per [databind#647], we will have problems if
// the original property is left in place. So let's remove it now.
// 25-Mar-2017, tatu: Wonder if this could be problematic wrt creators?
Expand Down Expand Up @@ -1011,13 +1021,6 @@ protected NameTransformer _findPropertyUnwrapper(DeserializationContext ctxt,
if (am != null) {
NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(am);
if (unwrapper != null) {
// 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
// of unwrapped values through creator properties, so fail fast
if (prop instanceof CreatorProperty) {
ctxt.reportBadDefinition(getValueType(), String.format(
"Cannot define Creator property \"%s\" as `@JsonUnwrapped`: combination not yet supported",
prop.getName()));
}
return unwrapper;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.util.Annotations;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.databind.util.ViewMatcher;

/**
Expand Down Expand Up @@ -583,6 +584,27 @@ public final Object deserializeWith(JsonParser p, DeserializationContext ctxt,
return value;
}

/**
* Returns a copy of this property, unwrapped using given {@link NameTransformer}.
*
* @since 2.19
*/
cowtowncoder marked this conversation as resolved.
Show resolved Hide resolved
public SettableBeanProperty unwrapped(NameTransformer xf)
{
String newName = xf.transform(getName());
SettableBeanProperty renamed = withSimpleName(newName);
JsonDeserializer<?> deser = renamed.getValueDeserializer();
if (deser != null) {
@SuppressWarnings("unchecked")
JsonDeserializer<Object> newDeser = (JsonDeserializer<Object>)
deser.unwrappingDeserializer(xf);
if (newDeser != deser) {
renamed = renamed.withValueDeserializer(newDeser);
}
}
return renamed;
}

/*
/**********************************************************
/* Helper methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.PropertyName;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.util.ClassUtil;
Expand Down Expand Up @@ -361,7 +357,7 @@ public BeanPropertyMap renameAll(NameTransformer transformer)
newProps.add(prop);
continue;
}
newProps.add(_rename(prop, transformer));
newProps.add(prop.unwrapped(transformer));
}
// should we try to re-index? Ordering probably changed but caller probably doesn't want changes...
// 26-Feb-2017, tatu: Probably SHOULD handle renaming wrt Aliases?
Expand Down Expand Up @@ -712,23 +708,9 @@ public String toString()
/**********************************************************
*/

protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf)
cowtowncoder marked this conversation as resolved.
Show resolved Hide resolved
{
if (prop == null) {
return prop;
}
String newName = xf.transform(prop.getName());
prop = prop.withSimpleName(newName);
JsonDeserializer<?> deser = prop.getValueDeserializer();
if (deser != null) {
@SuppressWarnings("unchecked")
JsonDeserializer<Object> newDeser = (JsonDeserializer<Object>)
deser.unwrappingDeserializer(xf);
if (newDeser != deser) {
prop = prop.withValueDeserializer(newDeser);
}
}
return prop;
@Deprecated // in 2.19: remove from 2.20 or later
protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf) {
return prop.unwrapped(xf);
}

protected void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.deser.SettableAnyProperty;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.util.NameTransformer;

/**
* Object that is used to collect arguments for non-default creator
Expand Down Expand Up @@ -92,6 +93,19 @@ protected PropertyBasedCreator(DeserializationContext ctxt,
}
}

/**
* @since 2.19
*/
protected PropertyBasedCreator(PropertyBasedCreator base,
HashMap<String, SettableBeanProperty> propertyLookup,
SettableBeanProperty[] allProperties)
{
_propertyCount = base._propertyCount;
_valueInstantiator = base._valueInstantiator;
_propertyLookup = propertyLookup;
_allProperties = allProperties;
}

/**
* Factory method used for building actual instances to be used with POJOS:
* resolves deserializers, checks for "null values".
Expand Down Expand Up @@ -158,6 +172,46 @@ public static PropertyBasedCreator construct(DeserializationContext ctxt,
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
}

/**
* Mutant factory method for constructing a map where the names of all properties
* are transformed using the given {@link NameTransformer}.
*
* @since 2.19
*/
public PropertyBasedCreator renameAll(NameTransformer transformer)
{
if (transformer == null || (transformer == NameTransformer.NOP)) {
return this;
}

final int len = _allProperties.length;
HashMap<String, SettableBeanProperty> newLookup = new HashMap<>(_propertyLookup);
List<SettableBeanProperty> newProps = new ArrayList<>(len);

for (SettableBeanProperty prop : _allProperties) {
if (prop == null) {
newProps.add(null);
continue;
}

SettableBeanProperty renamedProperty = prop.unwrapped(transformer);
String oldName = prop.getName();
String newName = renamedProperty.getName();

newProps.add(renamedProperty);

if (!oldName.equals(newName) && newLookup.containsKey(oldName)) {
newLookup.remove(oldName);
newLookup.put(newName, renamedProperty);
}
}

return new PropertyBasedCreator(this,
newLookup,
newProps.toArray(new SettableBeanProperty[0])
);
}

/*
/**********************************************************
/* Accessors
Expand Down
Loading