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

Experimenting with #1467: Support for @JsonUnwrapped in @JsonCreators #4271

Open
wants to merge 5 commits into
base: 2.19
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@
import java.util.concurrent.atomic.AtomicReference;

import com.fasterxml.jackson.annotation.*;

import com.fasterxml.jackson.core.JsonParser;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.*;
import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate;
import com.fasterxml.jackson.databind.deser.impl.CreatorCollector;
import com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators;
import com.fasterxml.jackson.databind.deser.impl.JavaUtilCollectionsDeserializers;
import com.fasterxml.jackson.databind.deser.impl.*;
import com.fasterxml.jackson.databind.deser.std.*;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.ext.OptionalHandlerFactory;
Expand Down Expand Up @@ -49,12 +44,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 @@ -406,11 +395,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 All @@ -427,7 +412,7 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt,
*/
}
}

private void _addImplicitDelegatingFactories(DeserializationContext ctxt,
VisibilityChecker<?> vchecker,
CreatorCollector creators,
Expand Down Expand Up @@ -535,7 +520,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 @@ -602,17 +587,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 @@ -939,6 +939,10 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri
}
}

// We could still have some unset creator properties that are unwrapped. These have to be processed last, because 'tokens' contains
// all the 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 @@ -308,7 +308,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 @@ -320,15 +319,18 @@ 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:
_propertyBasedCreator = pbc != null ? pbc.renameAll(unwrapper) : null;
_beanProperties = src._beanProperties.renameAll(unwrapper);
} else {
_propertyBasedCreator = pbc;
_beanProperties = src._beanProperties;
}
_unwrappedPropertyHandler = uph;
Expand Down Expand Up @@ -579,7 +581,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 @@ -1005,20 +1013,11 @@ protected SettableBeanProperty _resolvedObjectIdProperty(DeserializationContext
* property: these require special handling.
*/
protected NameTransformer _findPropertyUnwrapper(DeserializationContext ctxt,
SettableBeanProperty prop)
throws JsonMappingException
{
SettableBeanProperty prop) {
AnnotatedMember am = prop.getMember();
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 @@ -13,6 +13,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 @@ -584,6 +585,27 @@ public final Object deserializeWith(JsonParser p, DeserializationContext ctxt,
return value;
}

/**
* Returns a copy of this property, unwrapped using the given {@link NameTransformer}.
*
* @since 1.19
*/
Copy link
Member

@pjfanning pjfanning Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add @since 2.18? likewise with any other new public methods.

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 @@ -362,7 +362,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 @@ -713,23 +713,12 @@ public String toString()
/**********************************************************
*/

protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf)
Copy link
Member

@pjfanning pjfanning Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this breaks backward compatibility - add back the old method and deprecate it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the method can call the new unwrap method, but we need to keep the existing method (its implementation can change - as long as it the change retains the existing behaviour)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, readded it!

{
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 since 1.19 in favor of {@link SettableBeanProperty#unwrapped(NameTransformer)}
*/
@Deprecated
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 @@ -9,6 +9,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 @@ -93,6 +94,21 @@ protected PropertyBasedCreator(DeserializationContext ctxt,
}
}

/**
* @since 2.19
*/
protected PropertyBasedCreator(
int propertyCount,
ValueInstantiator valueInstantiator,
HashMap<String, SettableBeanProperty> propertyLookup,
SettableBeanProperty[] allProperties
) {
_propertyCount = propertyCount;
_valueInstantiator = 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 @@ -159,6 +175,48 @@ 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 1.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);
ArrayList<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(
_propertyCount,
_valueInstantiator,
newLookup,
newProps.toArray(new SettableBeanProperty[newProps.size()])
);
}

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