Skip to content

Commit 75d67ce

Browse files
committed
Correctly unwrap nested JSON array projections.
We now check for double-nesting of JSON path evaluation results when the return type is a collection. If the result is double-wrapped and the nested object is a collection then we unwrap it. Closes #2270
1 parent a9c881d commit 75d67ce

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* {@link MethodInterceptorFactory} to create a {@link MethodInterceptor} that will
5353
*
5454
* @author Oliver Gierke
55+
* @author Mark Paluch
5556
* @soundtrack Jeff Coffin - Fruitcake (The Inside Of The Outside)
5657
* @since 1.13
5758
*/
@@ -152,7 +153,9 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
152153
if (returnType.getRequiredActualType().getType().isInterface()) {
153154

154155
List<?> result = context.read(jsonPath);
155-
return result.isEmpty() ? null : result.get(0);
156+
Object nested = result.isEmpty() ? null : result.get(0);
157+
158+
return isCollectionResult && !(nested instanceof Collection) ? result : nested;
156159
}
157160

158161
type = isCollectionResult && JsonPath.isPathDefinite(jsonPath)

src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java

+39-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import lombok.NoArgsConstructor;
2323

2424
import java.io.ByteArrayInputStream;
25+
import java.nio.charset.StandardCharsets;
2526
import java.util.List;
2627
import java.util.Set;
2728

@@ -38,6 +39,7 @@
3839
* Unit tests for {@link JsonProjectingMethodInterceptorFactory}.
3940
*
4041
* @author Oliver Gierke
42+
* @author Mark Paluch
4143
* @since 1.13
4244
* @soundtrack Richard Spaven - Assemble (Whole Other*)
4345
*/
@@ -51,7 +53,8 @@ void setUp() {
5153

5254
String json = "{\"firstname\" : \"Dave\", "//
5355
+ "\"address\" : { \"zipCode\" : \"01097\", \"city\" : \"Dresden\" }," //
54-
+ "\"addresses\" : [ { \"zipCode\" : \"01097\", \"city\" : \"Dresden\" }]" + " }";
56+
+ "\"addresses\" : [ { \"zipCode\" : \"01097\", \"city\" : \"Dresden\" }, { \"zipCode\" : \"69469\", \"city\" : \"Weinheim\" }]"
57+
+ " }";
5558

5659
SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
5760

@@ -97,6 +100,13 @@ void accessPropertyOnNestedProjection() {
97100
assertThat(customer.getAddressProjections().get(0).getZipCode()).isEqualTo("01097");
98101
}
99102

103+
@Test // gh-2270
104+
void nestedProjectionCollectionShouldContainMultipleElements() {
105+
assertThat(customer.getAddressProjections()).hasSize(2);
106+
assertThat(customer.getAddressProjections().get(0).getZipCode()).isEqualTo("01097");
107+
assertThat(customer.getAddressProjections().get(1).getZipCode()).isEqualTo("69469");
108+
}
109+
100110
@Test // DATCMNS-885
101111
void accessPropertyThatUsesJsonPathProjectionInTurn() {
102112
assertThat(customer.getAnotherAddressProjection().getZipCodeButNotCity()).isEqualTo("01097");
@@ -107,7 +117,7 @@ void accessCollectionPropertyThatUsesJsonPathProjectionInTurn() {
107117

108118
List<AnotherAddressProjection> projections = customer.getAnotherAddressProjections();
109119

110-
assertThat(projections).hasSize(1);
120+
assertThat(projections).hasSize(2);
111121
assertThat(projections.get(0).getZipCodeButNotCity()).isEqualTo("01097");
112122
}
113123

@@ -133,7 +143,7 @@ void accessNestedPropertyButStayOnRootLevel() {
133143
void accessNestedFields() {
134144

135145
assertThat(customer.getNestedCity()).isEqualTo("Dresden");
136-
assertThat(customer.getNestedCities()).hasSize(2);
146+
assertThat(customer.getNestedCities()).hasSize(3);
137147
}
138148

139149
@Test // DATACMNS-1144
@@ -146,6 +156,18 @@ void triesMultipleDeclaredPathsIfNotAvailable() {
146156
assertThat(customer.getName().getSomeName()).isEqualTo(customer.getName().getFirstname());
147157
}
148158

159+
@Test // gh-2270
160+
void shouldProjectOnArray() {
161+
162+
String json = "[ { \"creationDate\": 1610111331413, \"changeDate\": 1610111332160, \"person\": { \"caption\": \"Test2 TEST2\", \"firstName\": \"Test2\", \"lastName\": \"Test2\" } }, "
163+
+ "{ \"creationDate\": 1609775450502, \"changeDate\": 1609775451333, \"person\": { \"caption\": \"Test TEST\", \"firstName\": \"Test\", \"lastName\": \"Test\" } }]";
164+
165+
UserPayload projection = projectionFactory.createProjection(UserPayload.class,
166+
new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
167+
168+
assertThat(projection.users()).hasSize(2);
169+
}
170+
149171
interface Customer {
150172

151173
String getFirstname();
@@ -218,4 +240,18 @@ interface AnotherAddressProjection {
218240
static class Address {
219241
private String zipCode, city;
220242
}
243+
244+
@ProjectedPayload
245+
interface UserPayload {
246+
247+
@JsonPath("$..person")
248+
List<Users> users();
249+
250+
interface Users {
251+
252+
public String getFirstName();
253+
254+
public String getLastName();
255+
}
256+
}
221257
}

0 commit comments

Comments
 (0)