From d0187579957570ceffcc3717ebfeffcbd69aa90e Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 24 Aug 2017 12:56:19 +0200 Subject: [PATCH] DATACMNS-1144 - @JsonPath now supports multiple path expressions. Path expressions are now evaluated one by one with the first one actually available in the payload being used for value lookup. Also tweaked the default behavior to return null in case of an single, invalid path. --- .../springframework/data/web/JsonPath.java | 7 ++- ...sonProjectingMethodInterceptorFactory.java | 55 +++++++++++++------ ...tingMethodInterceptorFactoryUnitTests.java | 15 +++++ 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/springframework/data/web/JsonPath.java b/src/main/java/org/springframework/data/web/JsonPath.java index 968b17840..6e2bcbc29 100644 --- a/src/main/java/org/springframework/data/web/JsonPath.java +++ b/src/main/java/org/springframework/data/web/JsonPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,9 +34,10 @@ import java.lang.annotation.Target; public @interface JsonPath { /** - * The JSON Path expression to be evaluated when the annotated method is invoked. + * The JSON Path expressions to be evaluated when the annotated method is invoked. If multiple ones are defined, the + * value of the first one actually present in the payload will be returned. * * @return */ - String value(); + String[] value(); } diff --git a/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java b/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java index a122d1f1b..1fd64ead6 100644 --- a/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java +++ b/src/main/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,9 @@ import net.minidev.json.JSONObject; import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -43,6 +45,7 @@ import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.ParseContext; +import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.TypeRef; import com.jayway.jsonpath.spi.mapper.MappingProvider; @@ -135,27 +138,39 @@ public class JsonProjectingMethodInterceptorFactory implements MethodInterceptor Method method = invocation.getMethod(); TypeInformation returnType = ClassTypeInformation.fromReturnTypeOf(method); ResolvableType type = ResolvableType.forMethodReturnType(method); - String jsonPath = getJsonPath(method); - - if (returnType.getRequiredActualType().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)); + Iterable jsonPaths = getJsonPaths(method); - if (isCollectionResult && JsonPath.isPathDefinite(jsonPath)) { - result = (List) result.get(0); + for (String jsonPath : jsonPaths) { + + try { + + if (returnType.getRequiredActualType().getType().isInterface()) { + + List result = context.read(jsonPath); + return result.isEmpty() ? null : result.get(0); + } + + 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); + + } catch (PathNotFoundException o_O) { + // continue with next path + } } - return isCollectionResult ? result : result.isEmpty() ? null : result.get(0); + return null; } /** @@ -164,12 +179,16 @@ public class JsonProjectingMethodInterceptorFactory implements MethodInterceptor * @param method * @return */ - private static String getJsonPath(Method method) { + private static Collection getJsonPaths(Method method) { org.springframework.data.web.JsonPath annotation = AnnotationUtils.findAnnotation(method, org.springframework.data.web.JsonPath.class); - return annotation != null ? annotation.value() : "$.".concat(new Accessor(method).getPropertyName()); + if (annotation != null) { + return Arrays.asList(annotation.value()); + } + + return Collections.singletonList("$.".concat(new Accessor(method).getPropertyName())); } @RequiredArgsConstructor diff --git a/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java b/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java index 910312f6f..627508897 100755 --- a/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/web/JsonProjectingMethodInterceptorFactoryUnitTests.java @@ -136,6 +136,16 @@ public class JsonProjectingMethodInterceptorFactoryUnitTests { assertThat(customer.getNestedCities()).hasSize(2); } + @Test // DATACMNS-1144 + public void returnsNullForNonExistantValue() { + assertThat(customer.getName().getLastname()).isNull(); + } + + @Test // DATACMNS-1144 + public void triesMultipleDeclaredPathsIfNotAvailable() { + assertThat(customer.getName().getSomeName()).isEqualTo(customer.getName().getFirstname()); + } + interface Customer { String getFirstname(); @@ -187,8 +197,13 @@ public class JsonProjectingMethodInterceptorFactoryUnitTests { @JsonPath("$.firstname") String getFirstname(); + // Not available in the payload @JsonPath("$.lastname") String getLastname(); + + // First one not available in the payload + @JsonPath({ "$.lastname", "$.firstname" }) + String getSomeName(); } interface AnotherAddressProjection {