25
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-01-21

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 には触れません)

@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 が利用されるので、

{"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 します。

@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)

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

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

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

JUnit テストでの設定

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

    @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 を指定できるのでそれを利用します。

@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 {
        // ......
    }
}

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

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

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

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

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

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

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

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

        converters.add(new MappingJackson2HttpMessageConverter(objectMapper));

        super.configureMessageConverters(converters);
    }
  1. このインデント幅とかコロンの前後とかキモいなぁ、もっとカスタマイズできないかなぁと思っていましたが、 こちらの Tips を利用するとカスタマイズできるそうです。

25
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?