Skip to content

Commit 502fe88

Browse files
authored
Implemented #1467: Support for @JsonUnwrapped in @JsonCreators (#4271)
1 parent a777bf3 commit 502fe88

File tree

14 files changed

+442
-188
lines changed

14 files changed

+442
-188
lines changed

release-notes/CREDITS-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,10 @@ Mike Minicki (@martel)
18531853
* Reported #4788: `EnumFeature.WRITE_ENUMS_TO_LOWERCASE` overrides `@JsonProperty` values
18541854
(2.18.2)
18551855

1856+
Liam Feid (@fxshlein)
1857+
* Contributed #1467: Support `@JsonUnwrapped` with `@JsonCreator`
1858+
(2.19.0)
1859+
18561860
@SandeepGaur2016
18571861
* Contributed fix for #2461: Nested `@JsonUnwrapped` property names not correctly handled
18581862
(2.19.0)

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Project: jackson-databind
66

77
2.19.0 (not yet released)
88

9+
#1467: Support `@JsonUnwrapped` with `@JsonCreator`
10+
(implementation by Liam F)
911
#2461: Nested `@JsonUnwrapped` property names not correctly handled
1012
(reported by @plovell)
1113
(fix contributed by @SandeepGaur2016)

src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ public abstract class BasicDeserializerFactory
4545
private final static Class<?> CLASS_MAP_ENTRY = Map.Entry.class;
4646
private final static Class<?> CLASS_SERIALIZABLE = Serializable.class;
4747

48-
/**
49-
* We need a placeholder for creator properties that don't have name
50-
* but are marked with `@JsonWrapped` annotation.
51-
*/
52-
protected final static PropertyName UNWRAPPED_CREATOR_PARAM_NAME = new PropertyName("@JsonUnwrapped");
53-
5448
/*
5549
/**********************************************************
5650
/* Config
@@ -402,11 +396,7 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt,
402396
}
403397
NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param);
404398
if (unwrapper != null) {
405-
_reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
406-
/*
407-
properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
408-
++explicitNameCount;
409-
*/
399+
properties[i] = constructCreatorProperty(ctxt, beanDesc, UnwrappedPropertyHandler.creatorParamName(i), i, param, null);
410400
}
411401
}
412402

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

