Spring Frameworkで未入力のフォームの値をデフォルトで設定されている空文字ではなく、nullで受け取る方法をまとめます。
環境は以下です。
Spring Boot 2.3.1.RELEASE
Thymeleaf 3.0.11.RELEASE
Kotlin 1.3.70
以下のようなフォームと、フォームの値を格納するクラスを用意して説明していきます。
<form method="post" action="/todo" th:object="${todoCreateForm}"> <div class="form-group"> <label th:for="*{title}">Title</label> <input type="text" class="form-control" name="title"> <p class="alert alert-danger" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></p> </div> <div class="form-group"> <label th:for="*{detail}">Detail</label> <textarea class="form-control" name="detail"></textarea> <p class="alert alert-danger" th:if="${#fields.hasErrors('detail')}" th:errors="*{detail}"></p> </div> <button type="submit" class="btn btn-primary"> create</button> </form>
data class TodoCreateForm( @field:NotNull val title: String? = null, @field:NotNull val detail: String? = null )
このときデフォルトの挙動では、未入力の項目は空文字として扱われるため、下の画像のように@NotNull
のバリデーションエラーにならずに、その後の処理が進んでいきます。
今回の要件であれば@NotEmpty
を使うことで問題を解消することはできますが、例えば必須ではない入力項目に対して、@Pattern
を使った形式チェックをしたい場合などでは、常に空文字を許容できる正規表現を書く必要があります。
こういったケースのように未入力のフォーム値を空文字ではなくnullで扱うためには、以下のようにDataBinderに対象の型とPropertyEditorを設定します。
@Controller @RequestMapping("todo") class TodoController( private val todoQueryService: TodoQueryService, private val todoCreateService: TodoCreateService, private val todoDeleteService: TodoDeleteService ) { @InitBinder() fun allowEmptyDataBinding(binder: WebDataBinder) { binder.registerCustomEditor(String::class.java, StringTrimmerEditor(true)) } // 省略 }
今回はStringの値を空文字ではなくnullで扱いたいのでWebDataBinder#registerCustomEditor()
の第一引数にStringを、第二引数にSpringが提供しているStringTrimmerEditor
を引数にtrueを指定して作成したインスタンスを設定します。
StringTrimmerEditorのソースコードは以下のようになっています。(一部省略)
// 省略 public class StringTrimmerEditor extends PropertyEditorSupport { @Nullable private final String charsToDelete; private final boolean emptyAsNull; /** * Create a new StringTrimmerEditor. * @param emptyAsNull {@code true} if an empty String is to be * transformed into {@code null} */ public StringTrimmerEditor(boolean emptyAsNull) { this.charsToDelete = null; this.emptyAsNull = emptyAsNull; } @Override public void setAsText(@Nullable String text) { if (text == null) { setValue(null); } else { String value = text.trim(); if (this.charsToDelete != null) { value = StringUtils.deleteAny(value, this.charsToDelete); } if (this.emptyAsNull && value.isEmpty()) { setValue(null); } else { setValue(value); } } } // 省略 }
StringTrimmerEditorのソースコードは以下のようになっていて、上記でStringTrimmerEditorに渡した引数trueはemptyAsNull
というフィールドの値として処理されます。
emptyAsNullの値にtrueを設定することで、setAsText()
の中の処理で空文字をnullとして設定するようになります。
実際に、サンプルアプリケーションではこれを設定することで、未入力のフォーム値の場合には値にnullが入り、@NotNullのバリデーションエラーによってエラーメッセージが表示されます。
今回はStringTrimmerEditorを使いましたが、Springでは他にもさまざまなPropertyEditorが提供されていて、公式ドキュメントに概要がまとめられているので、以下をみると面白いかもしれません。
参考
StringTrimmerEditor (Spring Framework 5.2.8.RELEASE API)
spring-framework/StringTrimmerEditor.java at master · ndimiduk/spring-framework · GitHub
This entry is released under version 2.0 of the Apache License.