How do I give type hints to the Jackson deserializer?
jackson, java
Solution
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.
Problem
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? Edit I should note, I'm using Jackson 1.8.2, but could update if there's a compelling reason to.