Skip to content

Commit

Permalink
Implement FasterXML#1467: Support for @JsonCreator and @JsonUnwrapped
Browse files Browse the repository at this point in the history
Co-authored-by: Tatu Saloranta <tatu.saloranta@iki.fi>
  • Loading branch information
fxshlein and cowtowncoder committed Sep 1, 2024
1 parent c995671 commit 22cb7ee
Show file tree
Hide file tree
Showing 12 changed files with 419 additions and 201 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
package com.fasterxml.jackson.databind.deser;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.*;
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 All @@ -27,6 +15,15 @@
import com.fasterxml.jackson.databind.type.*;
import com.fasterxml.jackson.databind.util.*;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

/**
* Abstract factory base class that can provide deserializers for standard
* JDK classes, including collection classes and simple heuristics for
Expand All @@ -49,12 +46,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 +397,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 +414,7 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt,
*/
}
}

private void _addImplicitDelegatingFactories(DeserializationContext ctxt,
VisibilityChecker<?> vchecker,
CreatorCollector creators,
Expand Down Expand Up @@ -528,7 +515,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 @@ -595,17 +582,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 @@ -934,6 +934,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,25 @@ public final Object deserializeWith(JsonParser p, DeserializationContext ctxt,
return value;
}

/**
* Returns a copy of this property, unwrapped using the given {@link NameTransformer}.
*/
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,25 +713,6 @@ public String toString()
/**********************************************************
*/

protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf)
{
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;
}

protected void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
throws IOException
{
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.18
*/
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,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}.
*/
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

0 comments on commit 22cb7ee

Please sign in to comment.