diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiator.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiator.java index b4dd364c..8ff82027 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiator.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiator.java @@ -17,6 +17,7 @@ package org.springframework.graphql.data.method.annotation.support; import java.lang.reflect.Constructor; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,7 +26,10 @@ import java.util.Stack; import org.springframework.beans.BeanUtils; import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValues; +import org.springframework.core.CollectionFactory; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.validation.DataBinder; /** @@ -52,9 +56,9 @@ class GraphQlArgumentInstantiator { public T instantiate(Class targetType, Map arguments) { Object target; Constructor ctor = BeanUtils.getResolvableConstructor(targetType); - MutablePropertyValues propertyValues = extractPropertyValues(arguments); if (ctor.getParameterCount() == 0) { + MutablePropertyValues propertyValues = extractPropertyValues(arguments); target = BeanUtils.instantiateClass(ctor); DataBinder dataBinder = new DataBinder(target); dataBinder.bind(propertyValues); @@ -67,12 +71,20 @@ class GraphQlArgumentInstantiator { Object[] args = new Object[paramTypes.length]; for (int i = 0; i < paramNames.length; i++) { String paramName = paramNames[i]; - Object value = propertyValues.get(paramName); - value = (value instanceof List ? ((List) value).toArray() : value); + Object value = arguments.get(paramName); MethodParameter methodParam = new MethodParameter(ctor, i); if (value == null && methodParam.isOptional()) { args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null); } + else if (value != null && CollectionFactory.isApproximableCollectionType(value.getClass())) { + Collection rawCollection = (Collection) value; + Collection values = CollectionFactory.createApproximateCollection(value, rawCollection.size()); + + TypeDescriptor typeDescriptor = new TypeDescriptor(methodParam); + Class elementType = typeDescriptor.getElementTypeDescriptor().getType(); + rawCollection.forEach(item -> values.add(this.instantiate(elementType, (Map)item))); + args[i] = values; + } else { args[i] = binder.convertIfNecessary(value, paramTypes[i], methodParam); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiatorTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiatorTests.java index 25393089..2519f686 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiatorTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInstantiatorTests.java @@ -93,6 +93,16 @@ class GraphQlArgumentInstantiatorTests { assertThat(result.getItems()).hasSize(2).extracting("name").containsExactly("first", "second"); } + @Test + void shouldInstantiatePrimaryConstructorNestedBeanLists() throws Exception { + String payload = "{\"nestedList\": { \"items\": [ {\"name\": \"first\"}, {\"name\": \"second\"}] } }"; + DataFetchingEnvironment environment = initEnvironment(payload); + PrimaryConstructorNestedList result = instantiator.instantiate(PrimaryConstructorNestedList.class, environment.getArgument("nestedList")); + + assertThat(result).isNotNull().isInstanceOf(PrimaryConstructorNestedList.class); + assertThat(result.getItems()).hasSize(2).extracting("name").containsExactly("first", "second"); + } + private DataFetchingEnvironment initEnvironment(String jsonPayload) throws JsonProcessingException { Map arguments = this.mapper.readValue(jsonPayload, new TypeReference>() { }); @@ -147,6 +157,19 @@ class GraphQlArgumentInstantiatorTests { } } + static class PrimaryConstructorNestedList { + + final List items; + + public PrimaryConstructorNestedList(List items) { + this.items = items; + } + + public List getItems() { + return items; + } + } + static class Item { String name;