From f568bce8a5b1055c7f2a1117f7e5ac0210be0ca7 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 9 Mar 2022 21:24:31 +0000 Subject: [PATCH] Correctly bind nested empty list Closes gh-301 --- .../data/GraphQlArgumentInitializer.java | 18 +++++++---- .../GraphQlArgumentInitializerTests.java | 32 +++++++++++++------ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentInitializer.java b/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentInitializer.java index 75e46e0b..5cec4cd9 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentInitializer.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentInitializer.java @@ -217,11 +217,18 @@ public class GraphQlArgumentInitializer { Object value = arguments.get(key); if (value instanceof List) { List items = (List) value; - Map subValues = new HashMap<>(items.size()); - for (int i = 0; i < items.size(); i++) { - subValues.put(key + "[" + i + "]", items.get(i)); + if (items.isEmpty()) { + path.push(key); + mpvs.add(pathToPropertyName(path), value); + path.pop(); + } + else { + Map subValues = new HashMap<>(items.size()); + for (int i = 0; i < items.size(); i++) { + subValues.put(key + "[" + i + "]", items.get(i)); + } + visitArgumentMap(subValues, mpvs, path); } - visitArgumentMap(subValues, mpvs, path); } else if (value instanceof Map) { path.push(key); @@ -232,8 +239,7 @@ public class GraphQlArgumentInitializer { } else { path.push(key); - String propertyName = pathToPropertyName(path); - mpvs.add(propertyName, value); + mpvs.add(pathToPropertyName(path), value); path.pop(); } } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInitializerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInitializerTests.java index 960f73c1..3792a3af 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInitializerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/GraphQlArgumentInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2022 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. @@ -20,7 +20,6 @@ import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; @@ -40,9 +39,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; */ class GraphQlArgumentInitializerTests { - private ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = new ObjectMapper(); - private GraphQlArgumentInitializer initializer = new GraphQlArgumentInitializer(null); + private final GraphQlArgumentInitializer initializer = new GraphQlArgumentInitializer(null); @Test @@ -71,10 +70,13 @@ class GraphQlArgumentInitializerTests { void shouldFailIfNoPrimaryConstructor() throws Exception { String payload = "{\"noPrimary\": { \"name\": \"test\"} }"; DataFetchingEnvironment environment = initEnvironment(payload); - assertThatThrownBy(() -> { - ResolvableType targetType = ResolvableType.forClass(NoPrimaryConstructor.class); - initializer.initializeArgument(environment, "noPrimary", targetType); - }).isInstanceOf(IllegalStateException.class).hasMessageContaining("No primary or single unique constructor found"); + assertThatThrownBy( + () -> { + ResolvableType targetType = ResolvableType.forClass(NoPrimaryConstructor.class); + initializer.initializeArgument(environment, "noPrimary", targetType); + }) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("No primary or single unique constructor found"); } @Test @@ -101,6 +103,16 @@ class GraphQlArgumentInitializerTests { assertThat(((NestedList) result).getItems()).hasSize(2).extracting("name").containsExactly("first", "second"); } + @Test // gh-301 + void shouldInstantiateNestedBeanListsEmpty() throws Exception { + String payload = "{\"nestedList\": { \"items\": [] } }"; + Object result = initializer.initializeArgument( + initEnvironment(payload), "nestedList", ResolvableType.forClass(NestedList.class)); + + assertThat(result).isNotNull().isInstanceOf(NestedList.class); + assertThat(((NestedList) result).getItems()).hasSize(0); + } + @Test void shouldInstantiatePrimaryConstructorNestedBeanLists() throws Exception { String payload = "{\"nestedList\": { \"items\": [ {\"name\": \"first\"}, {\"name\": \"second\"}] } }"; @@ -125,9 +137,9 @@ class GraphQlArgumentInitializerTests { assertThat(((PrimaryConstructorComplexInput) result).name).isEqualTo("Hello"); } + @SuppressWarnings("unchecked") private DataFetchingEnvironment initEnvironment(String jsonPayload) throws JsonProcessingException { - Map arguments = this.mapper.readValue(jsonPayload, new TypeReference>() { - }); + Map arguments = this.mapper.readValue(jsonPayload, Map.class); return DataFetchingEnvironmentImpl.newDataFetchingEnvironment().arguments(arguments).build(); }