Skip to content

Commit cc6d372

Browse files
committed
Fix #1498
1 parent 0ac0b46 commit cc6d372

File tree

6 files changed

+91
-20
lines changed

6 files changed

+91
-20
lines changed

release-notes/CREDITS-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ Fernando Otero (zeitos@github)
175175
Lovro Pandžić (lpandzic@github)
176176
* Reported #421: @JsonCreator not used in case of multiple creators with parameter names
177177
(2.5.0)
178+
* Requested #1498: Allow handling of single-arg constructor as property based by default
179+
(2.12.0)
178180

179181
Adam Stroud (adstro@github)
180182
* Contributed #576: Add fluent API for adding mixins

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Project: jackson-databind
1616
(reported by Mike G; fix contributed by Ville K)
1717
#1296: Add `@JsonIncludeProperties(propertyNames)` (reverse of `@JsonIgnoreProperties`)
1818
(contributed Baptiste P)
19+
#1498: Allow handling of single-arg constructor as property based by default
20+
(requested by Lovro P)
1921
#1852: Allow case insensitive deserialization of String value into
2022
`boolean`/`Boolean` (esp for Excel)
2123
(requested by Patrick J)

src/main/java/com/fasterxml/jackson/databind/cfg/ConstructorDetector.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.fasterxml.jackson.databind.cfg;
22

3+
import com.fasterxml.jackson.databind.util.ClassUtil;
4+
35
/**
46
* Configurable handler used to select aspects of selecting
57
* constructor to use as "Creator" for POJOs.
@@ -143,17 +145,17 @@ protected ConstructorDetector(SingleArgConstructor singleArgMode) {
143145
this(singleArgMode, false, false);
144146
}
145147

146-
protected ConstructorDetector withSingleArgMode(SingleArgConstructor singleArgMode) {
148+
public ConstructorDetector withSingleArgMode(SingleArgConstructor singleArgMode) {
147149
return new ConstructorDetector(singleArgMode,
148150
_requireCtorAnnotation, _allowJDKTypeCtors);
149151
}
150152

151-
protected ConstructorDetector withRequireAnnotation(boolean state) {
153+
public ConstructorDetector withRequireAnnotation(boolean state) {
152154
return new ConstructorDetector(_singleArgMode,
153155
state, _allowJDKTypeCtors);
154156
}
155157

156-
protected ConstructorDetector withAllowJDKTypes(boolean state) {
158+
public ConstructorDetector withAllowJDKTypes(boolean state) {
157159
return new ConstructorDetector(_singleArgMode,
158160
_requireCtorAnnotation, state);
159161
}
@@ -183,4 +185,13 @@ public boolean singleArgCreatorDefaultsToDelegating() {
183185
public boolean singleArgCreatorDefaultsToProperties() {
184186
return _singleArgMode == SingleArgConstructor.PROPERTIES;
185187
}
188+
189+
public boolean allowImplicitCreators(Class<?> rawType) {
190+
// May not allow implicit creator introspection at all:
191+
if (_requireCtorAnnotation) {
192+
return false;
193+
}
194+
// But if it is allowed, may further limit use for JDK types
195+
return _allowJDKTypeCtors || !ClassUtil.isJDKClass(rawType);
196+
}
186197
}

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

+14-10
Original file line numberDiff line numberDiff line change
@@ -252,12 +252,16 @@ protected ValueInstantiator _constructDefaultValueInstantiator(DeserializationCo
252252
throws JsonMappingException
253253
{
254254
final CreatorCollectionState ccState;
255+
final boolean findImplicit;
255256

256257
{
257258
final DeserializationConfig config = ctxt.getConfig();
258259
// need to construct suitable visibility checker:
259260
final VisibilityChecker<?> vchecker = config.getDefaultVisibilityChecker(beanDesc.getBeanClass(),
260261
beanDesc.getClassInfo());
262+
// 18-Sep-2020, tatu: Although by default implicit introspection is allowed, 2.12
263+
// has settings to prevent that either generally, or at least for JDK types
264+
findImplicit = config.getConstructorDetector().allowImplicitCreators(beanDesc.getBeanClass());
261265

262266
// 24-Sep-2014, tatu: Tricky part first; need to merge resolved property information
263267
// (which has creator parameters sprinkled around) with actual creator
@@ -274,7 +278,7 @@ protected ValueInstantiator _constructDefaultValueInstantiator(DeserializationCo
274278
}
275279

276280
// Start with explicitly annotated factory methods
277-
_addExplicitFactoryCreators(ctxt, ccState);
281+
_addExplicitFactoryCreators(ctxt, ccState, findImplicit);
278282

279283
// constructors only usable on concrete types:
280284
if (beanDesc.getType().isConcrete()) {
@@ -296,16 +300,16 @@ protected ValueInstantiator _constructDefaultValueInstantiator(DeserializationCo
296300
// TODO: look for `@JsonCreator` annotated ones, throw explicit exception?
297301
;
298302
} else {
299-
_addExplicitConstructorCreators(ctxt, ccState);
300-
if (!ccState.hasExplicitFactories() && !ccState.hasExplicitConstructors()
301-
&& ccState.hasImplicitConstructorCandidates()) {
303+
_addExplicitConstructorCreators(ctxt, ccState, findImplicit);
304+
if (ccState.hasImplicitConstructorCandidates()
305+
&& !ccState.hasExplicitFactories() && !ccState.hasExplicitConstructors()) {
302306
_addImplicitConstructorCreators(ctxt, ccState, ccState.implicitConstructorCandidates());
303307
}
304308
}
305309
}
306310
// and finally, implicitly found factory methods if nothing explicit found
307-
if (!ccState.hasExplicitFactories() && !ccState.hasExplicitConstructors()
308-
&& ccState.hasImplicitFactoryCandidates()) {
311+
if (ccState.hasImplicitFactoryCandidates()
312+
&& !ccState.hasExplicitFactories() && !ccState.hasExplicitConstructors()) {
309313
_addImplicitFactoryCreators(ctxt, ccState, ccState.implicitFactoryCandidates());
310314
}
311315
return ccState.creators.constructValueInstantiator(ctxt);
@@ -418,7 +422,7 @@ protected void _addRecordConstructor(DeserializationContext ctxt, CreatorCollect
418422
*/
419423

