Jacksonでインタフェースと実装クラスがわかれているときにデシリアライズする方法


インタフェースクラス(Child.class)と、実装クラス(ChildImpl.class)がわかれているとき、そのままデシリアライズしようとすると、

public class DeserTest {

  static interface Child {
    public String getMemo();
    public void setMemo(String memo);
  }

  static class ChildImpl implements Child {
    private String memo;

    public String getMemo() {  return memo;  }
    public void setMemo(String memo) {  this.memo = memo;  }
  }

  static class HogeBean {
    private int id;
    private String name;
    private URL url;
    private List<Child> list;
    private Map<Integer, Child> map;

    public List<Child> getList() {  return list;  }
    public void setList(List<Child> list) {  this.list = list;  }
    public Map<Integer, Child> getMap() {  return map;  }
    public void setMap(Map<Integer, Child> map) {  this.map = map;  }
    public int getId() {  return id;  }
    private void setId(int id) {  this.id = id;  }
    public String getName() {  return name;  }
    public void setName(String name) {  this.name = name;  }
    public URL getUrl() {  return url;  }
    public void setUrl(URL url) {  this.url = url;  }
  }
  public static void main(String[] args) throws Exception {
    String text =
      "{\"list\":[{\"memo\":\"めもですが\"},{\"memo\":\"めもですが22\"},{\"memo\":123}],"
          + "\"id\":-773504340,"
          + "\"name\":\"あい うえ男\","
          + "\"url\":\"http://www.hoge.co.jp\","
          + "\"map\":{\"999\":{\"memo\":\"map-999\"},\"222\":{\"memo\":\"map-222\"}}"
          + "}";

    ObjectMapper mapper = new ObjectMapper();
    HogeBean obj = mapper.readValue(text, HogeBean.class);
  }
}

インタフェースクラスをインスタンス化しようとするので、例外がスローされる。

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: 
 Can not construct instance of DeserTest$Child, problem: abstract types either 
 need to be mapped to concrete types, have custom deserializer,
 or be instantiated with additional type information

このようなときは、Java Bean がインターフェースと実装に分離している場合にあるように、@JsonTypeInfoアノテーションを使って対応できる。

けれど、ソースに手を入れてアノテーションを設定することができないこともある。@JsonTypeInfoアノテーションを使わずに、Childインタフェースの実装クラスはChildImplクラスであることをObjectMapperに伝えるにはSimpleModule#addAbstractTypeMapping()を使うといいようだ。

import com.fasterxml.jackson.databind.module.SimpleModule;
...
    SimpleModule module = new SimpleModule();
    module.addAbstractTypeMapping(Child.class, ChildImpl.class);
    mapper.registerModule(module);

    HogeBean obj = mapper.readValue(text, HogeBean.class);
    System.out.println(obj.getMap());
    System.out.println(obj.getMap().getClass());

結果
{999=DeserTest\$ChildImpl@35af4d51, 222=DeserTest$ChildImpl@7fd1c60}
class java.util.LinkedHashMap

Mapインタフェースの実装をHashMapクラスに設定すると、当然そうなる。

    SimpleModule module = new SimpleModule();
    module.addAbstractTypeMapping(Child.class, ChildImpl.class);
    module.addAbstractTypeMapping(Map.class, HashMap.class);
    mapper.registerModule(module);
    ...

結果
{222=DeserTest\$ChildImpl@1f78a4e8, 999=DeserTest$ChildImpl@29928b7c}
class java.util.HashMap