After updating to 3.2.x, deserialization seems to be broken in my project. Here is some simple code to reproduce.
Given a class:
import java.beans.ConstructorProperties;
import org.springframework.lang.NonNull;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper = false)
public class TestClass {
@ConstructorProperties("value")
public TestClass(String somethingElse) {
System.out.println("Got value: " + somethingElse);
this.value = somethingElse;
}
@NonNull
String value;
}
And a unit test:
@Test
void testCountryId() throws Exception {
// Given
var object = new TestClass("test");
var mapper = new Jackson2ObjectMapperBuilder().build();
// When
var toString = mapper.writeValueAsString(object);
var fromString = mapper.readValue(toString, TestClass.class);
// Then
then(fromString).isEqualTo(object);
}
The test passes in both versions (in my actual project it fails because the value cannot be null), but
When running the unit test with Spring 3.2, it will print:
Got value: test
Got value: null
However, when running the unit test with Spring 3.1, it will print:
Got value: test
Got value: test
If I change the constructor parameter name from somethingElse
to value
then 3.2 prints the same as 3.1.
Comment From: bclozel
I think this is due to the addition of the parameter names module in #27511.
Rewriting the test without involving Spring at all shows the same behavior:
@Test
void testCountryId() throws Exception {
// Given
var object = new TestClass("test");
var mapper = new ObjectMapper();
mapper.registerModule(new ParameterNamesModule());
// When
var toString = mapper.writeValueAsString(object);
var fromString = mapper.readValue(toString, TestClass.class);
// Then
then(fromString).isEqualTo(object);
assertThat(fromString.getValue()).isEqualTo("test");
}
The behavior is described in Jackson's documentation for the module:
if class Person has a single argument constructor, its argument needs to be annotated with @JsonProperty("propertyName"). This is to preserve legacy behavior, see https://github.com/FasterXML/jackson-databind/issues/1498
Updating the class accordingly makes things work:
@Value
@EqualsAndHashCode(callSuper = false)
public class TestClass {
@ConstructorProperties("value")
public TestClass(@JsonProperty("value") String somethingElse) {
Assert.notNull(somethingElse, "constructor value should not be null");
this.value = somethingElse;
}
@NonNull
String value;
}
While this behavior change comes from a Spring Framework update, I believe the behavior itself comes from Jackson and there's nothing we can do (besides removing the default registration of that module?).
Comment From: sdeleuze
Thanks @bclozel for your analysis.
This change of behavior is indeed a side effect of the Jackson module registration, but on Spring side that was not properly documented. That's now the case in 2 places:
- Jackson2ObjectMapperBuilder
Javadoc now documents such registration properly.
- The upgrade side-effect is now properly documented here.
As a consequence, I have turned this issue into a documentation one.
Comment From: lbenedetto
I created a bug report with jackson and they seem to have accepted it as an actual issue on their end.
It seems that ParameterNamesModule
causes @ConstructorProperties
annotations to be ignored when they shouldn't be.
https://github.com/FasterXML/jackson-databind/issues/4310
Comment From: sdeleuze
Interesting, thanks for the follow up, hopefully this will be fixed on Jackson side.