Browse Source

Allow YAML processors to create a flattened map with nulls included

Add an additional `getFlattenedMap` method to `YamlProcessor` to allow
the resulting flattened map to include nulls.

This update will allow processor subclasses to tell the difference
between YAML that is defined with an empty object vs missing the key
entirely:

e.g.:

  application:
    name: test
    optional: {}

vs

  application:
    name: test
    optional: {}

Closes gh-36197

Signed-off-by: Phillip Webb <phil.webb@broadcom.com>
pull/36204/head
Phillip Webb 1 week ago committed by Juergen Hoeller
parent
commit
0c9f40b171
  1. 27
      spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java
  2. 28
      spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java

27
spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java

@ -304,13 +304,32 @@ public abstract class YamlProcessor { @@ -304,13 +304,32 @@ public abstract class YamlProcessor {
* @since 4.1.3
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
return getFlattenedMap(source, false);
}
/**
* Return a flattened version of the given map, recursively following any nested Map
* or Collection values. Entries from the resulting map retain the same order as the
* source. When called with the Map from a {@link MatchCallback} the result will
* contain the same values as the {@link MatchCallback} Properties.
* @param source the source map
* @param includeNulls if {@code null} entries can be included in the result
* @return a flattened map
* @since 7.0.4
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source, boolean includeNulls) {
Map<String, Object> result = new LinkedHashMap<>();
buildFlattenedMap(result, source, null);
buildFlattenedMap(result, source, null, includeNulls);
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 includeNulls) {
if (includeNulls && source.isEmpty()) {
result.put(path, null);
return;
}
source.forEach((key, value) -> {
if (StringUtils.hasText(path)) {
if (key.startsWith("[")) {
@ -325,7 +344,7 @@ public abstract class YamlProcessor { @@ -325,7 +344,7 @@ public abstract class YamlProcessor {
}
else if (value instanceof Map map) {
// Need a compound key
buildFlattenedMap(result, map, key);
buildFlattenedMap(result, map, key, includeNulls);
}
else if (value instanceof Collection collection) {
// Need a compound key
@ -336,7 +355,7 @@ public abstract class YamlProcessor { @@ -336,7 +355,7 @@ public abstract class YamlProcessor {
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result, Collections.singletonMap(
"[" + (count++) + "]", object), key);
"[" + (count++) + "]", object), key, includeNulls);
}
}
}

28
spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java

@ -44,8 +44,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.set; @@ -44,8 +44,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.set;
*/
class YamlProcessorTests {
private final YamlProcessor processor = new YamlProcessor() {
};
private final TestYamlProcessor processor = new TestYamlProcessor();
@Test
@ -182,8 +181,33 @@ class YamlProcessorTests { @@ -182,8 +181,33 @@ class YamlProcessorTests {
.withMessageContaining("Global tag is not allowed: tag:yaml.org,2002:java.net.URL");
}
@Test
void processAndFlattenWithoutIncludedNulls() {
setYaml("foo: bar\nbar:\n spam: {}");
Map<String, Object> flattened = this.processor.processAndFlatten(false);
assertThat(flattened).containsEntry("foo", "bar").doesNotContainKey("bar.spam").hasSize(1);
}
@Test
void processAndFlattenWithIncludedNulls() {
setYaml("foo: bar\nbar:\n spam: {}");
Map<String, Object> flattened = this.processor.processAndFlatten(true);
assertThat(flattened).containsEntry("foo", "bar").containsEntry("bar.spam", null).hasSize(2);
}
private void setYaml(String yaml) {
this.processor.setResources(new ByteArrayResource(yaml.getBytes()));
}
private static class TestYamlProcessor extends YamlProcessor {
Map<String, Object> processAndFlatten(boolean includeNulls) {
Map<String, Object> flattened = new LinkedHashMap<>();
process((properties, map) -> flattened.putAll(getFlattenedMap(map, includeNulls)));
return flattened;
}
}
}

Loading…
Cancel
Save