From f3b83f977eaa9e62516ea62a5b53faeadc5acbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJeremy?= Date: Mon, 21 Sep 2015 20:57:20 -0600 Subject: [PATCH] Add basic support for using bracket notation in JSON field paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../docs/asciidoc/documenting-your-api.adoc | 26 +++++++++-- .../restdocs/payload/JsonFieldPath.java | 46 ++++++++++++------- .../restdocs/payload/JsonFieldPathTests.java | 16 +++++++ 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 1bbc2b05..0a07f13e 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -92,8 +92,10 @@ must be compatible with `application/xml`. [[documenting-your-api-request-response-payloads-json-field-paths]] ===== JSON field paths -JSON field paths use `.` to descend into a child object and `[]` to identify an array. For -example, with this JSON payload: +JSON field paths use `.` or bracket notation to descend into a child object and `[]` to +identify an array. Using bracket notation enables the use of `.` within a key name. + +For example, with this JSON payload: [source,json,indent=0] ---- @@ -109,7 +111,8 @@ example, with this JSON payload: { "d":"three" } - ] + ], + "e.dot" : "four" } } ---- @@ -126,6 +129,15 @@ The following paths are all present: |`a.b` |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[]` |An array containing three objects @@ -134,6 +146,14 @@ The following paths are all present: |`a.b[].d` |The string `three` + +|`a['e.dot']` +|The string `four` + +|`['a']['e.dot']` +|The string `four` + + |=== diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java index ea44aaff..e546a115 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java @@ -16,7 +16,7 @@ package org.springframework.restdocs.payload; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -25,10 +25,15 @@ import java.util.regex.Pattern; * A path that identifies a field in a JSON payload * * @author Andy Wilkinson + * @author Jeremy Rickard * */ 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 .compile("\\[([0-9]+|\\*){0,1}\\]"); @@ -76,31 +81,38 @@ final class JsonFieldPath { } private static List extractSegments(String path) { - Matcher matcher = ARRAY_INDEX_PATTERN.matcher(path); - StringBuilder buffer = new StringBuilder(); + Matcher matcher = BRACKETS_AND_ARRAY_PATTERN.matcher(path); + int previous = 0; + + List tokens = new ArrayList<>(); while (matcher.find()) { - appendWithSeparatorIfNecessary(buffer, - path.substring(previous, matcher.start(0))); - appendWithSeparatorIfNecessary(buffer, matcher.group()); + if (previous != matcher.start()) { + tokens.addAll(expandToken(path.substring(previous, matcher.start()))); + } + if (matcher.group(1) != null) { + tokens.add(matcher.group(1)); + } else { + tokens.add(matcher.group()); + } previous = matcher.end(0); } + if (previous < path.length()) { - appendWithSeparatorIfNecessary(buffer, path.substring(previous)); + tokens.addAll(expandToken(path.substring(previous))); } - String processedPath = buffer.toString(); - - return Arrays.asList(processedPath.indexOf('.') > -1 ? processedPath.split("\\.") - : new String[] { processedPath }); + return tokens; } - private static void appendWithSeparatorIfNecessary(StringBuilder buffer, - String toAppend) { - if (buffer.length() > 0 && (buffer.lastIndexOf(".") != buffer.length() - 1) - && !toAppend.startsWith(".")) { - buffer.append("."); + private static List expandToken(String token) { + String[] tokens = token.split("\\."); + List expandedTokens = new ArrayList<>(); + for (String aToken : tokens) { + if (aToken.length() > 0) { + expandedTokens.add(aToken); + } } - buffer.append(toAppend); + return expandedTokens; } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java index f94beee4..f219abce 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java @@ -27,6 +27,7 @@ import org.junit.Test; * Tests for {@link JsonFieldPath} * * @author Andy Wilkinson + * @author Jeremy Rickard */ public class JsonFieldPathTests { @@ -110,4 +111,19 @@ public class JsonFieldPathTests { 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")); + } + }