From 762a12b4a298737910be24b82af23cc45b232e69 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 18 Jul 2016 12:32:59 +0200 Subject: [PATCH] DATACMNS-885 - Workaround for bug in Jayway's JSONPath array access. When a recursive decent operator is used in a JSON Path expression there's no way to get out of the array mode again to indicate one is interested in a particular element (e.g. "the first one no matter where in the document") [0]. We now work around this by always letting the parser return lists, so that the mapping library finally kicking in can be equipped with the correct target type to create. Unfortunately this causes arrays to be double-wrapped for definite paths so that we basically have to adapt the type handed to Jackson in another round to the unwrap the mapping result in turn [1]. [0] https://github.com/jayway/JsonPath/issues/248 [1] https://github.com/jayway/JsonPath/issues/249 --- ...sonProjectingMethodInterceptorFactory.java | 24 +++++++++++++++++-- ...tingMethodInterceptorFactoryUnitTests.java | 16 +++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java b/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java index 58a43328f..64bf85db3 100644 --- a/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java +++ b/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java @@ -22,6 +22,8 @@ import net.minidev.json.JSONObject; import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; import java.util.Map; import org.aopalliance.intercept.MethodInterceptor; @@ -38,6 +40,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; import com.jayway.jsonpath.ParseContext; import com.jayway.jsonpath.TypeRef; import com.jayway.jsonpath.spi.mapper.MappingProvider; @@ -63,6 +66,7 @@ public class JsonProjectingMethodInterceptorFactory implements MethodInterceptor Assert.notNull(mappingProvider, "MappingProvider must not be null!"); Configuration build = Configuration.builder()// + .options(Option.ALWAYS_RETURN_LIST)// .mappingProvider(mappingProvider)// .build(); @@ -131,8 +135,24 @@ public class JsonProjectingMethodInterceptorFactory implements MethodInterceptor ResolvableType type = ResolvableType.forMethodReturnType(method); String jsonPath = getJsonPath(method); - return !returnType.getActualType().getType().isInterface() ? context.read(jsonPath, new ResolvableTypeRef(type)) - : context.read(jsonPath); + if (returnType.getActualType().getType().isInterface()) { + + List result = context.read(jsonPath); + return result.isEmpty() ? null : result.get(0); + } + + boolean isCollectionResult = Collection.class.isAssignableFrom(type.getRawClass()); + type = isCollectionResult ? type : ResolvableType.forClassWithGenerics(List.class, type); + type = isCollectionResult && JsonPath.isPathDefinite(jsonPath) + ? ResolvableType.forClassWithGenerics(List.class, type) : type; + + List result = (List) context.read(jsonPath, new ResolvableTypeRef(type)); + + if (isCollectionResult && JsonPath.isPathDefinite(jsonPath)) { + result = (List) result.get(0); + } + + return isCollectionResult ? result : result.isEmpty() ? null : result.get(0); } /** diff --git a/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java b/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java index 6ab4a7f17..26f21cdf7 100644 --- a/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java @@ -163,6 +163,16 @@ public class JsonProjectingMethodInterceptorFactoryUnitTests { assertThat(name.getFirstname(), is("Dave")); } + /** + * @see DATACMNS-885 + */ + @Test + public void accessNestedFields() { + + assertThat(customer.getNestedCity(), is("Dresden")); + assertThat(customer.getNestedCities(), hasSize(2)); + } + interface Customer { String getFirstname(); @@ -194,6 +204,12 @@ public class JsonProjectingMethodInterceptorFactoryUnitTests { @JsonPath("$.address") Set getAnotherAddressProjectionAsCollection(); + + @JsonPath("$..city") + String getNestedCity(); + + @JsonPath("$..city") + List getNestedCities(); } interface AddressProjection {