I am really confused how jackson (2.9.6 version) ObjectMapper
works with @ConstructorProperties
annotation.
It seems that mapper ignores property names which are present in a @ConstructorProperties
annotation value method.
What's even more interesting - mapper works correctly regardless of properties names.
What I am talking about?
Let's consider custom XmlMapper:
private static final ObjectMapper XML_MAPPER = new XmlMapper()
.setAnnotationIntrospector(
AnnotationIntrospector.pair(
new JaxbAnnotationIntrospector(),
new JacksonAnnotationIntrospector()
)
)
.registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);
and simple Data Transfer Object (DTO):
@XmlRootElement(name = "person")
@XmlAccessorType(XmlAccessType.NONE)
static class Person {
@XmlAttribute
final int age;
@XmlAttribute
final String name;
@XmlAttribute
final LocalDate dateOfBirth;
@ConstructorProperties({"age","name","dateOfBirth"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", dateOfBirth=" + dateOfBirth +
'}';
}
}
I created test to reproduce the issue:
@Test
@DisplayName("Check xml deseralization for Person class")
void deserialization() throws IOException {
String xml = "<person age=\"26\" name=\"Fred\" date-of-birth=\"1991-11-07\"/>";
Person person = XML_MAPPER.readValue(xml, Person.class);
Assertions.assertEquals("Person{age=26, name='Fred', dateOfBirth=1991-11-07}", person.toString());
}
It's strange for me why test is passed regardless of @ConstructorProperties
annotation. The test passed with annotation
@ConstructorProperties({"a","b","c"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
Is it a magic? How jackson processes this annotation? What is an equivalent in jackson annotations to ConstructorProperties?
It's passing because the JaxbAnnotationIntrospector
can determine the property names from the @XmlAttribute
annotations.
The doc on AnnotationIntrospectorPair
says:
Helper class that allows using 2 introspectors such that one introspector acts as the primary one to use; and second one as a fallback used if the primary does not provide conclusive or useful result for a method.
The JacksonAnnotationIntrospector
(which understands the @ConstructorProperties
annotation) isn't being used at all.
If you remove all the JAXB annotations your test will only pass when the correct names are specified in @ConstructorProperties
.
If you want to do it "the jackson way", then remove the JAXB annotations and the JaxbAnnotationIntrospector
completely (just drop the call to setAnnotationIntrospector
, the mapper will default to using the JacksonAnnotationIntrospector
).
Deserialisation will work, but you'll have to add some jackson native annotations if you want to achieve the same serialised form:
@JacksonXmlRootElement(localName = "person")
static class Person {
@JacksonXmlProperty(isAttribute = true)
final int age;
@JacksonXmlProperty(isAttribute = true)
final String name;
@JacksonXmlProperty(isAttribute = true)
final LocalDate dateOfBirth;
@ConstructorProperties({"age", "name", "dateOfBirth"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
//...