Skip to content

Commit 5a28745

Browse files
author
Dave Syer
committed
Handle unresolved property names with underscore
When the configuration property name is not resolved against the target bean's property descriptors and the property name is delimited by `underscore` instead of `dot`, then `RelaxedDataBinder` doesn't bind those properties to the target if the property is of type Map. With thanks to @ilayaperumalg for the original PR. Updated by @dsyer to recognize that the property is of type Map, rather than blindly trying all combinations of property paths. Fixes spring-projectsgh-3836
1 parent d46cf8e commit 5a28745

2 files changed

Lines changed: 54 additions & 21 deletions

File tree

spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ public RelaxedDataBinder(Object target) {
8383
* @param namePrefix An optional prefix to be used when reading properties
8484
*/
8585
public RelaxedDataBinder(Object target, String namePrefix) {
86-
super(wrapTarget(target), (StringUtils.hasLength(namePrefix) ? namePrefix
87-
: DEFAULT_OBJECT_NAME));
86+
super(wrapTarget(target),
87+
(StringUtils.hasLength(namePrefix) ? namePrefix : DEFAULT_OBJECT_NAME));
8888
this.namePrefix = cleanNamePrefix(namePrefix);
8989
}
9090

@@ -146,7 +146,8 @@ private MutablePropertyValues modifyProperties(MutablePropertyValues propertyVal
146146
propertyValues = addMapPrefix(propertyValues);
147147
}
148148
BeanWrapper wrapper = new BeanWrapperImpl(target);
149-
wrapper.setConversionService(new RelaxedConversionService(getConversionService()));
149+
wrapper.setConversionService(
150+
new RelaxedConversionService(getConversionService()));
150151
wrapper.setAutoGrowNestedPaths(true);
151152
List<PropertyValue> sortedValues = new ArrayList<PropertyValue>();
152153
Set<String> modifiedNames = new HashSet<String>();
@@ -214,16 +215,17 @@ private MutablePropertyValues getPropertyValuesForNamePrefix(
214215
name = name.substring(candidate.length());
215216
if (!(this.ignoreNestedProperties && name.contains("."))) {
216217
PropertyOrigin propertyOrigin = findPropertyOrigin(value);
217-
rtn.addPropertyValue(new OriginCapablePropertyValue(name, value
218-
.getValue(), propertyOrigin));
218+
rtn.addPropertyValue(new OriginCapablePropertyValue(name,
219+
value.getValue(), propertyOrigin));
219220
}
220221
}
221222
}
222223
}
223224
return rtn;
224225
}
225226

