Skip to content

Commit 77f5875

Browse files
koenpuntbclozel
authored andcommitted
Recursively instantiate input arguments
Prior to this commit, the `GraphQlArgumentInstantiator` would not instantiate and bind properly types with a list attribute that has elements which need to be instantiated with a primary constructor. This commit ensures that every list that is encountered is recursively passed to the instantiator, instead of trying to convert the list using the databinder. This also fixes gh-145, by directly accessing the arguments from the arguments map, instead of the mapped propertyvalues used for the DataBinder. See gh-147
1 parent 552c4f7 commit 77f5875

File tree

2 files changed

+38
-3
lines changed

2 files changed

+38
-3
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiator.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.graphql.data.method.annotation.support;
1818

1919
import java.lang.reflect.Constructor;
20+
import java.util.Collection;
2021
import java.util.HashMap;
2122
import java.util.List;
2223
import java.util.Map;
@@ -25,7 +26,10 @@
2526

2627
import org.springframework.beans.BeanUtils;
2728
import org.springframework.beans.MutablePropertyValues;
29+
import org.springframework.beans.PropertyValues;
30+
import org.springframework.core.CollectionFactory;
2831
import org.springframework.core.MethodParameter;
32+
import org.springframework.core.convert.TypeDescriptor;
2933
import org.springframework.validation.DataBinder;
3034

3135
/**
@@ -52,9 +56,9 @@ class GraphQlArgumentInstantiator {
5256
public <T> T instantiate(Class<T> targetType, Map<String, Object> arguments) {
5357
Object target;
5458
Constructor<?> ctor = BeanUtils.getResolvableConstructor(targetType);
55-
MutablePropertyValues propertyValues = extractPropertyValues(arguments);
5659

5760
if (ctor.getParameterCount() == 0) {
61+
MutablePropertyValues propertyValues = extractPropertyValues(arguments);
5862
target = BeanUtils.instantiateClass(ctor);
5963
DataBinder dataBinder = new DataBinder(target);
6064
dataBinder.bind(propertyValues);
@@ -67,12 +71,20 @@ public <T> T instantiate(Class<T> targetType, Map<String, Object> arguments) {
6771
Object[] args = new Object[paramTypes.length];
6872
for (int i = 0; i < paramNames.length; i++) {
6973
String paramName = paramNames[i];
70-
Object value = propertyValues.get(paramName);
71-
value = (value instanceof List ? ((List<?>) value).toArray() : value);
74+
Object value = arguments.get(paramName);
7275
MethodParameter methodParam = new MethodParameter(ctor, i);
7376
if (value == null && methodParam.isOptional()) {
7477
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
7578
}
79+
else if (value != null && CollectionFactory.isApproximableCollectionType(value.getClass())) {
80+
Collection<Object> rawCollection = (Collection<Object>) value;
81+
Collection<Object> values = CollectionFactory.createApproximateCollection(value, rawCollection.size());
82+
83+
TypeDescriptor typeDescriptor = new TypeDescriptor(methodParam);
84+
Class<?> elementType = typeDescriptor.getElementTypeDescriptor().getType();
85+
rawCollection.forEach(item -> values.add(this.instantiate(elementType, (Map<String, Object>)item)));
86+
args[i] = values;
87+
}
7688
else {
7789
args[i] = binder.convertIfNecessary(value, paramTypes[i], methodParam);
7890
}

spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiatorTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ void shouldInstantiateNestedBeanLists() throws Exception {
9393
assertThat(result.getItems()).hasSize(2).extracting("name").containsExactly("first", "second");
9494
}
9595

96+
@Test
97+
void shouldInstantiatePrimaryConstructorNestedBeanLists() throws Exception {
98+
String payload = "{\"nestedList\": { \"items\": [ {\"name\": \"first\"}, {\"name\": \"second\"}] } }";
99+
DataFetchingEnvironment environment = initEnvironment(payload);
100+
PrimaryConstructorNestedList result = instantiator.instantiate(PrimaryConstructorNestedList.class, environment.getArgument("nestedList"));
101+
102+
assertThat(result).isNotNull().isInstanceOf(PrimaryConstructorNestedList.class);
103+
assertThat(result.getItems()).hasSize(2).extracting("name").containsExactly("first", "second");
104+
}
105+
96106
private DataFetchingEnvironment initEnvironment(String jsonPayload) throws JsonProcessingException {
97107
Map<String, Object> arguments = this.mapper.readValue(jsonPayload, new TypeReference<Map<String, Object>>() {
98108
});
@@ -147,6 +157,19 @@ public void setItems(List<Item> items) {
147157
}
148158
}
149159

160+
static class PrimaryConstructorNestedList {
161+
162+
final List<Item> items;
163+
164+
public PrimaryConstructorNestedList(List<Item> items) {
165+
this.items = items;
166+
}
167+
168+
public List<Item> getItems() {
169+
return items;
170+
}
171+
}
172+
150173
static class Item {
151174

152175
String name;

0 commit comments

Comments
 (0)