【Spring Framework】List<String>型のフォーム値が未入力の場合にnullのリストを受け取る方法

前回のエントリで、Spring FrameworkにおいてString型のフォーム値が未入力の場合に、空文字ではなくnullを受け取る方法を説明しました。

この方法では、List型のフォーム値が未入力の場合は空文字のリストになってしまうので、今回はこれをnullのリストで受け取るための方法を説明します。

環境

  • Spring Boot 2.3.1.RELEASE

  • Thymeleaf 3.0.11.RELEASE

  • Kotlin 1.3.70

  • Java 11.0.7 (azul JDK)

方法

nullのリストを受け取るために、Springが提供しているCustomCollectionEditorというクラスを継承した自作クラスを作成します。 Kotlinでは引数の型の実装がすこし煩わしかったため、このクラスだけJavaで書いて、これをKotlinのクラスから利用するようにしました。

public class ListStringEditor extends CustomCollectionEditor {

    public ListStringEditor(Class<? extends Collection> collectionType) {
        super(collectionType);
    }

    public ListStringEditor(Class<? extends Collection> collectionType, boolean nullAsEmptyCollection) {
        super(collectionType, nullAsEmptyCollection);
    }

    @Override
    protected Object convertElement(Object element) {
        if (element instanceof String) {
            String str = (String) element;
            if (str.isBlank()) {
                return null;
            }
        }

        return element;
    }
}

その後、実装した独自クラスをControllerクラス上でDataBinderにセットします。

@Controller
class SampleController {

    // 省略

    @InitBinder()
    fun allowEmptyDataBinding(binder: WebDataBinder) {
        binder.registerCustomEditor(List::class.java, ListStringEditor(List::class.java))
    }

    // 省略
}

解説

Springが提供しているCustomCollectionEditorは、Collection型のプロパティに対するバインド処理を設定するためのPropertyEditorです。

CutomCollectionEditorの内部では、以下のように、setText()メソッドの中でCollection型の要素一つ一つに対してconvertElement()メソッドを適用しています。

実際にconvertElement()JavaDocを読むと、overrideすることで任意の変換処理を実行できると書いてあるので、今回はこれを利用しました。

// 省略

public class CustomCollectionEditor extends PropertyEditorSupport {

        // 省略

    @Override
    public void setValue(@Nullable Object value) {
        if (value == null && this.nullAsEmptyCollection) {
            super.setValue(createCollection(this.collectionType, 0));
        }
        else if (value == null || (this.collectionType.isInstance(value) && !alwaysCreateNewCollection())) {
            // Use the source value as-is, as it matches the target type.
            super.setValue(value);
        }
        else if (value instanceof Collection) {
            // Convert Collection elements.
            Collection<?> source = (Collection<?>) value;
            Collection<Object> target = createCollection(this.collectionType, source.size());
            for (Object elem : source) {
                target.add(convertElement(elem));
            }
            super.setValue(target);
        }
        else if (value.getClass().isArray()) {
            // Convert array elements to Collection elements.
            int length = Array.getLength(value);
            Collection<Object> target = createCollection(this.collectionType, length);
            for (int i = 0; i < length; i++) {
                target.add(convertElement(Array.get(value, i)));
            }
            super.setValue(target);
        }
        else {
            // A plain value: convert it to a Collection with a single element.
            Collection<Object> target = createCollection(this.collectionType, 1);
            target.add(convertElement(value));
            super.setValue(target);
        }
    }

    // 省略

    protected Object convertElement(Object element) {
        return element;
    }

    // 省略 

}

github.com

参考資料

CustomCollectionEditor (Spring Framework 5.2.7.RELEASE API)

spring-framework/CustomCollectionEditor.java at master · spring-projects/spring-framework · GitHub

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-beans-conversion

This entry is released under version 2.0 of the Apache License.