Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9da10e3
Fix #5246
cowtowncoder Sep 10, 2025
6cced68
Actual fix
cowtowncoder Sep 10, 2025
b03d3b7
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 11, 2025
918c115
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 12, 2025
0f62396
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
fa42995
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
5db5ca2
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
b79f233
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
0359ab4
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
0f10f54
...
cowtowncoder Sep 16, 2025
a85f41a
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
28b54d2
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
c6d7990
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
8937101
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
edf7bea
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
0c35678
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
37de81d
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
542b018
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
a4e34a2
Merge branch '3.x' into tatu/3.0/5246-jsoncreator-no-params
cowtowncoder Sep 16, 2025
253fa78
Add configurability
cowtowncoder Sep 19, 2025
2bde18b
Last tweaks
cowtowncoder Sep 19, 2025
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
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Versions: 3.x (for earlier see VERSION-2.x)
#5314: Add `MapperFeature.DETECT_PARAMETER_NAMES` to allow disabling `parameter-names`
functionality
(fixed by Joo-Hyuk K)
#5318: Allow auto-detection of Properties-based Constructor even if class has
no-parameters (default) Constructor

3.0.0-rc9 (05-Sep-2025)

Expand Down
57 changes: 50 additions & 7 deletions src/main/java/tools/jackson/databind/cfg/ConstructorDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@
public final class ConstructorDetector
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 3L;

/**
* See {@link #allowImplicitWithDefaultConstructor} for details.
*
* @since 3.0
*/
public final static boolean DEFAULT_ALLOW_IMPLICIT_WITH_DEFAULT_CONSTRUCTOR = true;

/**
* Definition of alternate handling modes of single-argument constructors
Expand Down Expand Up @@ -122,6 +129,12 @@ public enum SingleArgConstructor {
*/
protected final boolean _allowJDKTypeCtors;

/**
* Whether implicit detection of Properties-based Constructors is allowed
* when there is the default (no-parameter) constructor available.
*/
protected final boolean _allowImplicitWithDefaultConstructor;

/*
/**********************************************************************
/* Life-cycle
Expand All @@ -130,34 +143,51 @@ public enum SingleArgConstructor {

protected ConstructorDetector(SingleArgConstructor singleArgMode,
boolean requireCtorAnnotation,
boolean allowJDKTypeCtors)
boolean allowJDKTypeCtors,
boolean allowImplicitWithDefaultConstructor)
{
_singleArgMode = singleArgMode;
_requireCtorAnnotation = requireCtorAnnotation;
_allowJDKTypeCtors = allowJDKTypeCtors;
_allowImplicitWithDefaultConstructor = allowImplicitWithDefaultConstructor;
}

/**
* Constructors used for default configurations which only varies
* by {@code _singleArgMode}
*/
protected ConstructorDetector(SingleArgConstructor singleArgMode) {
this(singleArgMode, false, false);
this(singleArgMode, false, false, DEFAULT_ALLOW_IMPLICIT_WITH_DEFAULT_CONSTRUCTOR);
}

public ConstructorDetector withSingleArgMode(SingleArgConstructor singleArgMode) {
return new ConstructorDetector(singleArgMode,
_requireCtorAnnotation, _allowJDKTypeCtors);
_requireCtorAnnotation, _allowJDKTypeCtors,
_allowImplicitWithDefaultConstructor);
}

public ConstructorDetector withRequireAnnotation(boolean state) {
return new ConstructorDetector(_singleArgMode,
state, _allowJDKTypeCtors);
state, _allowJDKTypeCtors,
_allowImplicitWithDefaultConstructor);
}

public ConstructorDetector withAllowJDKTypeConstructors(boolean state) {
return new ConstructorDetector(_singleArgMode,
_requireCtorAnnotation, state);
_requireCtorAnnotation, state,
_allowImplicitWithDefaultConstructor);
}

/**
* Mutant factory method that can be used to change setting that
* {@link #allowImplicitWithDefaultConstructor} returns.
*
* @since 3.0
*/
public ConstructorDetector withAllowImplicitWithDefaultConstructor(boolean state) {
return new ConstructorDetector(_singleArgMode,
_requireCtorAnnotation, _allowJDKTypeCtors,
state);
}

/*
Expand All @@ -178,6 +208,19 @@ public boolean allowJDKTypeConstructors() {
return _allowJDKTypeCtors;
}

/**
* Method that can be used to determine whether implicit detection of
* Properties-based Constructors is allowed when there is a default
* (no-parameter) constructor available.
* Note that this only matters if {@link #shouldIntrospectImplicitConstructors}
* returns {@code true}.
*
* @since 3.0
*/
public boolean allowImplicitWithDefaultConstructor() {
return _allowImplicitWithDefaultConstructor;
}

