From ff637d3aaad14116d88aca5096d44f5da4821448 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 10 Apr 2015 10:56:34 +0100 Subject: [PATCH] Re-write of path parsing pieces of RelaxedDataBinder Keys with explicit indexes (e.g. foo[bar]) were handled only partially before this change and in quite a crude way. The main new feature (or fixed bug if you prefer) is that map keys with periods were not identified correctly, so foo[bar.spam] came out wrong. By scanning the key for indexes "[]" first, we can split the logic better to make sure that all keys can be explicitly indexed if desired. I believe this would work in YAML as well but it definitely works for properties file inputs. Fixes gh-2387 (I believe) --- .../boot/bind/RelaxedDataBinder.java | 45 ++++++++++++------- .../boot/bind/RelaxedDataBinderTests.java | 15 +++++++ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java index db4afff4a8..60425e5fa3 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java @@ -451,27 +451,40 @@ public class RelaxedDataBinder extends DataBinder { private List splitPath(String path) { List nodes = new ArrayList(); - for (String name : StringUtils.delimitedListToStringArray(path, ".")) { - for (String sub : StringUtils.delimitedListToStringArray(name, "[")) { - if (StringUtils.hasText(sub)) { - if (sub.endsWith("]")) { - sub = sub.substring(0, sub.length() - 1); - if (sub.matches("[0-9]+")) { - nodes.add(new ArrayIndexNode(sub)); - } - else { - nodes.add(new MapIndexNode(sub)); - } - } - else { - nodes.add(new PropertyNode(sub)); - } - } + String current = extractIndexedPaths(path, nodes); + for (String name : StringUtils.delimitedListToStringArray(current, ".")) { + if (StringUtils.hasText(name)) { + nodes.add(new PropertyNode(name)); } } return nodes; } + private String extractIndexedPaths(String path, List nodes) { + int begin = 0; + int startRef = path.indexOf("["); + String current = path; + while (startRef >= 0) { + if (startRef > begin) { + nodes.addAll(splitPath(current.substring(begin, startRef))); + } + int endRef = current.indexOf("]", startRef); + if (endRef > 0) { + String sub = current.substring(startRef + 1, endRef); + if (sub.matches("[0-9]+")) { + nodes.add(new ArrayIndexNode(sub)); + } + else { + nodes.add(new MapIndexNode(sub)); + } + } + begin = endRef + 1; + current = current.substring(begin); + startRef = current.indexOf("["); + } + return current; + } + public void collapseKeys(int index) { List revised = new ArrayList(); for (int i = 0; i < index; i++) { diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java index 8a9d0f578d..f0c05c9aab 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java @@ -301,6 +301,14 @@ public class RelaxedDataBinderTests { assertEquals("123", target.getNested().get("value.foo")); } + @Test + public void testBindNestedMapOfStringReferenced() throws Exception { + TargetWithNestedMapOfString target = new TargetWithNestedMapOfString(); + bind(target, "nested.foo: bar\n" + "nested[value.foo]: 123"); + assertEquals("bar", target.getNested().get("foo")); + assertEquals("123", target.getNested().get("value.foo")); + } + @Test public void testBindNestedMapOfEnum() throws Exception { this.conversionService = new DefaultConversionService(); @@ -317,6 +325,13 @@ public class RelaxedDataBinderTests { assertEquals("123", target.getNested().get("value")); } + @Test + public void testBindNestedMapBracketReferencedAndPeriods() throws Exception { + TargetWithNestedMap target = new TargetWithNestedMap(); + bind(target, "nested[foo]: bar\n" + "nested[foo.value]: 123"); + assertEquals("123", target.getNested().get("foo.value")); + } + @SuppressWarnings("unchecked") @Test public void testBindDoubleNestedMap() throws Exception {