I'm trying to deserialize a JSON structure with Jackson and I'm working with a DTO that looks like this:
public class RootLevelDTO {
private List<ComplexEntry> complexEntries;
// ... other fields, not relevant
}
Now, the ComplexEntry
can have sub-types, those have properties of enum types etc. A lot can go wrong here if the other side of the communication updates their API and e.g. adds another sub type or adds an enum literal.
What I would like to do is to tell Jackson:
complexEntries
field...What I tried so far is to use a delegating deserializer for ComplexEntry
:
public class ComplexEntryDeserializer extends StdDeserializer<ComplexEntry> {
private StdDeserializer<ComplexEntry> delegate;
public ComplexEntryDeserializer(StdDeserializer<ComplexEntry> delegate){
this.delegate = delegate;
}
public ComplexEntry deserialize(JsonParser p, DeserializationContext ctxt){
try {
return this.delegate.deserialize(p, ctxt);
}catch(Exception e){
// the list entry failed to deserialize, but we have to return *something* here
return null;
}
}
// ... other mandatory methods, not relevant here
}
This solution has the problem that it will introduce null
values to the complexEntries
list, which I then have to explicitly get rid of with a Converter
.
Is there a more elegant solution to this problem?
After a lot of tinkering I've ended up with the following solution. It doesn't require any additional jackson modules or other magic, only a single (specific) deserializer.
DTO:
public class RootLevelDTO {
// use a custom deserializer for the list
@JsonDeserialize(using = ListOfComplexEntryDeserializer.class)
private List<ComplexEntry> complexEntries;
}
Deserializer:
public class ListOfComplexEntryDeserializer extends JsonDeserializer<List<ComplexEntry>> {
@Override
public List<ComplexEntry> deserialize(JsonParser p, DeserializationContext ctxt) {
List<ComplexEntry> resultList = new ArrayList<>();
while(p.nextToken() != JsonToken.END_ARRAY){
try {
// delegate the deserialization of the individual list entries to the standard deserializers
resultList.add(ctxt.readValue(p, ComplexEntry.class))
}catch(Exception e){
// log that the entry wasn't deserialized properly
System.out.println("ComplexEntry could not be read and will be ignored.");
}
}
return resultList;
}
}
Big disclaimer: While the code above works, it's not something you should go for by design. I'm really with my back to the wall here and have no other choice (due to external factors beyond my control), and for that case it works.