spring-data
spring-data copied to clipboard
Wrong Wrapping of Lists in Queries Returning Arrays
@Query("RETURN [1 , 2]")
List<Long> getOneTwo();
Throws an Exception:
org.springframework.data.mapping.MappingException: Can't convert result to type java.util.List!
...
I changed the return type a bit to find the actual issue and here it is:
@Query("RETURN [1 , 2]")
Object getOneTwo();
Throws no Exception and the actual result type is the correct ArrayList<Long>
@Query("RETURN [1 , 2]")
List<Object> getOneTwo();
Throws no Exception and the actual result type is ArrayList<ArrayList<Long>>
So it seems Spring Data tries to wrap the whole object in a list when the return type is a list. This Issue also occurs when using functions that return arrays like FLATTEN, UNION, CONCAT and so on
The provided AQL query actually returns:
[
[
1,
2
]
]
therefore it is mapped to ArrayList<ArrayList<Long>>
.
To return a flattened array that could be mapped to List<Long>
, the query should be:
FOR i IN [1, 2] RETURN i
which returns:
[
1,
2
]
Ohh my bad, I guess the confusion is that AQL always wraps a return in an array. Still weird that if Object is used as the return type the second list vanishes. That was what led me to this being a bug.
Thank you anyways :)
I tested this some more because it was still bugging me. This is definitely a bug in arango spring data. See the following:
// AQL returns: [1]
// Java returns: 1
@Query("RETURN LENGTH([1])")
Long getsExtracted1();
// AQL returns: [[1]]
// Java returns: List<Long>
@Query("RETURN [1]")
Object getsExtracted2();
// AQL returns: [[1]]
// Java returns: List<List<Long>>
@Query("RETURN [1]")
List<Object> doesntGetExtracted();
// AQL returns: [[1]]
// Java throws: Method threw 'org.springframework.data.mapping.MappingException' exception: Can't convert result to type java.util.List!
@Query("RETURN [1]")
List<Long> throwsException1();
// AQL returns: [[1]]
// Java throws: Method threw 'org.springframework.data.mapping.MappingException' exception: Can't convert result to type java.util.List!
@Query("RETURN [1]")
List<List<Long>> throwsException2();
// AQL returns: [[1]]
// Java throws: Method threw 'org.springframework.data.mapping.MappingException' exception: Can't convert result to type java.util.List!
@Query("RETURN [1]")
List<List<List<Long>>> throwsException3();
// AQL returns: [1]
// Java returns: List<Long>
@Query("FOR i IN [1] RETURN i")
List<Long> doesntGetExtracted();
My findings:
- AQL will always wrap results in an array
- Spring Data is smart enough to remove that array in most cases (see first example)
- When using generics (Long in this example) inside the List Spring Data has some issue
- using the
FOR i in [] RETURN i
to remove the second array is enough so that spring data again can find the correct type - Spring Data should always remove one array when using
RETURN
and no array when usingFOR x IN
Thanks for clarifying, this seems indeed a bug.
Investigating this further, I found that wrapped types returned by query methods are always recursively unwrapped by Spring Data in QueryExecutionConverters.unwrapWrapperTypes()
, see https://github.com/spring-projects/spring-data-commons/blob/3.2.x/src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java#L238-L264
This method unwraps the type until it finds a type assignable to the repository domain type.
For example, this means that in case the return type of your query method is List<List<List<Long>>>
then the deserialization target type will be Long
.
The reason for this, is that returning nested arrays/lists from a query method is not a use case supported by Spring Data. You can find comments about it here: https://github.com/spring-projects/spring-data-commons/issues/2509#issuecomment-990706777
Return type inspection is subject to unwrap to the individual object type and so far we do not have the use-case of returning nested arrays/lists from a query method. Collection-like types are always assumed to represent a collection query.
To work around this, you can make your AQL queries return arrays of json objects (instead of arrays of arrays), create a class matching the structure of the returned data and set it as return type, for example:
@Query("RETURN {\"result\": [1]}")
List<FooResult> foo();
// ...
@Data
public class FooResult {
private List<Long> result;
}
Closing as not a bug. Feel free to reopen in case of further questions.