Skip to content

Commit 3505c4b

Browse files
committed
Ensure DataBinder can bind constructor with a Map with simple values
This change ensures that DataBinder can bind constructor with a Map parameter that has no nested properties, but just simple values like a String: `someMap[0]=exampleString`. Integration tests have been added to cover similar cases that use the ServletRequestDataBinder. Closes gh-34043
1 parent 58670db commit 3505c4b

File tree

2 files changed

+140
-2
lines changed

2 files changed

+140
-2
lines changed

spring-context/src/main/java/org/springframework/validation/DataBinder.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ private <V> Map<String, V> createMap(
10801080
}
10811081
int startIdx = paramPath.length() + 1;
10821082
int endIdx = name.indexOf(']', startIdx);
1083-
String nestedPath = name.substring(0, endIdx + 2);
1083+
String nestedPath = ((name.length() > endIdx + 1) ? name.substring(0, endIdx + 2) : "");
10841084
boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\'');
10851085
String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx));
10861086
if (map == null) {
@@ -1114,7 +1114,7 @@ private static SortedSet<Integer> getIndexes(String paramPath, ValueResolver val
11141114
SortedSet<Integer> indexes = null;
11151115
for (String name : valueResolver.getNames()) {
11161116
if (name.startsWith(paramPath + "[")) {
1117-
int endIndex = name.indexOf(']', paramPath.length() + 2);
1117+
int endIndex = name.indexOf(']', paramPath.length() + 1);
11181118
String rawIndex = name.substring(paramPath.length() + 1, endIndex);
11191119
int index = Integer.parseInt(rawIndex);
11201120
indexes = (indexes != null ? indexes : new TreeSet<>());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.web.servlet.samples.spr;
18+
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import org.junit.jupiter.api.Disabled;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
26+
import org.springframework.test.web.servlet.MockMvc;
27+
import org.springframework.web.bind.annotation.ModelAttribute;
28+
import org.springframework.web.bind.annotation.PostMapping;
29+
import org.springframework.web.bind.annotation.RestController;
30+
import org.springframework.web.context.WebApplicationContext;
31+
32+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
33+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
34+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
35+
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
36+
37+
@SpringJUnitWebConfig(ServletRequestDataBinderIntegrationTests.SpringWebKeyValueController.class)
38+
class ServletRequestDataBinderIntegrationTests {
39+
40+
@Test // gh-34043
41+
void postMap(WebApplicationContext wac) throws Exception {
42+
MockMvc mockMvc = webAppContextSetup(wac).build();
43+
mockMvc.perform(post("/map")
44+
.param("someMap[a]", "valueA")
45+
.param("someMap[b]", "valueB"))
46+
.andExpect(status().isOk())
47+
.andExpect(content().string("valueB"));
48+
}
49+
50+
@Test
51+
void postArray(WebApplicationContext wac) throws Exception {
52+
MockMvc mockMvc = webAppContextSetup(wac).build();
53+
mockMvc.perform(post("/array")
54+
.param("someArray[0]", "valueA")
55+
.param("someArray[1]", "valueB"))
56+
.andExpect(status().isOk())
57+
.andExpect(content().string("valueB"));
58+
}
59+
60+
@Disabled("see gh-34121")
61+
@Test // gh-34121
62+
void postArrayWithEmptyIndex(WebApplicationContext wac) throws Exception {
63+
MockMvc mockMvc = webAppContextSetup(wac).build();
64+
mockMvc.perform(post("/array")
65+
.param("someArray[]", "valueA")
66+
.param("someArray[]", "valueB"))
67+
.andExpect(status().isOk())
68+
.andExpect(content().string("valueB"));
69+
}
70+
71+
@Test
72+
void postArrayWithoutIndex(WebApplicationContext wac) throws Exception {
73+
MockMvc mockMvc = webAppContextSetup(wac).build();
74+
mockMvc.perform(post("/array")
75+
.param("someArray", "valueA")
76+
.param("someArray", "valueB"))
77+
.andExpect(status().isOk())
78+
.andExpect(content().string("valueB"));
79+
}
80+
81+
@Test
82+
void postList(WebApplicationContext wac) throws Exception {
83+
MockMvc mockMvc = webAppContextSetup(wac).build();
84+
mockMvc.perform(post("/list")
85+
.param("someList[0]", "valueA")
86+
.param("someList[1]", "valueB"))
87+
.andExpect(status().isOk())
88+
.andExpect(content().string("valueB"));
89+
}
90+
91+
@Disabled("see gh-34121")
92+
@Test // gh-34121
93+
void postListWithEmptyIndex(WebApplicationContext wac) throws Exception {
94+
MockMvc mockMvc = webAppContextSetup(wac).build();
95+
mockMvc.perform(post("/list")
96+
.param("someList[]", "valueA")
97+
.param("someList[]", "valueB"))
98+
.andExpect(status().isOk())
99+
.andExpect(content().string("valueB"));
100+
}
101+
102+
@Test
103+
void postListWithoutIndex(WebApplicationContext wac) throws Exception {
104+
MockMvc mockMvc = webAppContextSetup(wac).build();
105+
mockMvc.perform(post("/list")
106+
.param("someList", "valueA")
107+
.param("someList", "valueB"))
108+
.andExpect(status().isOk())
109+
.andExpect(content().string("valueB"));
110+
}
111+
112+
record PayloadWithMap(Map<String, String> someMap) {}
113+
114+
record PayloadWithArray(String[] someArray) {}
115+
116+
record PayloadWithList(List<String> someList) {}
117+
118+
@RestController
119+
@SuppressWarnings("unused")
120+
static class SpringWebKeyValueController {
121+
122+
@PostMapping("/map")
123+
String postMap(@ModelAttribute("payload") PayloadWithMap payload) {
124+
return payload.someMap.get("b");
125+
}
126+
127+
@PostMapping("/array")
128+
String postArray(@ModelAttribute("payload") PayloadWithArray payload) {
129+
return payload.someArray[1];
130+
}
131+
132+
@PostMapping("/list")
133+
String postList(@ModelAttribute("payload") PayloadWithList payload) {
134+
return payload.someList.get(1);
135+
}
136+
}
137+
138+
}

0 commit comments

Comments
 (0)