YamlMapFactoryBean appears to have a limitation in overriding complex values (Map with another nested map in it). When creating a new project this issue can be not apparent, but after I downloaded sources in IntelliJ Idea I started to get this limitation.
Code example with tests.
@SpringBootTest
public class FileMergerTest {
@Test
public void shouldOverrideComplexValueWithEmptyMap() {
String valueA = "someKey:\n someChild:\n oneMoreKey: value";
String valueB = "someKey:\n someChild: {}";
Map<String, Object> mergedValues = mergeTwoYamls(valueA, valueB);
// Expected True but actually False
Assertions.assertEquals( "{someKey={someChild={}}}", mergedValues.toString());
}
@Test
public void shouldOverrideValuesWithEmptyMap() {
String valueA = "someKey:\n someChild: value";
String valueB = "someKey:\n someChild: {}";
Map<String, Object> mergedValues = mergeTwoYamls(valueA, valueB);
Assertions.assertEquals( "{someKey={someChild={}}}", mergedValues.toString());
}
@Test
public void shouldOverrideComplexValueWithNotEmptyComplexValue() {
String valueA = "someKey:\n someChild:\n oneMoreKey: value";
String valueB = "someKey:\n someChild:\n oneMoreKey: value2";
Map<String, Object> mergedValues = mergeTwoYamls(valueA, valueB);
Assertions.assertEquals( "{someKey={someChild={oneMoreKey=value2}}}", mergedValues.toString());
}
@Test
public void shouldOverrideComplexValueWithEmptyArray() {
String valueA = "someKey:\n someChild:\n oneMoreKey: value";
String valueB = "someKey:\n someChild: []";
Map<String, Object> mergedValues = mergeTwoYamls(valueA, valueB);
Assertions.assertEquals( "{someKey={someChild=[]}}", mergedValues.toString());
}
@Test
public void shouldOverrideComplexValueWithEmptyString() {
String valueA = "someKey:\n someChild:\n oneMoreKey: value";
String valueB = "someKey:\n someChild: ''";
Map<String, Object> mergedValues = mergeTwoYamls(valueA, valueB);
Assertions.assertEquals( "{someKey={someChild=}}", mergedValues.toString());
}
private Map<String, Object> mergeTwoYamls(String valueA, String valueB) {
YamlMapFactoryBean factoryBean = new YamlMapFactoryBean();
factoryBean.setResolutionMethod(YamlProcessor.ResolutionMethod.OVERRIDE_AND_IGNORE);
factoryBean.setSingleton(false);
factoryBean.setResources(
new ByteArrayResource(valueA.getBytes()),
new ByteArrayResource(valueB.getBytes()));
return factoryBean.getObject();
}
}
For the first time, I spotted this bug in spring-boot-starter-parent 2.7.6 with the version spring-beans 5.3.24. I tried the latest version of Spring Boot (3.0.5) in case this has been fixed, but faced the same bug.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
</properties>
Comment From: simonbasle
First, please note that the setResolutionMethod
doesn't change the way nested maps are merged but rather how the YamlProcessor
(parent class of YamlMapFactoryBean
) deals with parsing multiple resources / multiple YAML files and whether or not it was able to load Documents from each resource.
YamlMapFactoryBean
processes each resource sequentially, turning it into a result Map
. If there is a second file with overlapping entries (i.e with keys already present in the result Map
), it means on the second pass the value will get replaced (a simple Map#put
).
However, as you've seen when dealing with nested maps / dictionaries YamlMapFactoryBean
does attempt to merge. This only occurs if both sides are Map
s.
This might be opinionated, but I would be hesitant to call this a bug. YamlMapFactoryBean
heavily builds upon YamlProcessor
so if you strongly disagree with this way of merging, it shouldn't be too difficult to take inspiration from it and build your own factory bean with a merge method better adapted to your expectations.