Skip to content

Commit 7b0d80c

Browse files
committed
YAML brackets escaping keys are doubled in Properties spring-projectsgh-27020
1 parent 3bcf7bc commit 7b0d80c

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java

+35-8
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,38 @@ private void handleProcessError(Resource resource, IOException ex) {
296296
}
297297
}
298298

299+
/**
300+
* Customize how the {@code Object} keys in the raw Map from the YAML parser
301+
* are turned into {@code String} keys in a {@code Map<String, Object>},
302+
* as a preparation step to the flattening and conversion to a
303+
* {@code Properties} instance.
304+
* <p>The default implementation performs the following sanitization:
305+
* <ul>
306+
* <li>{@code String} keys escaped with brackets in the YAML have brackets
307+
* doubled. These will be used as full keys, dot-separated, in the Properties.</li>
308+
* <li>non-string keys (eg. integer keys) are escaped with single brackets.
309+
* These will be used as indexes (no dot separator) in the Properties.</li>
310+
* </ul>
311+
*
312+
* @param rawKey the raw key as parsed by the YAML parser
313+
* @return the sanitized key in {@code String} form
314+
*/
315+
protected String sanitizeKey(Object rawKey) {
316+
if (rawKey instanceof CharSequence csKey) {
317+
String key = csKey.toString();
318+
//detecting keys escaped in brackets, turning to double brackets in properties
319+
if (csKey.length() > 0 && csKey.charAt(0) == '['
320+
&& csKey.charAt(csKey.length()-1) == ']') {
321+
key = key.replace("[", "[[").replace("]", "]]");
322+
}
323+
return key;
324+
}
325+
else {
326+
// It has to be a map key in this case
327+
return "[" + rawKey + "]";
328+
}
329+
}
330+
299331
@SuppressWarnings({ "unchecked", "rawtypes" })
300332
private Map<String, Object> asMap(Object object) {
301333
// YAML can have numbers as keys
@@ -310,13 +342,8 @@ private Map<String, Object> asMap(Object object) {
310342
if (value instanceof Map) {
311343
value = asMap(value);
312344
}
313-
if (key instanceof CharSequence) {
314-
result.put(key.toString(), value);
315-
}
316-
else {
317-
// It has to be a map key in this case
318-
result.put("[" + key.toString() + "]", value);
319-
}
345+
String sanitizedKey = sanitizeKey(key);
346+
result.put(sanitizedKey, value);
320347
});
321348
return result;
322349
}
@@ -379,7 +406,7 @@ protected final Map<String, Object> getFlattenedMap(Map<String, Object> source)
379406
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path) {
380407
source.forEach((key, value) -> {
381408
if (StringUtils.hasText(path)) {
382-
if (key.startsWith("[")) {
409+
if (key.startsWith("[") && !key.startsWith("[[")) {
383410
key = path + key;
384411
}
385412
else {

Diff for: spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java

+34
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,40 @@ void arrayWithNestedListsNotIncludingFullLists() {
176176
});
177177
}
178178

179+
// gh-27020
180+
@Test
181+
void mapWithBracketInKey() {
182+
setYaml("""
183+
webservices:
184+
"[domain.test]":
185+
- username: me
186+
password: myPassword
187+
- username: alsoMe
188+
password: myOtherPassword
189+
""");
190+
this.processor.setIncludeSimpleLists(true);
191+
192+
this.processor.process((properties, map) -> {
193+
assertThat(properties)
194+
//the webservices.[[domain.test]] key is not added because it is a list of maps
195+
.doesNotContainKeys("webservices", "webservices.[[domain.test]]")
196+
.containsOnlyKeys(
197+
"webservices.[[domain.test]][0].username",
198+
"webservices.[[domain.test]][0].password",
199+
"webservices.[[domain.test]][1].username",
200+
"webservices.[[domain.test]][1].password");
201+
202+
assertThat(properties.getProperty("webservices.[[domain.test]][0].username"))
203+
.as("first username").isEqualTo("me");
204+
assertThat(properties.getProperty("webservices.[[domain.test]][0].password"))
205+
.as("first password").isEqualTo("myPassword");
206+
assertThat(properties.getProperty("webservices.[[domain.test]][1].username"))
207+
.as("second username").isEqualTo("alsoMe");
208+
assertThat(properties.getProperty("webservices.[[domain.test]][1].password"))
209+
.as("second password").isEqualTo("myOtherPassword");
210+
});
211+
}
212+
179213
@Test
180214
void stringResource() {
181215
setYaml("foo # a document that is a literal");

0 commit comments

Comments
 (0)