From 491f89831a069b2fb2e44a1c9f2612566ca453c4 Mon Sep 17 00:00:00 2001 From: Mengqi Xu <2663479778@qq.com> Date: Sun, 19 Jan 2025 17:55:25 +0800 Subject: [PATCH 1/3] YamlPropertiesFactoryBean incorrect flatten nested map to properties when map key contains escaped brackets. Close gh-27020. Signed-off-by: Mengqi Xu <2663479778@qq.com> --- .../beans/factory/config/YamlProcessor.java | 14 +++++--- .../YamlPropertiesFactoryBeanTests.java | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java index e0d332027f0f..891e1bb937b5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java @@ -56,6 +56,7 @@ * @author Juergen Hoeller * @author Sam Brannen * @author Brian Clozel + * @author Mengqi Xu * @since 4.1 */ public abstract class YamlProcessor { @@ -305,17 +306,20 @@ private boolean process(Map map, MatchCallback callback) { */ protected final Map getFlattenedMap(Map source) { Map result = new LinkedHashMap<>(); - buildFlattenedMap(result, source, null); + buildFlattenedMap(result, source, null, false); return result; } @SuppressWarnings({"rawtypes", "unchecked"}) - private void buildFlattenedMap(Map result, Map source, @Nullable String path) { + private void buildFlattenedMap(Map result, Map 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; } @@ -325,7 +329,7 @@ private void buildFlattenedMap(Map result, Map 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 @@ -336,7 +340,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); } } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java index 218b0d10ee44..294378890a19 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java @@ -165,6 +165,38 @@ 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("indexed:\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())); + + 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("indexed[0][[a]]")).isEqualTo("foo"); + assertThat(properties.getProperty("indexed[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"); + } + + @Test void loadNonExistentResource() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); From b3d3e467e422c50ea772b133a8f9b405b74ea5f2 Mon Sep 17 00:00:00 2001 From: Mengqi Xu <2663479778@qq.com> Date: Sun, 19 Jan 2025 19:04:53 +0800 Subject: [PATCH 2/3] Rename property name. Signed-off-by: Mengqi Xu <2663479778@qq.com> --- .../factory/config/YamlPropertiesFactoryBeanTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java index 294378890a19..8e9d5b19beae 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java @@ -177,7 +177,7 @@ void loadResourceWithEscapedKey() { factory.setResources( new ByteArrayResource(yaml.getBytes()), new ByteArrayResource("indexed:\n \"[0]\": foo\n \"[1]\": bar".getBytes()), - new ByteArrayResource("indexed:\n - \"[a]\": foo\n \"[b]\": 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())); @@ -188,8 +188,9 @@ void loadResourceWithEscapedKey() { assertThat(properties.getProperty("indexed[[0]]")).isEqualTo("foo"); assertThat(properties.getProperty("indexed[[1]]")).isEqualTo("bar"); - assertThat(properties.getProperty("indexed[0][[a]]")).isEqualTo("foo"); - assertThat(properties.getProperty("indexed[0][[b]]")).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"); From 585bbfdb44e2e8177e124cd7837e60c97ce987ca Mon Sep 17 00:00:00 2001 From: Mengqi Xu <2663479778@qq.com> Date: Sun, 19 Jan 2025 20:20:06 +0800 Subject: [PATCH 3/3] Handle the number key like 1: foo. Signed-off-by: Mengqi Xu <2663479778@qq.com> --- .../beans/factory/config/YamlProcessor.java | 8 +------- .../factory/config/YamlMapFactoryBeanTests.java | 14 ++++++++++++++ .../beans/factory/config/YamlProcessorTests.java | 4 ++-- .../config/YamlPropertiesFactoryBeanTests.java | 5 ++++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java index 891e1bb937b5..4a8ac6351ab6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java @@ -245,13 +245,7 @@ private Map 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; } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java index 1f9dcc7709d0..9a545934892b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java @@ -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 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 sub = (Map) object; + assertThat(sub.containsKey("1")).isTrue(); + assertThat(sub.get("1")).isEqualTo("bar"); + } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java index 7a039b11422a..e1fd98831e81 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java @@ -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); }); } @@ -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); }); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java index 8e9d5b19beae..0331fc2ea8b3 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java @@ -180,7 +180,8 @@ void loadResourceWithEscapedKey() { 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("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"); @@ -195,6 +196,8 @@ void loadResourceWithEscapedKey() { 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"); }