Spring Web MVC でカスタマイズした jackson ObjectMapper を使う

**2016-01-28 追記**
Spring boot の場合 Jackson2ObjectMapperBuilder bean を設定するだけでよさそうです。
not boot でも Jackson2ObjectMapperBuilder 経由で ObjectMapper を作ったほうがいろいろいい感じにセットアップされていそう。(個人的にはまどろっこしいなと思いましたが)

refer to https://dzone.com/articles/latest-jackson-integration

# なにがしたいのか？

Spring では、プロジェクトに jackson-databind への依存をいれこみ、 `@ResponseBody` annotation をコントローラのメソッドにつけると、 JSON を返すことができます。

雑だけどこんな感じの例。 (今回 `@RestController` には触れません)

```java
@Controller
public class MainController {
    @RequestMapping(value = "/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public IndexResponse index() {
        return IndexResponse.builder().status(200).message("hello").build();
    }

    @Data
    @Builder
    public static class IndexResponse {
        private int status;
        private String message;
    }
}
```

でもこのままだと、デフォルトの ObjectMapper が利用されるので、

```json
{"status":200,"message":"hello"}
```

みたいに pretty print されてない感じの JSON になってしまう。

で、これをインデントされた JSON で返したりするには、カスタマイズした ObjectMapper を使う必要があるんだけど、それを Spring で利用すればどうすればいいかっていう話です。 (実際に pretty print 形式で返したいかは別として)

ちなみに今回のやりかたを適用すると、 response のほうだけじゃなくて `@RequestBody` な request 側にも同じ ObjectMapper が利用されます。

# WebMvcConfigurerAdapter での設定

実際に request や response で JSON をハンドリングしているのは `HttpMessageConverter` なのですが、カスタマイズした `MappingJackson2HttpMessageConverter` を設定するために WebMvcConfigurerAdapter の `configureMessageConverters()` を override します。

```java
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        final ObjectMapper objectMapper = new ObjectMapper()
                .enable(SerializationFeature.INDENT_OUTPUT);

        converters.add(new MappingJackson2HttpMessageConverter(objectMapper));

        converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
    }
}
```

このやり方、実は Spring の document に書いてあります。 ([21.16.12 Message Converters](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-config-message-converters))

(無設定のデフォルトだと `StringHttpMessageConverter` と `ResourceHttpMessageConverter` と状況に応じて `MappingJackson2HttpMessageConverter` が設定されているのですが、 `configureMessageConverters()` を override するとそれらのデフォルト設定が無効になるので注意してください。上記サンプルでは `StringHttpMessageConverter` を追加してあります。)

これだけで以下のように `INDENT_OUTPUT` が適用された JSON 出力が得られます。[^1]

```json
{
  "status" : 200,
  "message" : "hello"
}
```

[^1]: このインデント幅とかコロンの前後とかキモいなぁ、もっとカスタマイズできないかなぁと思っていましたが、 [こちらの Tips](http://qiita.com/komiya_atsushi/items/6e5b8e6690b82bca8198#2-2) を利用するとカスタマイズできるそうです。

# JUnit テストでの設定

上記の Configuration でカスタマイズする方法だと WebMVC Configuration をいじっているだけなので、実際の controller の出力は改善されましたが、通常の JUnit によるテストコードではうまくいきません。

```java
    @Test
    public void testIndex() {
        final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/"))
                .andExpect(jsonPath("$.status").value(200))         // => OK
                .andExpect(jsonPath("$.message").value("hello"))    // => OK
                .andReturn();

        log.info("{}", result.getResponse().getContentAsString());
            // => {"status":200,"message":"hello"}
            //        ^ INDENT_OUTPUT が効いていない
    }
```

ではどうすればいいのか。
`MockMvc` を作成するときに `StandaloneMockMvcBuilder` を使うと、 `MessageConverter` を指定できるのでそれを利用します。

```java
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
public class MainControllerTest {
    @Autowired
    private MainController mainController;

    private MockMvc mockMvc;

    @Before
    public void setUp() throws Exception {
        final ObjectMapper objectMapper = new ObjectMapper()
                .enable(SerializationFeature.INDENT_OUTPUT);

        mockMvc = MockMvcBuilders.standaloneSetup(mainController)
                .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                .build();
    }

    @Test
    public void testIndex() throws Exception {
        // ......
    }
}
```

サンプルでよくある `MockMvcBuilders` の `webAppContextSetup()` を使うと、
  `DefaultMockMvcBuilder` が利用されてしまいカスタム `MessageConverter` を設定する方法がありません。 (この場合のやり方がわかる方、教えてください)

# おまけ: lombok-jackson を Spring で使う場合

Lombok の `@Value` class のインスタンスを jackson で deserialize する場合、通常であればコンストラクタを自前で実装し、 `@JsonProperty` annotation をコンストラクタ引数に付与していく必要があります。

こんなとき、 [lombok-jackson](https://github.com/xebia/jackson-lombok) を使うと、自動的に上記のように annotation を付与したコンストラクタを生成してくれます。 (くわしくは [lombok-jackson](https://github.com/xebia/jackson-lombok) を参照してください)

ただこれ、利用するときに dependency に追加するだけではだめで、 `ObjectMapper` に一手間くわえる必要があります。(上記プロジェクトのドキュメントから抜粋)

```java
new ObjectMapper()
    .setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());
```

と、いうことで、これも以下のように `MessageConverter` を設定する技を使うと Spring から使うことができます。

```java
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        final ObjectMapper objectMapper = new ObjectMapper()
                .setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());

        converters.add(new MappingJackson2HttpMessageConverter(objectMapper));

        super.configureMessageConverters(converters);
    }
```