Skip to content

Commit 43c6527

Browse files
authored
Fix #5045 (#5301)
1 parent f64a10f commit 43c6527

File tree

5 files changed

+102
-11
lines changed

5 files changed

+102
-11
lines changed

release-notes/CREDITS-2.x

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Co-Authors (with only partial listings below):
1111
* Joo Hyuk Kim (@JooHyukKim)
1212
* PJ Fanning (@pjfanning)
1313
* Sim Yih Tsern (@yihtsern)
14+
* wrongwrong (@k163377)
1415

1516
----------------------------------------------------------------------------
1617

@@ -1526,7 +1527,7 @@ PJ Fanning (pjfanning@github)
15261527
* Contributed #3530: Change LRUMap to just evict one entry when maxEntries reached
15271528
(2.14.0)
15281529
* Contributed #3837: Set transformer factory attributes to improve protection against XXE
1529-
(2.14.3)
1530+
(2.14.3
15301531
- And NUMEROUS other contributions not listed here! (for 2.15 and above)
15311532

15321533
Igor Shymko (ancane@github)
@@ -1872,6 +1873,7 @@ wrongwrong (@k163377)
18721873
* Reported #4218: If `@JacksonInject` is specified for field and deserialized by
18731874
the Creator, the inject process will be executed twice
18741875
(2.20.0)
1876+
- And many other contributions not listed here! (for 2.21 and above)
18751877
18761878
Bernd Ahlers (@bernd)
18771879
* Reported #4742: Deserialization with Builder, External type id, `@JsonCreator` failing

release-notes/VERSION-2.x

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

77
2.21.0 (not yet released)
88

9+
#5045: If there is a no-parameter constructor marked as `JsonCreator` and
10+
a constructor reported as `DefaultCreator`, latter is incorrectly used
11+
(reported by @wrongwrong)
912
#5293: Fix minor typo in `PropertyBindingException.getMessageSuffix()`
1013
(reported by Johny L)
1114

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

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,13 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
690690
List<PotentialCreator> constructors = _collectCreators(_classDef.getConstructors());
691691
List<PotentialCreator> factories = _collectCreators(_classDef.getFactoryMethods());
692692

693+
// Note! 0-param ("default") constructor is NOT included in 'constructors':
694+
PotentialCreator zeroParamsConstructor;
695+
{
696+
AnnotatedConstructor ac = _classDef.getDefaultConstructor();
697+
zeroParamsConstructor = (ac == null) ? null : _potentialCreator(ac);
698+
}
699+
693700
// Then find what is the Primary Constructor (if one exists for type):
694701
// for Java Records and potentially other types too ("data classes"):
695702
// Needs to be done early to get implicit names populated
@@ -699,20 +706,30 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
699706
} else {
700707
// 02-Nov-2024, tatu: Alas, naming here is confusing: method properly
701708
// should have been "findPrimaryCreator()" so as not to confused with
702-
// 0-args default Creators...
709+
// 0-param default Creators...
703710
primaryCreator = _annotationIntrospector.findDefaultCreator(_config, _classDef,
704711
constructors, factories);
705712
}
706713
// Next: remove creators marked as explicitly disabled
707714
_removeDisabledCreators(constructors);
708715
_removeDisabledCreators(factories);
709-
716+
if (zeroParamsConstructor != null && _isDisabledCreator(zeroParamsConstructor)) {
717+
zeroParamsConstructor = null;
718+
}
719+
710720
// And then remove non-annotated static methods that do not look like factories
711721
_removeNonFactoryStaticMethods(factories, primaryCreator);
712722

