I'm trying to write some code that will deserialize JSON into somebody elses class. That is, I don't own the target class so I can't annotate it.
In particular, this class has some helper methods that are complicating the deserialization process. Something like this:
class Result {
private List<String> ids;
public List<String> getIds() {
return ids;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
// Helpers
public String getId() {
return this.ids.isEmpty() ? null : this.ids.get(0);
}
public void setId(String id) {
this.ids = List.of(id);
}
}
When serialized, we get both ids
and id
come through as fields:
{
"ids": ["1", "2", "3"],
"id": "1"
}
And then when deserializing this JSON, Jackson is calling both setters - reasonably enough - and thus the object is wrong. The end result is that the ids
field is set to ["1"]
and not ["1", "2", "3"]
as it should be.
So what I want to be able to do is fix this. And I'm thinking that the easiest/safest/best way is to be able to modify the JSON AST somewhere in the deserializing process. Specifically by just removing the "id" field from the JSON so that it doesn't get seen by the standard deserializer. (I know this works by doing it manually with string manipulation, but that's awful)
I could write a full deserializer for all the fields, but then I'm beholden to maintaining it if any new fields are added in the future, when all I actually want to do is ignore one single field and have everything else processed as normal. The real class actually has about a dozen fields at present, and I can't guarantee that it won't change in the future.
What I can't work out is how to do this. I was really hoping there was just some standard JsonDeserializer
subclass that would let me do this, but I can't find one. The best I've been able to work out is a normal StdDeserializer
that then uses parser.getCodec().treeToValue()
- courtesty of this answer - except that this results in an infinite loop as it calls back into the exact same deserializer every time!
Frustratingly, most answers to this problem are "Just annotate the class" - and that's not an option here!
Is there a standard way to achieve this?
Cheers
CodePudding user response:
There are the Jacksons mixins for exactly this case, a super useful feature!
For your case, define the mixin class and annotate it as if you were annotating the original; you only need to include the overrides, e.g.:
@JsonIgnoreProperties({ "id" })
public class ResultMixin {
// nothing else required!
}
Now, you will have to hook on the ObjectMapper
creation to define the mixin. This depends on the framework you are using, but in the end it should look like this:
ObjectMapper om = ...
om.addMixIn(Result.class, ResultMixin.class);
Now this ObjectMapper
will take into account the information from you mixin to serialize objects of type Result
(and ignore the synthetic id
property).
CodePudding user response:
And, of course, I've just found out how to do it :)
I can use BeanDeserializerModifier
instead to make things act as needed.
public class MyDeserializerModifier extends BeanDeserializerModifier {
@Override
public List<BeanPropertyDefinition> updateProperties(final DeserializationConfig config,
final BeanDescription beanDesc, final List<BeanPropertyDefinition> propDefs) {
if (beanDesc.getBeanClass().equals(Result.class)) {
propDefs.removeIf(prop -> prop.getName().equals("id"));
}
return super.updateProperties(config, beanDesc, propDefs);
}
@Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config, final BeanDescription beanDesc,
final BeanDeserializerBuilder builder) {
if (beanDesc.getBeanClass().equals(Result.class)) {
builder.addIgnorable("id");
}
return super.updateBuilder(config, beanDesc, builder);
}
}