601-
// 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
602-
// of unwrapped values through creator properties, so fail fast
603-
private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt,
604-
BeanDescription beanDesc, AnnotatedParameter param)
605-
throws JsonMappingException
606-
{
607-
ctxt.reportBadTypeDefinition(beanDesc,
608-
"Cannot define Creator parameter %d as `@JsonUnwrapped`: combination not yet supported",
609-
param.getIndex());
610-
}
611-
612591
/**
613592
* Method that will construct a property object that represents
614593
* a logical property passed via Creator (constructor or static

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,11 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri
941941
}
942942
}
943943

944+
// We could still have some not-yet-set creator properties that are unwrapped.
945+
// These have to be processed last, because 'tokens' contains all properties
946+
// that remain after regular deserialization.
947+
buffer = _unwrappedPropertyHandler.processUnwrappedCreatorProperties(p, ctxt, buffer, tokens);
948+
944949
// We hit END_OBJECT, so:
945950
Object bean;
946951
try {

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,6 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp
307307
_valueInstantiator = src._valueInstantiator;
308308
_delegateDeserializer = src._delegateDeserializer;
309309
_arrayDelegateDeserializer = src._arrayDelegateDeserializer;
310-
_propertyBasedCreator = src._propertyBasedCreator;
311310

312311
_backRefs = src._backRefs;
313312
_ignorableProps = src._ignorableProps;
@@ -319,17 +318,22 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp
319318

320319
_nonStandardCreation = src._nonStandardCreation;
321320
UnwrappedPropertyHandler uph = src._unwrappedPropertyHandler;
321+
PropertyBasedCreator pbc = src._propertyBasedCreator;
322322

323323
if (unwrapper != null) {
324324
// delegate further unwraps, if any
325325
if (uph != null) { // got handler, delegate
326326
uph = uph.renameAll(unwrapper);
327327
}
328328
// and handle direct unwrapping as well:
329+
if (pbc != null) {
330+
pbc = pbc.renameAll(unwrapper);
331+
}
329332
_beanProperties = src._beanProperties.renameAll(unwrapper);
330333
} else {
331334
_beanProperties = src._beanProperties;
332335
}
336+
_propertyBasedCreator = pbc;
333337
_unwrappedPropertyHandler = uph;
334338
_needViewProcesing = src._needViewProcesing;
335339
_serializationShape = src._serializationShape;
@@ -578,7 +582,13 @@ public void resolve(DeserializationContext ctxt) throws JsonMappingException
578582
if (unwrapped == null) {
579583
unwrapped = new UnwrappedPropertyHandler();
580584
}
581-
unwrapped.addProperty(prop);
585+
586+
if (prop instanceof CreatorProperty) {
587+
unwrapped.addCreatorProperty(prop);
588+
} else {
589+
unwrapped.addProperty(prop);
590+
}
591+
582592
// 12-Dec-2014, tatu: As per [databind#647], we will have problems if
583593
// the original property is left in place. So let's remove it now.
584594
// 25-Mar-2017, tatu: Wonder if this could be problematic wrt creators?
@@ -1011,13 +1021,6 @@ protected NameTransformer _findPropertyUnwrapper(DeserializationContext ctxt,
10111021
if (am != null) {
10121022
NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(am);
10131023
if (unwrapper != null) {
1014-
// 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
1015-
// of unwrapped values through creator properties, so fail fast
1016-
if (prop instanceof CreatorProperty) {
1017-
ctxt.reportBadDefinition(getValueType(), String.format(
1018-
"Cannot define Creator property \"%s\" as `@JsonUnwrapped`: combination not yet supported",
1019-
prop.getName()));
1020-
}
10211024
return unwrapper;
10221025
}
10231026
}

src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
1313
import com.fasterxml.jackson.databind.util.Annotations;
1414
import com.fasterxml.jackson.databind.util.ClassUtil;
15+
import com.fasterxml.jackson.databind.util.NameTransformer;
1516
import com.fasterxml.jackson.databind.util.ViewMatcher;
1617

1718
/**
@@ -583,6 +584,27 @@ public final Object deserializeWith(JsonParser p, DeserializationContext ctxt,
583584
return value;
584585
}
585586

587+
/**
588+
* Returns a copy of this property, unwrapped using given {@link NameTransformer}.
589+
*
590+
* @since 2.19
591+
*/
592+
public SettableBeanProperty unwrapped(NameTransformer xf)
593+
{
594+
String newName = xf.transform(getName());
595+
SettableBeanProperty renamed = withSimpleName(newName);
596+
JsonDeserializer<?> deser = renamed.getValueDeserializer();
597+
if (deser != null) {
598+
@SuppressWarnings("unchecked")
599+
JsonDeserializer<Object> newDeser = (JsonDeserializer<Object>)
600+
deser.unwrappingDeserializer(xf);
601+
if (newDeser != deser) {
602+
renamed = renamed.withValueDeserializer(newDeser);
603+
}
604+
}
605+
return renamed;
606+
}
607+
586608
/*
587609
/**********************************************************
588610
/* Helper methods

src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@
66

77
import com.fasterxml.jackson.core.JacksonException;
88
import com.fasterxml.jackson.core.JsonParser;
9-
import com.fasterxml.jackson.databind.DeserializationContext;
10-
import com.fasterxml.jackson.databind.DeserializationFeature;
11-
import com.fasterxml.jackson.databind.JsonDeserializer;
12-
import com.fasterxml.jackson.databind.JsonMappingException;
13-
import com.fasterxml.jackson.databind.MapperFeature;
14-
import com.fasterxml.jackson.databind.PropertyName;
9+
10+
import com.fasterxml.jackson.databind.*;
1511
import com.fasterxml.jackson.databind.cfg.MapperConfig;
1612
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
1713
import com.fasterxml.jackson.databind.util.ClassUtil;
@@ -361,7 +357,7 @@ public BeanPropertyMap renameAll(NameTransformer transformer)
361357
newProps.add(prop);
362358
continue;
363359
}
364-
newProps.add(_rename(prop, transformer));
360+
newProps.add(prop.unwrapped(transformer));
365361
}
366362
// should we try to re-index? Ordering probably changed but caller probably doesn't want changes...
367363
// 26-Feb-2017, tatu: Probably SHOULD handle renaming wrt Aliases?
@@ -712,23 +708,9 @@ public String toString()
712708
/**********************************************************
713709
*/
714710

