SpringMVCでJSONを受付けた時のvalidationで困っている件

SpringMVCを使っていて、バリューオブジェクトにアノテーションでバリデーションの定義入れて、 @Validだけ仕掛けておけばイイってのは楽チンではイイわーとか思ってたのですが、 FormとJSONとで、ちょっとばかし挙動が違くて困っています。 #ちゃんと解決してから書けばいいのですが、明日になると自分自身、内容を覚えてられるか #微妙なのでブログに書いてしまおうかな、と…w   ■ HTMLのFormを受取る時 - Controller

@RequestMapping(value = "/post", method = RequestMethod.POST)
public String hogePost(
    @Valid Hoge hoge,  // Integer:id, String:nameって感じのバリューオブジェクト
    BindingResult result,
    Locale locale,
    Model model) {
~略~
    if (result.hasErrors()) {
        List<FieldError> errors = result.getFieldErrors();
        for (FieldError error : errors) {
            logger.info(messageSource.getMessage(error, locale));
        }
     }
@NotNull
@Max(100)
private Integer id;
@NotNull
private String name;
  • Bean定義
<beans:bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</beans:bean>

<beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <beans:property name="basename"><beans:value>classpath:messages</beans:value></beans:property>
</beans:bean>
  • エラーメッセージファイル(messages.properties と messages_ja_JP.properties 同内容…)
typeMismatch=Type Mismatch!!
Max=MaxMaxMax!!

みたいな感じの実装と設定にして↓のように Integerのidの項目に"a"とか入れて叩いてやると、 ↓のようにタイプが違いますから~ってエラーメッセージを返してくれます。

INFO : com.shinodogg.hoge.HomeController - Type Mismatch!!

    JSON文字列を受取る時 今回のプロジェクトはクライアントからjQueryとか使って$.ajax的な感じで、 JSONがリクエストされてきます。   Controllerの中であーだこーだするのではなく、HTMLのFormと同じように @Validってチョロっと書いてドヤ顔したいわけです。   ちょこちょこハマったりしつつも、Twitterでたまに絡ませていただいてる @keisuke69さんのブログにお世話になったりしながら、 以下のような実装にしてみました。 - Controller

@RequestMapping(value="/jsonpost", method=RequestMethod.POST, headers="Accept=application/json")
public String hogeJsonPost(
    @RequestBody @Valid Hoge hoge, //JSONのバリデート後にバリューオブジェクトに値詰めてくれる
    Locale locale,
    Model model) {
 
~略~
 
@ExceptionHandler
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleMethodArgumentNotValidException(
        MethodArgumentNotValidException error) {
    logger.info("JSON Validate: " + error.getMessage());

バリデーションエラーが起こった場合は、BindResultがホゲホゲではなくて、 バリデーションエラーExceptionをハンドリングするメソッドを作ってやるのが特徴です。

って事で↓のようなクライアントからJSONをリクエストしてみます。 - jQuery

<html>
<head>
http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js
<script type="text/javascript">
  $(function(){
    $('#hoge').click(
      function(){
        $.ajax({
          type: "POST",
          url: "http://localhost:8080/Hoge/jsonpost",
          dataType: "json",
          data: '{"id": "123", "name": "hoge"}',
          success: function(data){
            alert(data);
          },
          contentType: "application/json"
        });
      });
    });
</script>
</head>
<body>
<a href="javascript: void(0)" id="hoge">hoge</a><br />
</body>
</html>

結果は上記のMax(100)が効いて↓のようなのが取得できましたよ、と。

INFO : com.shinodogg.hoge.HomeController - JSON Validate: Validation failed for argument at index 0 in method: public java.lang.String com.shinodogg.hoge.HomeController.hogeJsonPost(com.shinodogg.hoge.Hoge,java.util.Locale,org.springframework.ui.Model), with 1 error(s): [Field error in object 'hoge' on field 'id': rejected value [123]; codes [Max.hoge.id,Max.id,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [hoge.id,id]; arguments []; default message [id],100]; default message [must be less than or equal to 100]]

いやー、ご丁寧にありがとうございます、素敵なメッセージ、、、なんですが、 上記のmessage.propertiesでは Max=MaxMaxMax!! って定義してるんすよね、、と。   まぁ、イイっす。こいつはもうちょい調べればエラーメッセージをプロパティファイルの内容から 食う事が出来そうな気がしないでもないです。   が、、↓のようにidの項目に数値ではなく文字列を突っ込んでみると、、、

data: '{"id": "a", "name": "hoge"}',

typeMismatch=Type Mismatch!! が出ないのは、まぁ予想通りですが、、 ↓イヤイヤ、、ちょっとコレはキツくないすかね、、と。

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not construct instance of java.lang.Integer from String value 'a': not a valid Integer value
 at [Source: org.apache.catalina.connector.CoyoteInputStream@3008af78; line: 1, column: 2] (through reference chain: com.shinodogg.hoge.Hoge["id"]); nested exception is org.codehaus.jackson.map.JsonMappingException: Can not construct instance of java.lang.Integer from String value 'a': not a valid Integer value
 at [Source: org.apache.catalina.connector.CoyoteInputStream@3008af78; line: 1, column: 2] (through reference chain: com.shinodogg.hoge.Hoge["id"])
    at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readInternal(MappingJacksonHttpMessageConverter.java:127)

確かにJSONのコンバートで失敗するのは分かるのですが、そこをちゃんと拾って、 エラーメッセージを返して欲しかったな、と。MethodArgumentNotValidExceptionで。   まぁ、コレもなんとかなるのかもですが、StackOverFlowを見ながら トライアンドエラーしていくのは、日本人の私にはなかなかシンドイす。 (ま、英語の勉強になってるのでイイかってポジティブに考えてますが…)   ここまでたどり着く間にも、 HttpMediaTypeNotSupportedExceptionとか、 Spring Modules Validation使ったら動かなかったとか、 Jackson(JSONパーサー)の設定がpom.xmlに無かったとか、 知らんがな、、ってのにイロイロぶつかりましたが、 それもまた経験、、、、と。。   明日も継続してコレ調べるので、解決したら追記するなり、別エントリ書くなりいたしやす。