420424
protected void _addExplicitConstructorCreators(DeserializationContext ctxt,
421-
CreatorCollectionState ccState)
425+
CreatorCollectionState ccState, boolean findImplicit)
422426
throws JsonMappingException
423427
{
424428
final BeanDescription beanDesc = ccState.beanDesc;
@@ -444,7 +448,7 @@ protected void _addExplicitConstructorCreators(DeserializationContext ctxt,
444448
}
445449
if (creatorMode == null) {
446450
// let's check Visibility here, to avoid further processing for non-visible?
447-
if (vchecker.isCreatorVisible(ctor)) {
451+
if (findImplicit && vchecker.isCreatorVisible(ctor)) {
448452
ccState.addImplicitConstructorCandidate(CreatorCandidate.construct(intr,
449453
ctor, creatorParams.get(ctor)));
450454
}
@@ -623,7 +627,7 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt,
623627
*/
624628

625629
protected void _addExplicitFactoryCreators(DeserializationContext ctxt,
626-
CreatorCollectionState ccState)
630+
CreatorCollectionState ccState, boolean findImplicit)
627631
throws JsonMappingException
628632
{
629633
final BeanDescription beanDesc = ccState.beanDesc;
@@ -638,7 +642,7 @@ protected void _addExplicitFactoryCreators(DeserializationContext ctxt,
638642
final int argCount = factory.getParameterCount();
639643
if (creatorMode == null) {
640644
// Only potentially accept 1-argument factory methods
641-
if ((argCount == 1) && vchecker.isCreatorVisible(factory)) {
645+
if (findImplicit && (argCount == 1) && vchecker.isCreatorVisible(factory)) {
642646
ccState.addImplicitFactoryCandidate(CreatorCandidate.construct(intr, factory, null));
643647
}
644648
continue;

src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -1131,14 +1131,21 @@ public static boolean isJacksonStdImpl(Class<?> implClass) {
11311131
}
11321132

11331133
/**
1134-
* Some aspects of handling need to be changed for JDK types (and possibly
1135-
* some extensions under {@code javax.}?): for example, forcing of access
1134+
* Accessor for checking whether given {@code Class} is under Java package
1135+
* of {@code java.*} or {@code javax.*} (including all sub-packages).
1136+
*<p>
1137+
* Added since some aspects of handling need to be changed for JDK types (and
1138+
* possibly some extensions under {@code javax.}?): for example, forcing of access
11361139
* will not work well for future JDKs (12 and later).
1140+
*<p>
1141+
* Note: in Jackson 2.11 only returned true for {@code java.*} (and not {@code javax.*});
1142+
* was changed in 2.12.
11371143
*
11381144
* @since 2.11
11391145
*/
11401146
public static boolean isJDKClass(Class<?> rawType) {
1141-
return rawType.getName().startsWith("java.");
1147+
final String clsName = rawType.getName();
1148+
return clsName.startsWith("java.") || clsName.startsWith("javax.");
11421149
}
11431150

11441151
/*

src/test/java/com/fasterxml/jackson/databind/deser/creators/ConstructorDetector1498Test.java

+49-4
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public SingleArg2CtorsNotAnnotated(@ImplicitName("value") long value) {
6767
v = (int) (value * 2);
6868
}
6969
}
70-
70+
7171
static class SingleArg1498 {
7272
final int _bar;
7373

@@ -77,10 +77,22 @@ static class SingleArg1498 {
7777
}
7878
}
7979

80+
static class TwoArgsNotAnnotated {
81+
protected int _a, _b;
82+
83+
public TwoArgsNotAnnotated(@ImplicitName("a") int a, @ImplicitName("b") int b) {
84+
_a = a;
85+
_b = b;
86+
}
87+
}
88+
8089
private final ObjectMapper MAPPER_PROPS = mapperFor(ConstructorDetector.USE_PROPERTIES_BASED);
8190
private final ObjectMapper MAPPER_DELEGATING = mapperFor(ConstructorDetector.USE_DELEGATING);
8291
private final ObjectMapper MAPPER_EXPLICIT = mapperFor(ConstructorDetector.EXPLICIT_ONLY);
8392

93+
private final ObjectMapper MAPPER_MUST_ANNOTATE = mapperFor(ConstructorDetector.DEFAULT
94+
.withRequireAnnotation(true));
95+
8496
/*
8597
/**********************************************************************
8698
/* Test methods, selecting from 1-arg constructors, properties-based
@@ -96,7 +108,6 @@ public void test1ArgDefaultsToPropertiesNonAnnotated() throws Exception
96108

97109
public void test1ArgDefaultsToPropertiesNoMode() throws Exception
98110
{
99-
// and similarly for mode-less
100111
SingleArgNoMode value = MAPPER_PROPS.readValue(a2q("{'value' : 137 }"),
101112
SingleArgNoMode.class);
102113
assertEquals(137, value.v);
@@ -105,12 +116,20 @@ public void test1ArgDefaultsToPropertiesNoMode() throws Exception
105116
// And specific test for original [databind#1498]
106117
public void test1ArgDefaultsToPropertiesIssue1498() throws Exception
107118
{
108-
// and similarly for mode-less
109119
SingleArg1498 value = MAPPER_PROPS.readValue(a2q("{'bar' : 404 }"),
110120
SingleArg1498.class);
111121
assertEquals(404, value._bar);
112122
}
113123

124+
// This was working already but verify
125+
public void testMultiArgAsProperties() throws Exception
126+
{
127+
TwoArgsNotAnnotated value = MAPPER_PROPS.readValue(a2q("{'a' : 3, 'b':4 }"),
128+
TwoArgsNotAnnotated.class);
129+
assertEquals(3, value._a);
130+
assertEquals(4, value._b);
131+
}
132+
114133
// 18-Sep-2020, tatu: For now there is a problematic case of multiple eligible
115134
// choices; not cleanly solvable for 2.12
116135
public void test1ArgDefaultsToPropsMultipleCtors() throws Exception
@@ -166,7 +185,7 @@ public void test1ArgDefaultsToHeuristics() throws Exception
166185

167186
/*
168187
/**********************************************************************
169-
/* Test methods, selecting from 1-arg constructors, explicit fail
188+
/* Test methods, selecting from 1-arg constructors, explicit fails
170189
/**********************************************************************
171190
*/
172191

@@ -197,6 +216,32 @@ public void test1ArgFailsNoMode() throws Exception
197216
}
198217
}
199218

219+
public void test1ArgRequiresAnnotation() throws Exception
220+
{
221+
// First: if there is a 0-arg ctor, fine, must use that
222+
SingleArgNotAnnotated value = MAPPER_MUST_ANNOTATE.readValue("{ }",
223+
SingleArgNotAnnotated.class);
224+
assertEquals(new SingleArgNotAnnotated().v, value.v);
225+
226+
// But if no such ctor, will fail
227+
try {
228+
MAPPER_MUST_ANNOTATE.readValue(" { } ", SingleArg1498.class);
229+
fail("Should not pass");
230+
} catch (InvalidDefinitionException e) {
231+
verifyException(e, "no Creators, like default constructor");
232+
}
233+
}
234+
235+
public void testMultiArgRequiresAnnotation() throws Exception
236+
{
237+
try {
238+
MAPPER_MUST_ANNOTATE.readValue(" { } ", TwoArgsNotAnnotated.class);
239+
fail("Should not pass");
240+
} catch (InvalidDefinitionException e) {
241+
verifyException(e, "no Creators, like default constructor");
242+
}
243+
}
244+
200245
/*
201246
/**********************************************************************
202247
/* Helper methods

0 commit comments

Comments
 (0)