When trying to deserialize XML to an object that extends an abstract base class, I'm seeing that the list of references contains the expected number of elements, but all the fields on those objects are null.
This only happens if I create an XML reader for the abstract class. If I deserialize directly to the concrete implementation all the fields have the expected value.
I've added the minimum working example below
Expected output (as json for readability)
{
"References": [ { "id": "1", "Type": "Secondary Image" } ]
}
Actual output (as json for readability)
{
"References": [ { "id": null, "Type": null } ]
}
Test Data
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Foo TypeID="ConcreteA">
<Reference ID="1" Type="Secondary Image">
<Values/>
</Reference>
</Foo>
Abstract base class
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "TypeID", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ConcreteClassA.class, name = "ConcreteA")
})
public abstract class AbstractBase {
@JacksonXmlProperty(localName = "TypeID", isAttribute = true)
private String typeId;
@JsonIgnore
private List<Reference> references = new ArrayList<>();
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "Reference")
public List<Reference> getReferences() {
return references;
}
@JsonSetter
public AbstractBase setReferences(List<Reference> references) {
this.references.addAll(references);
return this;
}
}
Concrete Implementation
public class ConcreteClassA extends AbstractBase {}
Test Cases
public class DeserializationTest {
@Test
public void deserializedAbstractClass_fieldsShouldNotBeNull() throws JsonProcessingException {
var mapper = new XmlMapper()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.deactivateDefaultTyping()
.registerModule(new JacksonXmlModule());
var xmlData = readTestData();
var reader = mapper.readerFor(AbstractBase.class);
var deserializedObject = reader.readValue(xmlData);
assert(deserializedObject instanceof ConcreteClassA);
var concreteClassA = (ConcreteClassA) deserializedObject;
assert(concreteClassA.getReferences().get(0).getId() != null);
}
@Test
public void deserializedConcreteClass_fieldsShouldNotBeNull() throws JsonProcessingException {
var mapper = new XmlMapper()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.configure(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL, true)
.registerModule(new JacksonXmlModule());
var xmlData = readTestData();
var reader = mapper.readerFor(ConcreteClassA.class);
var deserializedObject = reader.readValue(xmlData);
assert(deserializedObject instanceof ConcreteClassA);
var concreteClassA = (ConcreteClassA) deserializedObject;
assert(concreteClassA.getReferences().get(0).getId() != null);
}
private String readTestData() {
try {
var datafile = getClass().getClassLoader().getResource("TestData.xml");
return Files.lines(Paths.get(datafile.toURI())).collect(Collectors.joining());
} catch (Exception e) { return ""; }
}
}
Turns out there are multiple problems, that I've managed to solve.
This problem was solved by upgrading to 2.12, which means that it's no longer necessary to do this to handle unwrapped elements.
I solved this by having the setter accept a single element, and then adding that to the list
@JsonSetter(value = "Reference")
public AbstractBase setReferences(Reference reference) {
this.references.add(references);
return this;
}