Fix JSON field type resolution for top-level and nested array fields

Previously, the logic that determined the field’s type would incorrectly
look at the contents of the array. For example, if the array contained
one or more objects, the field’s type would be resolved as Object rather
than Array.

This commit updates JsonFieldPath to record when a path explicitly
identifies an array so that the array itself is used to determine the
field’s type rather than its contents.

Closes gh-292
This commit is contained in:
Andy Wilkinson
2016-08-24 12:29:14 +01:00
parent 4e4d01a459
commit 0f1f84e6a6
8 changed files with 91 additions and 34 deletions

View File

@@ -32,43 +32,59 @@ import static org.junit.Assert.assertTrue;
public class JsonFieldPathTests {
@Test
public void singleFieldIsPrecise() {
assertTrue(JsonFieldPath.compile("a").isPrecise());
public void singleFieldIsPreciseAndNotAnArray() {
JsonFieldPath path = JsonFieldPath.compile("a");
assertTrue(path.isPrecise());
assertFalse(path.isArray());
}
@Test
public void singleNestedFieldIsPrecise() {
assertTrue(JsonFieldPath.compile("a.b").isPrecise());
public void singleNestedFieldIsPreciseAndNotAnArray() {
JsonFieldPath path = JsonFieldPath.compile("a.b");
assertTrue(path.isPrecise());
assertFalse(path.isArray());
}
@Test
public void topLevelArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("[]").isPrecise());
public void topLevelArrayIsPreciseAndAnArray() {
JsonFieldPath path = JsonFieldPath.compile("[]");
assertTrue(path.isPrecise());
assertTrue(path.isArray());
}
@Test
public void fieldBeneathTopLevelArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("[]a").isPrecise());
public void fieldBeneathTopLevelArrayIsNotPreciseAndNotAnArray() {
JsonFieldPath path = JsonFieldPath.compile("[]a");
assertFalse(path.isPrecise());
assertFalse(path.isArray());
}
@Test
public void arrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a[]").isPrecise());
public void arrayIsPreciseAndAnArray() {
JsonFieldPath path = JsonFieldPath.compile("a[]");
assertTrue(path.isPrecise());
assertTrue(path.isArray());
}
@Test
public void nestedArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a.b[]").isPrecise());
public void nestedArrayIsPreciseAndAnArray() {
JsonFieldPath path = JsonFieldPath.compile("a.b[]");
assertTrue(path.isPrecise());
assertTrue(path.isArray());
}
@Test
public void arrayOfArraysIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a[][]").isPrecise());
public void arrayOfArraysIsNotPreciseAndIsAnArray() {
JsonFieldPath path = JsonFieldPath.compile("a[][]");
assertFalse(path.isPrecise());
assertTrue(path.isArray());
}
@Test
public void fieldBeneathAnArrayIsNotPrecise() {
assertFalse(JsonFieldPath.compile("a[].b").isPrecise());
public void fieldBeneathAnArrayIsNotPreciseAndIsNotAnArray() {
JsonFieldPath path = JsonFieldPath.compile("a[].b");
assertFalse(path.isPrecise());
assertFalse(path.isArray());
}
@Test

View File

@@ -17,6 +17,7 @@
package org.springframework.restdocs.payload;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -55,6 +56,17 @@ public class JsonFieldProcessorTests {
equalTo((Object) "bravo"));
}
@Test
public void extractTopLevelArray() {
List<Map<String, Object>> payload = new ArrayList<>();
Map<String, Object> bravo = new HashMap<>();
bravo.put("b", "bravo");
payload.add(bravo);
payload.add(bravo);
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("[]"), payload),
equalTo((Object) payload));
}
@Test
public void extractArray() {
Map<String, Object> payload = new HashMap<>();

View File

@@ -17,6 +17,7 @@
package org.springframework.restdocs.payload;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -45,6 +46,22 @@ public class JsonFieldTypeResolverTests {
assertFieldType(JsonFieldType.ARRAY, "[]");
}
@Test
public void topLevelArray() throws IOException {
assertThat(
this.fieldTypeResolver.resolveFieldType("[]",
new ObjectMapper().readValue("[{\"a\":\"alpha\"}]", List.class)),
equalTo(JsonFieldType.ARRAY));
}
@Test
public void nestedArray() throws IOException {
assertThat(
this.fieldTypeResolver.resolveFieldType("a[]",
createPayload("{\"a\": [{\"b\":\"bravo\"}]}")),
equalTo(JsonFieldType.ARRAY));
}
@Test
public void booleanField() throws IOException {
assertFieldType(JsonFieldType.BOOLEAN, "true");

View File

@@ -67,13 +67,14 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
public void arrayRequestWithFields() throws IOException {
this.snippet.expectRequestFields()
.withContents(tableWithHeader("Path", "Type", "Description")
.row("`[]a.b`", "`Number`", "one")
.row("`[]a.c`", "`String`", "two")
.row("`[]a`", "`Object`", "three"));
.row("`[]`", "`Array`", "one").row("`[]a.b`", "`Number`", "two")
.row("`[]a.c`", "`String`", "three")
.row("`[]a`", "`Object`", "four"));
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"),
fieldWithPath("[]a.c").description("two"),
fieldWithPath("[]a").description("three")))
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one"),
fieldWithPath("[]a.b").description("two"),
fieldWithPath("[]a.c").description("three"),
fieldWithPath("[]a").description("four")))
.document(this.operationBuilder.request("http://localhost")
.content(
"[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]")
@@ -238,7 +239,7 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
.requestFields(fieldWithPath("a.b").description("one"),
fieldWithPath("a.c").description("two"))
.and(fieldWithPath("a").description("three"))
.document(operationBuilder.request("http://localhost")
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@@ -252,7 +253,7 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
PayloadDocumentation.requestFields(fieldWithPath("a").description("one"))
.andWithPrefix("a.", fieldWithPath("b").description("two"),
fieldWithPath("c").description("three"))
.document(operationBuilder.request("http://localhost")
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@@ -265,7 +266,7 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
new RequestFieldsSnippet(Arrays.asList(
fieldWithPath("Foo|Bar").type("one|two").description("three|four")))
.document(operationBuilder.request("http://localhost")
.document(this.operationBuilder.request("http://localhost")
.content("{\"Foo|Bar\": 5}").build());
}

View File

@@ -54,7 +54,7 @@ public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
.withContents(tableWithHeader("Path", "Type", "Description")
.row("`id`", "`Number`", "one").row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Object`", "four")
.row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"),
@@ -90,7 +90,7 @@ public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
public void arrayResponse() throws IOException {
this.snippet.expectResponseFields()
.withContents(tableWithHeader("Path", "Type", "Description").row("`[]`",
"`String`", "one"));
"`Array`", "one"));
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one")))
.document(this.operationBuilder.response()
.content("[\"a\", \"b\", \"c\"]").build());
@@ -291,7 +291,7 @@ public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
.withContents(tableWithHeader("Path", "Type", "Description")
.row("`id`", "`Number`", "one").row("`date`", "`String`", "two")
.row("`assets`", "`Array`", "three")
.row("`assets[]`", "`Object`", "four")
.row("`assets[]`", "`Array`", "four")
.row("`assets[].id`", "`Number`", "five")
.row("`assets[].name`", "`String`", "six"));
PayloadDocumentation