diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java index 06912741538..28f95109b3d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java @@ -27,6 +27,7 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource; import org.springframework.core.CollectionFactory; import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.util.ClassUtils; /** @@ -57,6 +58,10 @@ class MapBinder extends AggregateBinder> { Bindable resolvedTarget = resolveTarget(target); for (ConfigurationPropertySource source : getContext().getSources()) { if (!ConfigurationPropertyName.EMPTY.equals(name)) { + Object converted = convertIfFound(name, source, resolvedTarget); + if (converted != null) { + return converted; + } source = source.filter(name::isAncestorOf); } new EntryBinder(name, resolvedTarget, elementBinder).bindEntries(source, map); @@ -72,6 +77,20 @@ class MapBinder extends AggregateBinder> { return target; } + private Object convertIfFound(ConfigurationPropertyName name, ConfigurationPropertySource source, Bindable target) { + ConfigurationProperty configurationProperty = source.getConfigurationProperty(name); + if (configurationProperty != null) { + try { + return ResolvableTypeDescriptor.forType(target.getType()) + .convert(getContext().getConversionService(), configurationProperty.getValue()); + } + catch (ConverterNotFoundException ex) { + + } + } + return null; + } + @Override protected Map merge(Map existing, Map additional) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java index be7c39de7bb..b3f125d87b8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java @@ -38,8 +38,11 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import org.springframework.core.ResolvableType; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.StandardEnvironment; import org.springframework.test.context.support.TestPropertySourceUtils; +import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -524,6 +527,29 @@ public class MapBinderTests { assertThat(foo.get().getFoos().get("foo2").getValue()).isEqualTo("three"); } + @Test + public void bindToMapWithCustomConverter() { + DefaultConversionService conversionService = new DefaultConversionService(); + conversionService.addConverter(new MapConverter()); + Binder binder = new Binder(this.sources, null, conversionService); + + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo", "a,b"); + this.sources.add(source); + Map map = binder.bind("foo", STRING_STRING_MAP).get(); + assertThat(map.get("a")).isNotNull(); + assertThat(map.get("b")).isNotNull(); + } + + @Test + public void bindToMapWithNoConverterForValue() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo", "a,b"); + this.sources.add(source); + BindResult> result = this.binder.bind("foo", STRING_STRING_MAP); + assertThat(result.isBound()).isFalse(); + } + private Bindable> getMapBindable(Class keyGeneric, ResolvableType valueType) { ResolvableType keyType = ResolvableType.forClass(keyGeneric); @@ -576,4 +602,13 @@ public class MapBinderTests { } + static class MapConverter implements Converter> { + @Override + public Map convert(String s) { + Map map = new HashMap<>(); + StringUtils.commaDelimitedListToSet(s).forEach(k -> map.put(k, "")); + return map; + } + } + }