Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I give type hints to the Jackson deserializer?

Tags:

java

jackson

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 LinkedHashMaps.

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.

like image 810
Mark Peters Avatar asked May 31 '12 14:05

Mark Peters


1 Answers

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.

like image 98
Mark Peters Avatar answered Sep 24 '22 14:09

Mark Peters