715-
protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf)
716-
{
717-
if (prop == null) {
718-
return prop;
719-
}
720-
String newName = xf.transform(prop.getName());
721-
prop = prop.withSimpleName(newName);
722-
JsonDeserializer<?> deser = prop.getValueDeserializer();
723-
if (deser != null) {
724-
@SuppressWarnings("unchecked")
725-
JsonDeserializer<Object> newDeser = (JsonDeserializer<Object>)
726-
deser.unwrappingDeserializer(xf);
727-
if (newDeser != deser) {
728-
prop = prop.withValueDeserializer(newDeser);
729-
}
730-
}
731-
return prop;
711+
@Deprecated // in 2.19: remove from 2.20 or later
712+
protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf) {
713+
return prop.unwrapped(xf);
732714
}
733715

734716
protected void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)

src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.fasterxml.jackson.databind.deser.SettableAnyProperty;
99
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
1010
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
11+
import com.fasterxml.jackson.databind.util.NameTransformer;
1112

1213
/**
1314
* Object that is used to collect arguments for non-default creator
@@ -92,6 +93,19 @@ protected PropertyBasedCreator(DeserializationContext ctxt,
9293
}
9394
}
9495

96+
/**
97+
* @since 2.19
98+
*/
99+
protected PropertyBasedCreator(PropertyBasedCreator base,
100+
HashMap<String, SettableBeanProperty> propertyLookup,
101+
SettableBeanProperty[] allProperties)
102+
{
103+
_propertyCount = base._propertyCount;
104+
_valueInstantiator = base._valueInstantiator;
105+
_propertyLookup = propertyLookup;
106+
_allProperties = allProperties;
107+
}
108+
95109
/**
96110
* Factory method used for building actual instances to be used with POJOS:
97111
* resolves deserializers, checks for "null values".
@@ -158,6 +172,46 @@ public static PropertyBasedCreator construct(DeserializationContext ctxt,
158172
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
159173
}
160174

175+
/**
176+
* Mutant factory method for constructing a map where the names of all properties
177+
* are transformed using the given {@link NameTransformer}.
178+
*
179+
* @since 2.19
180+
*/
181+
public PropertyBasedCreator renameAll(NameTransformer transformer)
182+
{
183+
if (transformer == null || (transformer == NameTransformer.NOP)) {
184+
return this;
185+
}
186+
187+
final int len = _allProperties.length;
188+
HashMap<String, SettableBeanProperty> newLookup = new HashMap<>(_propertyLookup);
189+
List<SettableBeanProperty> newProps = new ArrayList<>(len);
190+
191+
for (SettableBeanProperty prop : _allProperties) {
192+
if (prop == null) {
193+
newProps.add(null);
194+
continue;
195+
}
196+
197+
SettableBeanProperty renamedProperty = prop.unwrapped(transformer);
198+
String oldName = prop.getName();
199+
String newName = renamedProperty.getName();
200+
201+
newProps.add(renamedProperty);
202+
203+
if (!oldName.equals(newName) && newLookup.containsKey(oldName)) {
204+
newLookup.remove(oldName);
205+
newLookup.put(newName, renamedProperty);
206+
}
207+
}
208+
209+
return new PropertyBasedCreator(this,
210+
newLookup,
211+
newProps.toArray(new SettableBeanProperty[0])
212+
);
213+
}
214+
161215
/*
162216
/**********************************************************
163217
/* Accessors

0 commit comments

Comments
 (0)