diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 2d00655f4c..3433af095b 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -1154,23 +1154,36 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { @SuppressWarnings("unchecked") @Nullable private V createIndexedValue( - String paramPath, Class paramType, ResolvableType elementType, + String paramPath, Class containerType, ResolvableType elementType, String indexedPath, ValueResolver valueResolver) { Object value = null; Class elementClass = elementType.resolve(Object.class); - Object rawValue = valueResolver.resolveValue(indexedPath, elementClass); - if (rawValue != null) { - try { - value = convertIfNecessary(rawValue, elementClass); - } - catch (TypeMismatchException ex) { - handleTypeMismatchException(ex, paramPath, paramType, rawValue); - } + + if (List.class.isAssignableFrom(elementClass)) { + value = createList(indexedPath, elementClass, elementType, valueResolver); + } + else if (Map.class.isAssignableFrom(elementClass)) { + value = createMap(indexedPath, elementClass, elementType, valueResolver); + } + else if (elementClass.isArray()) { + value = createArray(indexedPath, elementClass, elementType, valueResolver); } else { - value = createObject(elementType, indexedPath + ".", valueResolver); + Object rawValue = valueResolver.resolveValue(indexedPath, elementClass); + if (rawValue != null) { + try { + value = convertIfNecessary(rawValue, elementClass); + } + catch (TypeMismatchException ex) { + handleTypeMismatchException(ex, paramPath, containerType, rawValue); + } + } + else { + value = createObject(elementType, indexedPath + ".", valueResolver); + } } + return (V) value; } diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java index 8bae5d4c4d..4c81ca65cc 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java @@ -221,6 +221,34 @@ class DataBinderConstructTests { assertThat(target.integerArray()).containsExactly(1, 2); } + @Test + void nestedListWithinMap() { + MapValueResolver valueResolver = new MapValueResolver(Map.of( + "integerListMap[a][0]", "1", "integerListMap[a][1]", "2", + "integerListMap[b][0]", "3", "integerListMap[b][1]", "4")); + + DataBinder binder = initDataBinder(IntegerListMapRecord.class); + binder.construct(valueResolver); + + IntegerListMapRecord target = getTarget(binder); + assertThat(target.integerListMap().get("a")).containsExactly(1, 2); + assertThat(target.integerListMap().get("b")).containsExactly(3, 4); + } + + @Test + void nestedMapWithinList() { + MapValueResolver valueResolver = new MapValueResolver(Map.of( + "integerMapList[0][a]", "1", "integerMapList[0][b]", "2", + "integerMapList[1][a]", "3", "integerMapList[1][b]", "4")); + + DataBinder binder = initDataBinder(IntegerMapListRecord.class); + binder.construct(valueResolver); + + IntegerMapListRecord target = getTarget(binder); + assertThat(target.integerMapList().get(0)).containsOnly(Map.entry("a", 1), Map.entry("b", 2)); + assertThat(target.integerMapList().get(1)).containsOnly(Map.entry("a", 3), Map.entry("b", 4)); + } + @SuppressWarnings("SameParameterValue") private static DataBinder initDataBinder(Class targetType) { @@ -317,6 +345,14 @@ class DataBinderConstructTests { } + private record IntegerMapListRecord(List> integerMapList) { + } + + + private record IntegerListMapRecord(Map> integerListMap) { + } + + private record MapValueResolver(Map map) implements DataBinder.ValueResolver { @Override