Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

YamlPropertiesFactoryBean incorrect flatten nested map to properties #34285

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
* @author Juergen Hoeller
* @author Sam Brannen
* @author Brian Clozel
* @author Mengqi Xu
* @since 4.1
*/
public abstract class YamlProcessor {
Expand Down Expand Up @@ -244,13 +245,7 @@ private Map<String, Object> asMap(Object object) {
if (value instanceof Map) {
value = asMap(value);
}
if (key instanceof CharSequence) {
result.put(key.toString(), value);
}
else {
// It has to be a map key in this case
result.put("[" + key.toString() + "]", value);
}
result.put(key.toString(), value);
});
return result;
}
Expand Down Expand Up @@ -305,17 +300,20 @@ private boolean process(Map<String, Object> map, MatchCallback callback) {
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
Map<String, Object> result = new LinkedHashMap<>();
buildFlattenedMap(result, source, null);
buildFlattenedMap(result, source, null, false);
return result;
}

@SuppressWarnings({"rawtypes", "unchecked"})
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path) {
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path, boolean isIndexedKey) {
source.forEach((key, value) -> {
if (StringUtils.hasText(path)) {
if (key.startsWith("[")) {
if (isIndexedKey) {
key = path + key;
}
else if (key.startsWith("[") || key.endsWith("]")) {
key = path + '[' + key + ']';
}
else {
key = path + '.' + key;
}
Expand All @@ -325,7 +323,7 @@ private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> s
}
else if (value instanceof Map map) {
// Need a compound key
buildFlattenedMap(result, map, key);
buildFlattenedMap(result, map, key, false);
}
else if (value instanceof Collection collection) {
// Need a compound key
Expand All @@ -336,7 +334,7 @@ else if (value instanceof Collection collection) {
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result, Collections.singletonMap(
"[" + (count++) + "]", object), key);
"[" + (count++) + "]", object), key, true);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,18 @@ void testDuplicateKey() {
this.factory.getObject().get("mymap"));
}

@Test
void testMapWithIntegerKey() {
this.factory.setResources(new ByteArrayResource("foo:\n 1: bar".getBytes()));
Map<String, Object> map = this.factory.getObject();

assertThat(map).hasSize(1);
assertThat(map.containsKey("foo")).isTrue();
Object object = map.get("foo");
assertThat(object).isInstanceOf(LinkedHashMap.class);
@SuppressWarnings("unchecked")
Map<String, Object> sub = (Map<String, Object>) object;
assertThat(sub.containsKey("1")).isTrue();
assertThat(sub.get("1")).isEqualTo("bar");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ void mapConvertedToIndexedBeanReference() {
void integerKeyBehaves() {
setYaml("foo: bar\n1: bar");
this.processor.process((properties, map) -> {
assertThat(properties.get("[1]")).isEqualTo("bar");
assertThat(properties.get("1")).isEqualTo("bar");
assertThat(properties).hasSize(2);
});
}
Expand All @@ -108,7 +108,7 @@ void integerKeyBehaves() {
void integerDeepKeyBehaves() {
setYaml("foo:\n 1: bar");
this.processor.process((properties, map) -> {
assertThat(properties.get("foo[1]")).isEqualTo("bar");
assertThat(properties.get("foo.1")).isEqualTo("bar");
assertThat(properties).hasSize(1);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,42 @@ void loadResourceWithDefaultMatchSkippingMissedMatch() {
assertThat(properties.getProperty("one")).isEqualTo("two");
}


@Test // gh-27020
void loadResourceWithEscapedKey() {
String yaml = "root:\n" +
" webservices:\n" +
" \"[domain.test:8080]\":\n" +
" - username: foo\n" +
" password: bar\n";
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(
new ByteArrayResource(yaml.getBytes()),
new ByteArrayResource("indexed:\n \"[0]\": foo\n \"[1]\": bar".getBytes()),
new ByteArrayResource("indexed2:\n - \"[a]\": foo\n \"[b]\": bar".getBytes()),
new ByteArrayResource("only-left-bracket:\n \"[/key1/\": foo".getBytes()),
new ByteArrayResource("only-right-bracket:\n \"/key1/]\": foo".getBytes()),
new ByteArrayResource("special-bracket:\n \"][/key1/][\": foo".getBytes()),
new ByteArrayResource("number-key:\n 1: foo".getBytes()));

Properties properties = factory.getObject();
assertThat(properties.getProperty("root.webservices[[domain.test:8080]][0].username")).isEqualTo("foo");
assertThat(properties.getProperty("root.webservices[[domain.test:8080]][0].password")).isEqualTo("bar");

assertThat(properties.getProperty("indexed[[0]]")).isEqualTo("foo");
assertThat(properties.getProperty("indexed[[1]]")).isEqualTo("bar");

assertThat(properties.getProperty("indexed2[0][[a]]")).isEqualTo("foo");
assertThat(properties.getProperty("indexed2[0][[b]]")).isEqualTo("bar");

assertThat(properties.getProperty("only-left-bracket[[/key1/]")).isEqualTo("foo");
assertThat(properties.getProperty("only-right-bracket[/key1/]]")).isEqualTo("foo");
assertThat(properties.getProperty("special-bracket.][/key1/][")).isEqualTo("foo");

assertThat(properties.getProperty("number-key.1")).isEqualTo("foo");
}


@Test
void loadNonExistentResource() {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
Expand Down
Loading