Jackson の痒いところ Tips

Jackson の、普段あんまり使わない機能過ぎて使いたいときに忘れてぐぐって時間を浪費してしまうようなケースを回避するためにメモメモします。随時更新予定。

見ればわかりますが、元ネタはほぼすべて Stack Overflow です。英語だけだと読むのが辛い、という方向け（主に自分）にまとめています。


# snake_case でプロパティを表現している JSON と camelCase で表記している POJO をマッピングしたい

[java - Jackson overcoming underscores in favor of camel-case - Stack Overflow ](http://stackoverflow.com/a/11632040) より。

引数に ~~`PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES`~~ `PropertyNamingStrategy.SNAKE_CASE` を指定して、 ``ObjectMapper#setPropertyNamingStrategy()`` を呼び出すと、このマッピングをする ``ObjectMapper`` を得ることができます。

(2016-09-29 更新: `PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES` は deprecated になりました。代わりに `PropertyNamingStrategy.SNAKE_CASE` を使う必要があります)

以下、利用例です。

```java
    public static class Hoge {
        // このフィールドは JSON の "hoge_fuga" プロパティに対応する
        public String hogeFuga;
    }

    public static void snakeCaseJsonToCamelCasePojo() throws IOException {
        ObjectMapper mapper = new ObjectMapper()
                .setPropertyNamingStrategy(
                        PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

        Hoge hogeObj = mapper.readValue("{\"hoge_fuga\": \"piyopiyo\"}", Hoge.class);
        System.out.println(hogeObj.hogeFuga);
    }
```

# ObjectMapper#writeValue() などで出力される JSON を整形＆インデント (pretty print) したい

## 書式は何でもいいので、とにかくお手軽に整形したい

[java - Pretty printing JSON from Jackson 2.2's ObjectMapper - Stack Overflow](http://stackoverflow.com/a/17617958) より。

引数に ``SerializationFeature.INDENT_OUTPUT`` を指定して ``ObjectMapper#enable()`` を呼び出すと、pretty print を有効にした ``ObjectMapper`` を得ることができます。

以下、利用例です。

```java
    public static class Fuga {
        public List<String> strings = Arrays.asList("foo", "bar", "baz");
        public int number = 3;
    }

    public static void withPrettyPrint() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper()
                .enable(SerializationFeature.INDENT_OUTPUT);

        String json = mapper.writeValueAsString(new Fuga());

        System.out.println(json);
    }
```

出力結果はこんな感じ。この出力結果はそうでもないですが、標準の pretty printer はクセがあるので要注意です。

```json
{
  "strings" : [ "foo", "bar", "baz" ],
  "number" : 3
}
```

## 書式はそのままでいいけど、インデント数は 2 つから 4 つに変更したい

[java - Custom pretty printer using Jackson library - Stack Overflow](http://stackoverflow.com/a/18098787) より。

``DefaultPrettyPrinter`` が利用している ``Indenter`` を標準の ``DefaultPrettyPrinter.Lf2SpacesIndenter `` から独自のものに差し替えてあげることで実現できます。

```java
    /**
     * インデント 4 つの Indenter 実装です。
     */
    public class Lf4SpacesIndenter extends DefaultPrettyPrinter.Lf2SpacesIndenter {
        final static char[] LONG_SPACES = new char[64 * 2];
        static {
            Arrays.fill(LONG_SPACES, ' ');
        }
    
        @Override
        public void writeIndentation(JsonGenerator jg, int level) throws IOException, JsonGenerationException {
            jg.writeRaw(_lf);
            if (level > 0) {
                int numSpaces = level * 4;
                while (numSpaces > LONG_SPACES.length) {
                    jg.writeRaw(LONG_SPACES, 0, LONG_SPACES.length);
                    numSpaces -= LONG_SPACES.length;
                }
                jg.writeRaw(LONG_SPACES, 0, numSpaces);
            }
        }
    }

    public static class Fuga {
        public List<String> strings = Arrays.asList("foo", "bar", "baz");
        public int number = 3;
    }

    public static void with4SpacesIndentation() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper()
                .enable(SerializationFeature.INDENT_OUTPUT);

        String json = objectMapper
                .writer(new DefaultPrettyPrinter()
                        .withObjectIndenter(new Lf4SpacesIndenter()))
                .writeValueAsString(new Fuga());

        System.out.println(json);
    }
```

## やっぱり標準の pretty printer の書式が気持ち悪いので JSON.stringify() ぽくしたい

サンプルコードは長くなってしまったので、Gist に投稿しました。
https://gist.github.com/komiya-atsushi/832a97c84ccae7cdfc2a

実装は非常にだるいのですが、 ``com.fasterxml.jackson.core.PrettyPrinter`` を実装してしまえばだいたいなんでもできるようになります。


# POJO の null なフィールドは出力しないで JSON にシリアライズしたい

[java - How to tell Jackson to ignore a field during serialization if its value is null? - Stack Overflow](http://stackoverflow.com/questions/11757487/how-to-tell-jackson-to-ignore-a-field-during-serialization-if-its-value-is-null) より。

幾つか方法があります。

## その 1 : ObjectMapper に null なフィールドを無視させる設定をする

POJO のクラスをいじりたくない (いじれない) 場合に有効な方法です。

引数に ``JsonInclude.Include.NON_NULL`` を指定して、 ``ObjectMapper#setSerializationInclusion()`` を呼び出すことで、null なフィールドをシリアライズしない ``ObjectMapper`` を得ることができます。

```java
    public static class Fuga {
        public List<String> strings = null; // このフィールドはシリアライズして欲しくない
        public int number = 3;
    }

    public static void ignoreNullFieldByObjectMapper() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper()
                .setSerializationInclusion(JsonInclude.Include.NON_NULL);

        String json = mapper.writeValueAsString(new Fuga());

        System.out.println(json);
    }
```

実行結果はこちら。

```json
{"number":3}
```

## その 2 : POJO にアノテーションで、null なフィールドはシリアライズ不要である旨を表現させる

``ObjectMapper`` がいろんなところで使われていて、その 1 の対応方法では対処が難しい・面倒な場合にオススメです。

対象の POJO のクラス宣言にて、クラスに対して ``@JsonInclude(JsonInclude.Include.NON_NULL)`` のアノテーションを付与します。

```java
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Fuga {
        public List<String> strings = null; // このフィールドはシリアライズして欲しくない
        public int number = 3;
    }

    public static void ignoreNullFieldByAnnotation() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(new Fuga());

        System.out.println(json);
    }
```

# 列挙型 (enum) のフィールドを、文字列ではなく #ordinal() な値でシリアライズしたい

[``SerializationConfig.Feature.WRITE_ENUMS_USING_INDEX``](http://fasterxml.github.io/jackson-databind/javadoc/2.1.0/com/fasterxml/jackson/databind/SerializationFeature.html) と ``true`` を指定して、 ``ObjectMapper#configure()`` を呼び出します。

```java
    public enum Gender {
        MALE, FEMALE
    }

    public static void serializeUsingEnumOrdinal() throws IOException {
        List<Gender> genders = Arrays.asList(Gender.FEMALE, Gender.MALE);

        String json = new ObjectMapper()
                .configure(SerializationConfig.Feature.WRITE_ENUMS_USING_INDEX, true)
                .writeValueAsString(genders);

        System.out.println(json);
    }
```