Jackson 2.8.7 で、Lombokの@AllArgsConstructorと@NoArgsConstructorを指定していて、一部プロパティを@JsonIgnoreとしているとJsonMappingExceptionが起きる

下記のようなコードを書くと、Jackson 2.8.7 だとエラーとなります。

public class JacksonExample {

  public static void main(String[] args) throws IOException {

    ObjectMapper mapper = new ObjectMapper();

    User userBefore = new User(1, "Taro", "password");

    String json = mapper.writeValueAsString(userBefore);
    User userAfter = mapper.readValue(json, User.class);
  }

  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public static class User {

    private int id;

    private String name;

    @JsonIgnore
    private String password;
  }
}

スタックとレースは下記のような感じです。

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Could not find creator property with name 'id' (in class com.example.JacksonExample$User)
 at [Source: {"id":1,"name":"Taro"}; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
    at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:551)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:226)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:141)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:403)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:476)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3899)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3794)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
    at com.example.JacksonExample.main(JacksonExample.java:32)

エラー見ても意味がわからなくて、調べていったら、結局 2.8.7 だけでの問題で、前後のバージョン(2.8.6、2.8.8)で発生しないものでした。

条件としては、下記の場合に起きるようです。

  • 引数なしコンストラクタと、引数ありコンストラクタの両方がある
  • 引数ありコンストラクタの引数の数と、JSON上のプロパティの数が異なる

なお 2.8.7 で回避したい場合には、Jacksonとしてどちらかのコンストラクタしか使わないようにさせれば大丈夫でした。ということで、コンストラクタにJsonIgnoreを指定すれば回避できます。

  @Data
  @AllArgsConstructor(onConstructor = @__(@JsonIgnore))
  @NoArgsConstructor
  public static class User {

    private int id;

    private String name;

    @JsonIgnore
    private String password;
  }

ちなみに、Jacksonのようなものって、引数なしコンストラクタしか使わないと思っていましたが、そうではないんですね。 引数ありコンストラクタの引数とプロパティ名をどうマッピングしているんだろうと思いましたが、@java.beans.ConstructorProperties を見てマッピングしてくれているようでした。(LombokだとConstructorPropertiesも付与される)

Jacksonだと Immutable なオブジェクトが扱えるのですばらしいですね。