713723
// and use annotations to find explicitly chosen Creators
714724
if (_useAnnotations) { // can't have explicit ones without Annotation introspection
715-
// Start with Constructors as they have higher precedence:
725+
// Start with Constructors as they have higher precedence
726+
727+
// 08-Sep-2025, tatu: [databind#5045] Need to ensure 0-param ("default")
728+
// constructor considered if annotated (disabled case handled above).
729+
if (zeroParamsConstructor != null && zeroParamsConstructor.isAnnotated()) {
730+
creators.setPropertiesBased(_config, zeroParamsConstructor, "explicit");
731+
}
732+
716733
_addExplicitlyAnnotatedCreators(creators, constructors, props, false);
717734
// followed by Factory methods (lower precedence)
718735
_addExplicitlyAnnotatedCreators(creators, factories, props,
@@ -753,7 +770,7 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
753770
final ConstructorDetector ctorDetector = _config.getConstructorDetector();
754771
if (!creators.hasPropertiesBasedOrDelegating()
755772
&& !ctorDetector.requireCtorAnnotation()) {
756-
// But only if no Default (0-args) constructor available OR if we are configured
773+
// But only if no Default (0-params) constructor available OR if we are configured
757774
// to prefer properties-based Creators
758775
if ((_classDef.getDefaultConstructor() == null)
759776
|| ctorDetector.singleArgCreatorDefaultsToProperties()) {
@@ -809,25 +826,33 @@ private List<PotentialCreator> _collectCreators(List<? extends AnnotatedWithPara
809826
}
810827
List<PotentialCreator> result = new ArrayList<>();
811828
for (AnnotatedWithParams ctor : ctors) {
812-
JsonCreator.Mode creatorMode = _useAnnotations
813-
? _annotationIntrospector.findCreatorAnnotation(_config, ctor) : null;
814829
// 06-Jul-2024, tatu: Can't yet drop DISABLED ones; add all (for now)
815-
result.add(new PotentialCreator(ctor, creatorMode));
830+
result.add(_potentialCreator(ctor));
816831
}
817832
return (result == null) ? Collections.emptyList() : result;
818833
}
819834

835+
private PotentialCreator _potentialCreator(AnnotatedWithParams ctor) {
836+
final JsonCreator.Mode creatorMode = _useAnnotations
837+
? _annotationIntrospector.findCreatorAnnotation(_config, ctor) : null;
838+
return new PotentialCreator(ctor, creatorMode);
839+
}
840+
820841
private void _removeDisabledCreators(List<PotentialCreator> ctors)
821842
{
822843
Iterator<PotentialCreator> it = ctors.iterator();
823844
while (it.hasNext()) {
824-
// explicitly prevented? Remove
825-
if (it.next().creatorMode() == JsonCreator.Mode.DISABLED) {
845+
// explicitly disabled? Remove
846+
if (_isDisabledCreator(it.next())) {
826847
it.remove();
827848
}
828849
}
829850
}
830851

852+
private boolean _isDisabledCreator(PotentialCreator ctor) {
853+
return ctor.creatorMode() == JsonCreator.Mode.DISABLED;
854+
}
855+
831856
private void _removeNonVisibleCreators(List<PotentialCreator> ctors)
832857
{
833858
Iterator<PotentialCreator> it = ctors.iterator();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public PotentialCreators() { }
2424
/**********************************************************************
2525
*/
2626

27-
// desc -> "explicit", "implicit" etc
27+
// mode -> "explicit", "implicit" etc
2828
public void setPropertiesBased(MapperConfig<?> config, PotentialCreator ctor, String mode)
2929
{
3030
if (propertiesBased != null) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.fasterxml.jackson.databind.deser.creators;
2+
3+
import java.util.List;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import com.fasterxml.jackson.annotation.JsonCreator;
8+
import com.fasterxml.jackson.databind.*;
9+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
10+
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
11+
import com.fasterxml.jackson.databind.introspect.PotentialCreator;
12+
import com.fasterxml.jackson.databind.json.JsonMapper;
13+
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
14+
15+
import static org.junit.jupiter.api.Assertions.assertEquals;
16+
import static org.junit.jupiter.api.Assertions.assertNotNull;
17+
18+
public class NoParamsCreatorDefault5045Test extends DatabindTestUtil
19+
{
20+
static class User5045 {
21+
public int age;
22+
23+
public User5045(@ImplicitName("age") int age) {
24+
throw new IllegalStateException("Should not be called");
25+
}
26+
27+
@JsonCreator
28+
public User5045() {
29+
this.age = -1;
30+
}
31+
32+
public int getAge() { return age; }
33+
}
34+
35+
@SuppressWarnings("serial")
36+
static class AI5045 extends ImplicitNameIntrospector {
37+
@Override
38+
public PotentialCreator findDefaultCreator(MapperConfig<?> config,
39+
AnnotatedClass valueClass,
40+
List<PotentialCreator> declaredConstructors,
41+
List<PotentialCreator> declaredFactories)
42+
{
43+
for (PotentialCreator pc : declaredConstructors) {
44+
if (pc.paramCount() != 0) {
45+
return pc;
46+
}
47+
}
48+
return null;
49+
}
50+
}
51+
52+
@Test
53+
public void defaultCreator5045() throws Exception {
54+
ObjectMapper mapper = JsonMapper.builder().annotationIntrospector(new AI5045()).build();
55+
String json = "{ }";
56+
57+
User5045 user = mapper.readValue(json, User5045.class);
58+
assertNotNull(user);
59+
assertEquals(-1, user.getAge());
60+
}
61+
}

0 commit comments

Comments
 (0)