question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unannotated single-argument constructor / factory method not considered a creator

See original GitHub issue

Take the following domain class on Java 8 compiled with -parameters:

class SingleValueWrapper {

	private final Integer integer;

	public SingleOtherValue(Integer integer) {
		this.integer = integer;
	}
}

This test:

@Test
public void testname() throws Exception {

	ObjectMapper mapper = new ObjectMapper();
	mapper.registerModule(new ParameterNamesModule());

	SingleValueWrapper value = mapper.readValue("{ \"integer\" : 2 }", SingleValueWrapper.class);

	assertThat(value, is(notNullValue()));
}

fails with:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.example.LombokImmutablesTests$SingleValueWrapper` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{ "integer" : 2 }"; line: 1, column: 3]
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1342)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1031)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1290)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
	at com.example.LombokImmutablesTests.testname4(LombokImmutablesTests.java:73)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

A couple of more observations:

  • Adding an explicit annotation (e.g. @ConstructorProperties) makes the test go green. Unfortunately I don’t control the class so that I cannot add any annotations. Also, I thought adding the parameter names module should be sufficient as a name can now be derived from the class and additional annotations shouldn’t be needed. I.e. the annotation would just redeclare what’s already defined.
  • Adding an additional property makes the test pass. This is probably what strikes me most about the problem as it’s complete unintuitive why a constructor wouldn’t work for one parameter, but would for more than one.
  • The same applies if you rather expose a factory method than a constructor.
  • Even configuring a Mode explicitly doesn’t change anything about the failing test.
  • The execution at some point invokes findNameForDeserialization(…) which ´ParameterNamesAnnotationIntrospector` does not override. Is that by design? Because if I go ahead and implement that method like this:
@Override
public PropertyName findNameForDeserialization(Annotated a) {

	if (a instanceof AnnotatedParameter) {
		return PropertyName.construct(findParameterName((AnnotatedParameter) a));
	}

	return super.findNameForDeserialization(a);
}

things start to work again. I can see that this method is supposed to explicitly obtain a name from an annotation but it feels like that path is favored for a one-argument constructor.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:2
  • Comments:26 (15 by maintainers)

github_iconTop GitHub Comments

2reactions
cowtowncodercommented, Feb 29, 2020

@lpandzic Unless a very specific combo-fix (one part in jackson-databind, other [possibly] in java8 module), yes. So challenge would be that of restricting scope to – I think? – case of 1-argument, non-annotated constructor (and no other constructors).

Thinking about this aloud, addition of Builder-style construction in 2.10 and later would allow defining a new configuration setting, which would allow Enum for something like CreatorDiscovery or such…and that could change settings to, say:

  1. Use current heuristics for non-mode-defining JsonCreator, introspection [current setting]
  2. No heuristics: only explicit @JsonCreator (but what about mode?)
  3. Heuristics, use Mode.PROPERTIES if not specified (no annotation or no mode) – most likely option you would prefer?
  4. Heuristics, use Mode.DELEGATING if mode not specified, for 1-arg constructor

and with that actually java8 module would not need anything. Alternatively, bit more complicated, would be to define something like CreatorConfiguration POJO, with accessors. But that might be more useful for case of trying to select from multiple constructors, something not yet supported.

Now that I wrote this, it actually seems much more doable. WDYT? One thing I lack right now is time to work on things but I sort of like this approach.

1reaction
WellingRcommented, Feb 29, 2020

FYI, my specific use case is lombok @Value classes. Which are classes with all-arg constructors and all fields final. For the cases where there are multiple fields everything works fine by default. However for the classes with a single field, this behaviour surprises me.

Single-Argument Constructors CAN NOT act the same as multi-argument ones because of the ambiguity. I understand, which is why a setting has been proposed for this

The setting in question CAN NOT solve this problem as it does not induce equivalent of @JsonCreator, and futher, SHOULD NOT do so You mean the the setting I mentioned before? Looking at the code it configures a default for @JsonCreator constructor, I did not completely get from the documentation that this setting applied to JsonCreators only.

However what about a setting HANDLE_SINGLE_ARG_CONSTRUCTOR_AS_PROPERTY_CREATOR? Is there any reason why adding such a setting would not be possible (or would not be a good idea)?

Read more comments on GitHub >

github_iconTop Results From Across the Web

ObjectMapper can't deserialize without default constructor ...
Constructor /factory method where every argument is annotated with either JsonProperty or JacksonInject , to indicate name of property to bind to.
Read more >
How does the default mode work for @JsonCreator?
in cases of single-argument creator (constructor or factory method, annotated with `@JsonCreator`, but without: 1. `mode` property to determine which type ...
Read more >
Jackson 2.12 Most Wanted (3/5) - cowtowncoder - Medium
Consider this explicitly annotated POJO: ... Because 1-argument constructors (and static factory methods) are ambiguous: there are 2 ...
Read more >
@NoArgsConstructor, @RequiredArgsConstructor, ...
Constructors made to order: Generates constructors that take no arguments, one argument per final / non-null field, or one argument for every field....
Read more >
JsonCreator (Jackson-annotations 2.9.0 API) - FasterXML
Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found