Add basic support for using bracket notation in JSON field paths

Previously, only dot notation JSON field paths were supported. Using
dot notation, each key is separated by a '.'. This makes it impossible
to reference a field where the key itself contains a dot.

This commit adds basic bracket notation support to JsonFieldPath.
With this commit, existing functionality remains as is, but users can
now use a basic form of JsonPath bracket notation. This enables the
use of Json keys with embedded dots. i.e. something like :

{
	"a.b" : "one"
}

.andDo(document("example",responseFields(fieldWithPath("['a.b']")…

Closes gh-132
This commit is contained in:
“Jeremy
2015-09-21 20:57:20 -06:00
committed by Andy Wilkinson
parent 5cd25897ce
commit f3b83f977e
3 changed files with 68 additions and 20 deletions

View File

@@ -92,8 +92,10 @@ must be compatible with `application/xml`.
[[documenting-your-api-request-response-payloads-json-field-paths]] [[documenting-your-api-request-response-payloads-json-field-paths]]
===== JSON field paths ===== JSON field paths
JSON field paths use `.` to descend into a child object and `[]` to identify an array. For JSON field paths use `.` or bracket notation to descend into a child object and `[]` to
example, with this JSON payload: identify an array. Using bracket notation enables the use of `.` within a key name.
For example, with this JSON payload:
[source,json,indent=0] [source,json,indent=0]
---- ----
@@ -109,7 +111,8 @@ example, with this JSON payload:
{ {
"d":"three" "d":"three"
} }
] ],
"e.dot" : "four"
} }
} }
---- ----
@@ -126,6 +129,15 @@ The following paths are all present:
|`a.b` |`a.b`
|An array containing three objects |An array containing three objects
|`['a']['b']`
|An array containing three objects
|`a['b']`
|An array containing three objects
|`['a'].b`
|An array containing three objects
|`a.b[]` |`a.b[]`
|An array containing three objects |An array containing three objects
@@ -134,6 +146,14 @@ The following paths are all present:
|`a.b[].d` |`a.b[].d`
|The string `three` |The string `three`
|`a['e.dot']`
|The string `four`
|`['a']['e.dot']`
|The string `four`
|=== |===

View File

@@ -16,7 +16,7 @@
package org.springframework.restdocs.payload; package org.springframework.restdocs.payload;
import java.util.Arrays; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -25,10 +25,15 @@ import java.util.regex.Pattern;
* A path that identifies a field in a JSON payload * A path that identifies a field in a JSON payload
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Jeremy Rickard
* *
*/ */
final class JsonFieldPath { final class JsonFieldPath {
private static final Pattern BRACKETS_AND_ARRAY_PATTERN = Pattern
.compile("\\[\'(.+?)\'\\]|\\[([0-9]+|\\*){0,1}\\]");
private static final Pattern ARRAY_INDEX_PATTERN = Pattern private static final Pattern ARRAY_INDEX_PATTERN = Pattern
.compile("\\[([0-9]+|\\*){0,1}\\]"); .compile("\\[([0-9]+|\\*){0,1}\\]");
@@ -76,31 +81,38 @@ final class JsonFieldPath {
} }
private static List<String> extractSegments(String path) { private static List<String> extractSegments(String path) {
Matcher matcher = ARRAY_INDEX_PATTERN.matcher(path); Matcher matcher = BRACKETS_AND_ARRAY_PATTERN.matcher(path);
StringBuilder buffer = new StringBuilder();
int previous = 0; int previous = 0;
List<String> tokens = new ArrayList<>();
while (matcher.find()) { while (matcher.find()) {
appendWithSeparatorIfNecessary(buffer, if (previous != matcher.start()) {
path.substring(previous, matcher.start(0))); tokens.addAll(expandToken(path.substring(previous, matcher.start())));
appendWithSeparatorIfNecessary(buffer, matcher.group()); }
if (matcher.group(1) != null) {
tokens.add(matcher.group(1));
} else {
tokens.add(matcher.group());
}
previous = matcher.end(0); previous = matcher.end(0);
} }
if (previous < path.length()) { if (previous < path.length()) {
appendWithSeparatorIfNecessary(buffer, path.substring(previous)); tokens.addAll(expandToken(path.substring(previous)));
} }
String processedPath = buffer.toString(); return tokens;
return Arrays.asList(processedPath.indexOf('.') > -1 ? processedPath.split("\\.")
: new String[] { processedPath });
} }
private static void appendWithSeparatorIfNecessary(StringBuilder buffer, private static List<String> expandToken(String token) {
String toAppend) { String[] tokens = token.split("\\.");
if (buffer.length() > 0 && (buffer.lastIndexOf(".") != buffer.length() - 1) List<String> expandedTokens = new ArrayList<>();
&& !toAppend.startsWith(".")) { for (String aToken : tokens) {
buffer.append("."); if (aToken.length() > 0) {
expandedTokens.add(aToken);
}
} }
buffer.append(toAppend); return expandedTokens;
} }
} }

View File

@@ -27,6 +27,7 @@ import org.junit.Test;
* Tests for {@link JsonFieldPath} * Tests for {@link JsonFieldPath}
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Jeremy Rickard
*/ */
public class JsonFieldPathTests { public class JsonFieldPathTests {
@@ -110,4 +111,19 @@ public class JsonFieldPathTests {
contains("[]", "a", "b", "c")); contains("[]", "a", "b", "c"));
} }
@Test
public void compilationOfMultipleElementPathWithBrackets() {
assertThat(JsonFieldPath.compile("['a']['b']['c']").getSegments(), contains("a", "b", "c"));
}
@Test
public void compilationOfMultipleElementPathWithAndWithoutBrackets() {
assertThat(JsonFieldPath.compile("['a'][].b['c']").getSegments(), contains("a", "[]", "b", "c"));
}
@Test
public void compilationOfMultipleElementPathWithAndWithoutBracketsAndEmbeddedDots() {
assertThat(JsonFieldPath.compile("['a.key'][].b['c']").getSegments(), contains("a.key", "[]", "b", "c"));
}
} }