public boolean singleArgCreatorDefaultsToDelegating() {
return _singleArgMode == SingleArgConstructor.DELEGATING;
}
Expand All @@ -196,7 +239,7 @@ public boolean singleArgCreatorDefaultsToProperties() {
*
* @return True if implicit constructor detection should be enabled; false if not
*/
public boolean shouldIntrospectorImplicitConstructors(Class<?> rawType) {
public boolean shouldIntrospectImplicitConstructors(Class<?> rawType) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Fix a typo (3.0 API change).

// May not allow implicit creator introspection at all:
if (_requireCtorAnnotation) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ protected ValueInstantiator _constructDefaultValueInstantiator(DeserializationCo

// 18-Sep-2020, tatu: Although by default implicit introspection is allowed, 2.12
// has settings to prevent that either generally, or at least for JDK types
final boolean findImplicit = ctorDetector.shouldIntrospectorImplicitConstructors(beanDescRef.getBeanClass());
final boolean findImplicit = ctorDetector.shouldIntrospectImplicitConstructors(beanDescRef.getBeanClass());
if (findImplicit) {
_addImplicitDelegatingConstructors(ctxt, beanDescRef, vchecker,
creators,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,12 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
&& !ctorDetector.requireCtorAnnotation()) {
// But only if no Default (0-params) constructor available OR if we are configured
// to prefer properties-based Creators
if ((_classDef.getDefaultConstructor() == null)
|| ctorDetector.singleArgCreatorDefaultsToProperties()) {
// 19-Sep-2025, tatu: [databind#5318] Actually let's potentially allow
// implicit constructor even if class also has 0-param ("default") constructor
// (as long as it is not annotated)
if ((zeroParamsConstructor == null)
|| (!zeroParamsConstructor.isAnnotated()
&& ctorDetector.allowImplicitWithDefaultConstructor())) {
_addImplicitConstructor(creators, constructors, props);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package tools.jackson.databind.deser.creators;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;

import tools.jackson.databind.*;
import tools.jackson.databind.cfg.ConstructorDetector;
import tools.jackson.databind.exc.MismatchedInputException;
import tools.jackson.databind.testutil.DatabindTestUtil;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;

// For [databind#5318]: allow auto-detection of Properties-based Constructor
// even with class having Default (no-parameters) Constructor
public class NoParamsCreator5318Test extends DatabindTestUtil
{
static class Pojo5318Working {
final int productId;
final String name;

public Pojo5318Working() {
this(0, null);
}

public Pojo5318Working(int productId, String name) {
this.productId = productId;
this.name = name;
}
}

// No auto-detection, due to explicit annotation for 0-params ctor
static class Pojo5318Annotated {
@JsonCreator
public Pojo5318Annotated() { }

public Pojo5318Annotated(int productId, String name) {
throw new IllegalStateException("Should not be called");
}
}

// No auto-detection, due to explicit ignoral of 0-params ctor
static class Pojo5318Ignore {
protected Pojo5318Ignore() { }

@JsonIgnore
public Pojo5318Ignore(int productId, String name) {
throw new IllegalStateException("Should not be called");
}
}

private final ObjectMapper MAPPER = jsonMapperBuilder()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();

// For [databind#5318]: intended usage
@Test
void creatorDetectionWithNoParamsCtor() throws Exception {
Pojo5318Working pojo = MAPPER.readValue("{\"productId\":1,\"name\":\"foo\"}", Pojo5318Working.class);
assertEquals(1, pojo.productId);
assertEquals("foo", pojo.name);
}

// For [databind#5318]: avoid detection with explicit annotation on 0-params ctor
@Test
void noCreatorDetectionDueToCreatorAnnotation() throws Exception {
assertNotNull(MAPPER.readValue("{}", Pojo5318Annotated.class));
}

// For [databind#5318]: avoid detection with explicit ignoral of parameterized ctor
@Test
void noCreatorDetectionDueToIgnore() throws Exception {
assertNotNull(MAPPER.readValue("{}", Pojo5318Ignore.class));
}

// For [databind#5318]: avoid detection when configured to do so (not allow
// implicit with default ctor)
@Test
void noCreatorDetectionDueToFeatureDisabled() throws Exception {
final ObjectMapper mapper = jsonMapperBuilder()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.constructorDetector(ConstructorDetector.DEFAULT
.withAllowImplicitWithDefaultConstructor(false))
.build();
try {
mapper.readValue("{\"productId\": 1}", Pojo5318Working.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
verifyException(e, "Unrecognized property \"productId\"");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ static FactoryFromPoint createIt(Point p)
// Also: let's test BigDecimal-from-JSON-String factory
static class FactoryFromDecimalString
{
int _value;
int _value;

private FactoryFromDecimalString(BigDecimal d) {
_value = d.intValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public Content() { }

public void addPerson(String p) {
if (_persons == null) {
_persons = new ArrayList<String>();
_persons = new ArrayList<>();
}
_persons.add(p);
}
Expand Down
Loading