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.
This commit is contained in:
Oliver Gierke
2017-08-24 12:56:19 +02:00
parent ce7a2e8ea5
commit d018757995
3 changed files with 56 additions and 21 deletions

View File

@@ -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();
}

View File

@@ -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<Object> 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<String> 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<String> 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

View File

@@ -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 {