226-
private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
227+
private PropertyValue modifyProperty(BeanWrapper target,
228+
PropertyValue propertyValue) {
227229
String name = propertyValue.getName();
228230
String normalizedName = normalizePath(target, name);
229231
if (!normalizedName.equals(name)) {
@@ -277,8 +279,8 @@ public void setPropertyValue(PropertyValue pv) throws BeansException {
277279
}
278280
}
279281
};
280-
beanWrapper.setConversionService(new RelaxedConversionService(
281-
getConversionService()));
282+
beanWrapper.setConversionService(
283+
new RelaxedConversionService(getConversionService()));
282284
beanWrapper.registerCustomEditor(InetAddress.class,
283285
new InetAddressEditor());
284286
return beanWrapper;
@@ -352,8 +354,8 @@ private boolean isMapValueStringType(TypeDescriptor descriptor) {
352354

353355
@SuppressWarnings("rawtypes")
354356
private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key) {
355-
Object value = (wrapper.isReadableProperty(propertyName) ? wrapper
356-
.getPropertyValue(propertyName) : null);
357+
Object value = (wrapper.isReadableProperty(propertyName)
358+
? wrapper.getPropertyValue(propertyName) : null);
357359
if (value instanceof Map) {
358360
if (((Map) value).get(key) == BLANK) {
359361
return true;
@@ -362,7 +364,8 @@ private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key)
362364
return false;
363365
}
364366

365-
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path, int index) {
367+
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path,
368+
int index) {
366369
String name = path.prefix(index);
367370
TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name)
368371
.getElementTypeDescriptor();
@@ -425,6 +428,12 @@ private String resolveNestedPropertyName(BeanWrapper target, String prefix,
425428
candidate.append(field);
426429
String nested = resolvePropertyName(target, prefix, candidate.toString());
427430
if (nested != null) {
431+
Class<?> type = target.getPropertyType(nested);
432+
if (type != null && Map.class.isAssignableFrom(type)) {
433+
// Special case for map property (gh-3836). Maybe could be fixed
434+
// in spring-beans)?
435+
return nested + "[" + name.substring(candidate.length() + 1) + "]";
436+
}
428437
String propertyName = resolvePropertyName(target,
429438
joinString(prefix, nested),
430439
name.substring(candidate.length() + 1));

spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616

1717
package org.springframework.boot.bind;
1818

19+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
20+
import static org.hamcrest.Matchers.equalTo;
21+
import static org.hamcrest.Matchers.nullValue;
22+
import static org.junit.Assert.assertEquals;
23+
import static org.junit.Assert.assertFalse;
24+
import static org.junit.Assert.assertNotNull;
25+
import static org.junit.Assert.assertNull;
26+
import static org.junit.Assert.assertThat;
27+
1928
import java.lang.annotation.Documented;
2029
import java.lang.annotation.ElementType;
2130
import java.lang.annotation.Retention;
@@ -51,15 +60,6 @@
5160
import org.springframework.validation.FieldError;
5261
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
5362

54-
import static java.lang.annotation.RetentionPolicy.RUNTIME;
55-
import static org.hamcrest.Matchers.equalTo;
56-
import static org.hamcrest.Matchers.nullValue;
57-
import static org.junit.Assert.assertEquals;
58-
import static org.junit.Assert.assertFalse;
59-
import static org.junit.Assert.assertNotNull;
60-
import static org.junit.Assert.assertNull;
61-
import static org.junit.Assert.assertThat;
62-
6363
/**
6464
* Tests for {@link RelaxedDataBinder}.
6565
*
@@ -323,6 +323,14 @@ public void testBindNestedMap() throws Exception {
323323
assertEquals("123", target.getNested().get("value"));
324324
}
325325

326+
@Test
327+
public void testBindNestedMapPropsWithUnderscores() throws Exception {
328+
TargetWithNestedMap target = new TargetWithNestedMap();
329+
bind(target, "nested_foo: bar\n" + "nested_value: 123");
330+
assertEquals("123", target.getNested().get("value"));
331+
assertEquals("bar", target.getNested().get("foo"));
332+
}
333+
326334
@Test
327335
public void testBindNestedUntypedMap() throws Exception {
328336
TargetWithNestedUntypedMap target = new TargetWithNestedUntypedMap();
@@ -338,6 +346,22 @@ public void testBindNestedMapOfString() throws Exception {
338346
assertEquals("123", target.getNested().get("value.foo"));
339347
}
340348

349+
@Test
350+
public void testBindNestedMapOfStringWithUnderscore() throws Exception {
351+
TargetWithNestedMapOfString target = new TargetWithNestedMapOfString();
352+
bind(target, "nested_foo: bar\n" + "nested_value_foo: 123");
353+
assertEquals("bar", target.getNested().get("foo"));
354+
assertEquals("123", target.getNested().get("value_foo"));
355+
}
356+
357+
@Test
358+
public void testBindNestedMapOfStringWithUnderscoreAndupperCase() throws Exception {
359+
TargetWithNestedMapOfString target = new TargetWithNestedMapOfString();
360+
bind(target, "NESTED_FOO: bar\n" + "NESTED_VALUE_FOO: 123");
361+
assertEquals("bar", target.getNested().get("FOO"));
362+
assertEquals("123", target.getNested().get("VALUE_FOO"));
363+
}
364+
341365
@Test
342366
public void testBindNestedMapOfStringReferenced() throws Exception {
343367
TargetWithNestedMapOfString target = new TargetWithNestedMapOfString();
@@ -686,7 +710,7 @@ private BindingResult bind(DataBinder binder, Object target, String values)
686710
}
687711

688712
public static class RequiredKeysValidator implements
689-
ConstraintValidator<RequiredKeys, Map<String, Object>> {
713+
ConstraintValidator<RequiredKeys, Map<String, Object>> {
690714

691715
private String[] fields;
692716

0 commit comments

Comments
 (0)