Skip to content

Commit 5b0bc35

Browse files
authored
Change Records deserialization to use the same mechanism as POJO deserialization. (#3724)
1 parent 87b50e9 commit 5b0bc35

14 files changed

+946
-65
lines changed

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,16 +280,6 @@ protected ValueInstantiator _constructDefaultValueInstantiator(DeserializationCo
280280

281281
// constructors only usable on concrete types:
282282
if (beanDesc.getType().isConcrete()) {
283-
// [databind#2709]: Record support
284-
if (beanDesc.getType().isRecordType()) {
285-
final List<String> names = new ArrayList<>();
286-
// NOTE: this does verify that there is no explicitly annotated alternatives
287-
AnnotatedConstructor canonical = JDK14Util.findRecordConstructor(ctxt, beanDesc, names);
288-
if (canonical != null) {
289-
_addRecordConstructor(ctxt, ccState, canonical, names);
290-
return ccState.creators.constructValueInstantiator(ctxt);
291-
}
292-
}
293283
// 25-Jan-2017, tatu: As per [databind#1501], [databind#1502], [databind#1503], best
294284
// for now to skip attempts at using anything but no-args constructor (see
295285
// `InnerClassProperty` construction for that)
@@ -400,7 +390,10 @@ public ValueInstantiator _valueInstantiatorInstance(DeserializationConfig config
400390
* constructor is to be used: if so, we have implicit names to consider.
401391
*
402392
* @since 2.12
393+
* @deprecated since 2.15 - no longer used, but kept because this protected method might have been overridden/used
394+
* elsewhere
403395
*/
396+
@Deprecated
404397
protected void _addRecordConstructor(DeserializationContext ctxt, CreatorCollectionState ccState,
405398
AnnotatedConstructor canonical, List<String> implicitNames)
406399
throws JsonMappingException
@@ -547,7 +540,9 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt,
547540
JacksonInject.Value injectable = intr.findInjectableValue(param);
548541
final PropertyName name = (propDef == null) ? null : propDef.getFullName();
549542

550-
if (propDef != null && propDef.isExplicitlyNamed()) {
543+
if (propDef != null
544+
// NOTE: Record canonical constructor will have implicitly named propDef
545+
&& (propDef.isExplicitlyNamed() || beanDesc.getType().isRecordType())) {
551546
++explicitNameCount;
552547
properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable);
553548
continue;

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,12 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
466466
}
467467
// regular property? needs buffering
468468
SettableBeanProperty prop = _beanProperties.find(propName);
469-
if (prop != null) {
469+
// Special handling because Records' ignored creator props weren't removed (to help in creating
470+
// constructor-backed PropertyCreator) so they ended up in _beanProperties, unlike POJO (whose ignored
471+
// props are removed)
472+
boolean isClassWithoutMutator = _beanType.isRecordType();
473+
474+
if (prop != null && !isClassWithoutMutator) {
470475
try {
471476
buffer.bufferProperty(prop, _deserializeWithErrorWrapping(p, ctxt, prop));
472477
} catch (UnresolvedForwardReference reference) {

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.fasterxml.jackson.databind.*;
1010
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
1111
import com.fasterxml.jackson.databind.cfg.MapperConfig;
12+
import com.fasterxml.jackson.databind.jdk14.JDK14Util;
1213
import com.fasterxml.jackson.databind.util.ClassUtil;
1314

1415
/**
@@ -610,23 +611,53 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
610611
protected void _addCreators(Map<String, POJOPropertyBuilder> props)
611612
{
612613
// can be null if annotation processing is disabled...
613-
if (!_useAnnotations) {
614-
return;
615-
}
616-
for (AnnotatedConstructor ctor : _classDef.getConstructors()) {
617-
if (_creatorProperties == null) {
618-
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
614+
if (_useAnnotations) {
615+
for (AnnotatedConstructor ctor : _classDef.getConstructors()) {
616+
if (_creatorProperties == null) {
617+
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
618+
}
619+
for (int i = 0, len = ctor.getParameterCount(); i < len; ++i) {
620+
_addCreatorParam(props, ctor.getParameter(i));
621+
}
619622
}
620-
for (int i = 0, len = ctor.getParameterCount(); i < len; ++i) {
621-
_addCreatorParam(props, ctor.getParameter(i));
623+
for (AnnotatedMethod factory : _classDef.getFactoryMethods()) {
624+
if (_creatorProperties == null) {
625+
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
626+
}
627+
for (int i = 0, len = factory.getParameterCount(); i < len; ++i) {
628+
_addCreatorParam(props, factory.getParameter(i));
629+
}
622630
}
623631
}
624-
for (AnnotatedMethod factory : _classDef.getFactoryMethods()) {
625-
if (_creatorProperties == null) {
626-
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
627-
}
628-
for (int i = 0, len = factory.getParameterCount(); i < len; ++i) {
629-
_addCreatorParam(props, factory.getParameter(i));
632+
if (_classDef.getType().isRecordType()) {
633+
List<String> recordComponentNames = new ArrayList<String>();
634+
AnnotatedConstructor canonicalCtor = JDK14Util.findRecordConstructor(
635+
_classDef, _annotationIntrospector, _config, recordComponentNames);
636+
637+
if (canonicalCtor != null) {
638+
if (_creatorProperties == null) {
639+
_creatorProperties = new LinkedList<POJOPropertyBuilder>();
640+
}
641+
642+
Set<AnnotatedParameter> registeredParams = new HashSet<AnnotatedParameter>();
643+
for (POJOPropertyBuilder creatorProperty : _creatorProperties) {
644+
Iterator<AnnotatedParameter> iter = creatorProperty.getConstructorParameters();
645+
while (iter.hasNext()) {
646+
AnnotatedParameter param = iter.next();
647+
if (param.getOwner().equals(canonicalCtor)) {
648+
registeredParams.add(param);
649+
}
650+
}
651+
}
652+
653+
if (_creatorProperties.isEmpty() || !registeredParams.isEmpty()) {
654+
for (int i = 0; i < canonicalCtor.getParameterCount(); i++) {
655+
AnnotatedParameter param = canonicalCtor.getParameter(i);
656+
if (!registeredParams.contains(param)) {
657+
_addCreatorParam(props, param, recordComponentNames.get(i));
658+
}
659+
}
660+
}
630661
}
631662
}
632663
}
@@ -637,11 +668,23 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
637668
protected void _addCreatorParam(Map<String, POJOPropertyBuilder> props,
638669
AnnotatedParameter param)
639670
{
640-
// JDK 8, paranamer, Scala can give implicit name
641-
String impl = _annotationIntrospector.findImplicitPropertyName(param);
642-
if (impl == null) {
643-
impl = "";
671+
_addCreatorParam(props, param, null);
672+
}
673+
674+
private void _addCreatorParam(Map<String, POJOPropertyBuilder> props,
675+
AnnotatedParameter param, String recordComponentName)
676+
{
677+
String impl;
678+
if (recordComponentName != null) {
679+
impl = recordComponentName;
680+
} else {
681+
// JDK 8, paranamer, Scala can give implicit name
682+
impl = _annotationIntrospector.findImplicitPropertyName(param);
683+
if (impl == null) {
684+
impl = "";
685+
}
644686
}
687+
645688
PropertyName pn = _annotationIntrospector.findNameForDeserialization(param);
646689
boolean expl = (pn != null && !pn.isEmpty());
647690
if (!expl) {
@@ -650,10 +693,13 @@ protected void _addCreatorParam(Map<String, POJOPropertyBuilder> props,
650693
// this creator parameter -- may or may not be a problem, verified at a later point.
651694
return;
652695
}
653-
// Also: if this occurs, there MUST be explicit annotation on creator itself
654-
JsonCreator.Mode creatorMode = _annotationIntrospector.findCreatorAnnotation(_config,
655-
param.getOwner());
656-
if ((creatorMode == null) || (creatorMode == JsonCreator.Mode.DISABLED)) {
696+
697+
// Also: if this occurs, there MUST be explicit annotation on creator itself...
698+
JsonCreator.Mode creatorMode = _annotationIntrospector.findCreatorAnnotation(_config, param.getOwner());
699+
// ...or is a Records canonical constructor
700+
boolean isCanonicalConstructor = recordComponentName != null;
701+
702+
if ((creatorMode == null || creatorMode == JsonCreator.Mode.DISABLED) && !isCanonicalConstructor) {
657703
return;
658704
}
659705
pn = PropertyName.construct(impl);
@@ -902,6 +948,17 @@ protected void _removeUnwantedProperties(Map<String, POJOPropertyBuilder> props)
902948
}
903949
// Otherwise, check ignorals
904950
if (prop.anyIgnorals()) {
951+
// Special handling for Records, as they do not have mutators so relying on constructors with (mostly)
952+
// implicitly-named parameters...
953+
if (_classDef.getType().isRecordType()) {
954+
// ...so can only remove ignored field and/or accessors, not constructor parameters that are needed
955+
// for instantiation...
956+
prop.removeIgnored();
957+
// ...which will then be ignored (the incoming property value) during deserialization
958+
_collectIgnorals(prop.getName());
959+
continue;
960+
}
961+
905962
// first: if one or more ignorals, and no explicit markers, remove the whole thing
906963
// 16-May-2022, tatu: NOTE! As per [databind#3357] need to consider
907964
// only explicit inclusion by accessors OTHER than ones with ignoral marker

src/main/java/com/fasterxml/jackson/databind/jdk14/JDK14Util.java

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
99
import com.fasterxml.jackson.databind.AnnotationIntrospector;
1010
import com.fasterxml.jackson.databind.BeanDescription;
11-
import com.fasterxml.jackson.databind.DeserializationConfig;
1211
import com.fasterxml.jackson.databind.DeserializationContext;
12+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
13+
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
1314
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
1415
import com.fasterxml.jackson.databind.util.ClassUtil;
1516
import com.fasterxml.jackson.databind.util.NativeImageUtil;
@@ -30,7 +31,12 @@ public static String[] getRecordFieldNames(Class<?> recordType) {
3031

3132
public static AnnotatedConstructor findRecordConstructor(DeserializationContext ctxt,
3233
BeanDescription beanDesc, List<String> names) {
33-
return new CreatorLocator(ctxt, beanDesc)
34+
return findRecordConstructor(beanDesc.getClassInfo(), ctxt.getAnnotationIntrospector(), ctxt.getConfig(), names);
35+
}
36+
37+
public static AnnotatedConstructor findRecordConstructor(AnnotatedClass recordClass,
38+
AnnotationIntrospector intr, MapperConfig<?> config, List<String> names) {
39+
return new CreatorLocator(recordClass, intr, config)
3440
.locate(names);
3541
}
3642

@@ -150,25 +156,25 @@ public RawTypeName(Class<?> rt, String n) {
150156
}
151157

152158
static class CreatorLocator {
153-
protected final BeanDescription _beanDesc;
154-
protected final DeserializationConfig _config;
159+
protected final AnnotatedClass _recordClass;
160+
protected final MapperConfig<?> _config;
155161
protected final AnnotationIntrospector _intr;
156162

157163
protected final List<AnnotatedConstructor> _constructors;
158164
protected final AnnotatedConstructor _primaryConstructor;
159165
protected final RawTypeName[] _recordFields;
160166

161-
CreatorLocator(DeserializationContext ctxt, BeanDescription beanDesc)
167+
CreatorLocator(AnnotatedClass recordClass, AnnotationIntrospector intr, MapperConfig<?> config)
162168
{
163-
_beanDesc = beanDesc;
169+
_recordClass = recordClass;
164170

165-
_intr = ctxt.getAnnotationIntrospector();
166-
_config = ctxt.getConfig();
171+
_intr = intr;
172+
_config = config;
167173

168-
_recordFields = RecordAccessor.instance().getRecordFields(beanDesc.getBeanClass());
174+
_recordFields = RecordAccessor.instance().getRecordFields(recordClass.getRawType());
169175
if (_recordFields == null) {
170176
// not a record, or no reflective access on native image
171-
_constructors = beanDesc.getConstructors();
177+
_constructors = recordClass.getConstructors();
172178
_primaryConstructor = null;
173179
} else {
174180
final int argCount = _recordFields.length;
@@ -179,10 +185,10 @@ static class CreatorLocator {
179185

180186
// One special case: empty Records, empty constructor is separate case
181187
if (argCount == 0) {
182-
primary = beanDesc.findDefaultConstructor();
188+
primary = recordClass.getDefaultConstructor();
183189
_constructors = Collections.singletonList(primary);
184190
} else {
185-
_constructors = beanDesc.getConstructors();
191+
_constructors = recordClass.getConstructors();
186192
main_loop:
187193
for (AnnotatedConstructor ctor : _constructors) {
188194
if (ctor.getParameterCount() != argCount) {
@@ -199,7 +205,7 @@ static class CreatorLocator {
199205
}
200206
if (primary == null) {
201207
throw new IllegalArgumentException("Failed to find the canonical Record constructor of type "
202-
+ClassUtil.getTypeDescription(_beanDesc.getType()));
208+
+ClassUtil.getTypeDescription(_recordClass.getType()));
203209
}
204210
_primaryConstructor = primary;
205211
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.fasterxml.jackson.databind.records;
2+
3+
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
4+
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
5+
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
6+
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
7+
8+
class Jdk8ConstructorParameterNameAnnotationIntrospector extends JacksonAnnotationIntrospector {
9+
10+
@Override
11+
public String findImplicitPropertyName(AnnotatedMember member) {
12+
if (!(member instanceof AnnotatedParameter)) {
13+
return null;
14+
}
15+
AnnotatedParameter parameter = (AnnotatedParameter) member;
16+
if (!(parameter.getOwner() instanceof AnnotatedConstructor)) {
17+
return null;
18+
}
19+
AnnotatedConstructor constructor = (AnnotatedConstructor) parameter.getOwner();
20+
String parameterName = constructor.getAnnotated().getParameters()[parameter.getIndex()].getName();
21+
22+
if (parameterName == null || parameterName.isBlank()) {
23+
throw new IllegalArgumentException("Unable to extract constructor parameter name for: " + member);
24+
}
25+
26+
return parameterName;
27+
}
28+
}

0 commit comments

Comments
 (0)