I'm using Jackson as a tool to declare some objects whose classes I can't annotate (or modify at all). One of the classes has a setter and getter for an untyped list. Here's a sanitized version:
public class Family {
private List members;
public List getMembers() { return members; }
public void setMembers(List members) { this.members = members; }
//...many, many other properties
}
public class Member {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Here's the JSON I'm trying to deserialize:
{ "members" : [ { "name" : "Mark" } ] }
The naive code I would use is this:
ObjectMapper mapper = new ObjectMapper();
Family family = mapper.readValue(json, Family.class);
Member member = (Member) family.getMembers().get(0);
System.out.println(member.getName());
But of course this fails, as Jackson did not know to create a list of Members
instead of its fallback, a list of LinkedHashMap
s.
What's the easiest way to instruct Jackson to treat members
as a List<Member>
? I don't think I want to use a fully custom deserializer for the class, since there are many other properties that Jackson handles fine.
Here's the best I could come up with, using BeanDeserializerModifier:
mapper.setDeserializerProvider(new StdDeserializerProvider()
.withDeserializerModifier(new BeanDeserializerModifier() {
@Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BasicBeanDescription beanDesc, BeanDeserializerBuilder builder) {
if (beanDesc.getBeanClass() == Family.class) {
CollectionType type = CollectionType.construct(ArrayList.class, SimpleType.construct(Member.class));
TypeDeserializer typeDeserializer = type.getTypeHandler();
SettableBeanProperty.MethodProperty membersProperty = (SettableBeanProperty.MethodProperty) builder.removeProperty("members");
builder.addProperty(new SettableBeanProperty.MethodProperty(
"members",
type,
typeDeserializer,
beanDesc.getClassAnnotations(),
(AnnotatedMethod) membersProperty.getMember()
));
}
return builder;
}}));
It works, but seems really low level (and verbose!) for what I'm trying to do. What am I missing here?
I should note, I'm using Jackson 1.8.2, but could update if there's a compelling reason to.
Mix-in annotations were the critical piece of the puzzle I was missing. Here's a much cleaner way of solving this problem:
ObjectMapper mapper = new ObjectMapper();
mapper.getDeserializationConfig().addMixInAnnotations(Family.class, FamilyMixin.class);
Family family = mapper.readValue(json, Family.class);
Member member = (Member) family.getMembers().get(0);
//...
interface FamilyMixin {
@JsonDeserialize(contentAs = Member.class)
void setMembers(List members);
}
What mix-in annotations let you do is annotate a proxy that is under your control. When that mix-in class is applied to the real class, Jackson behaves as if those annotations annotated the real class's members.
In my case, I use JsonDeserialize.contentAs()
to specify the container's content type. But I believe most annotations should be available using this method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With