From 77f5875eac2b568e3c9919f98c88c9b9a5e05911 Mon Sep 17 00:00:00 2001 From: Koen Punt Date: Tue, 21 Sep 2021 11:08:22 +0200 Subject: [PATCH] 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 --- .../support/GraphQlArgumentInstantiator.java | 18 ++++++++++++--- .../GraphQlArgumentInstantiatorTests.java | 23 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) 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;