diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java index 8e6261894b6..553213f9515 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java @@ -186,6 +186,13 @@ public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Type target, NodeM // Extract out the expected generic type of the collection. Type memberType = Object.class; + + // If given a Class, then attempt to find the generic superclass (e.g., extending ArrayList with a + // concrete generic type). + if (targetType instanceof Class) { + targetType = ((Class) target).getGenericSuperclass(); + } + if (targetType instanceof ParameterizedType) { Type[] genericTypes = ((ParameterizedType) targetType).getActualTypeArguments(); if (genericTypes.length > 0) { diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java index 76eebb21ce7..d1d15729b10 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java @@ -5,8 +5,10 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasValue; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.startsWith; @@ -940,6 +942,59 @@ public static final class NonZeroArgConstructorCollection extends ArrayList { + public static final class Foo { + boolean a; + + public boolean a() { + return a; + } + + public void a(boolean a) { + this.a = a; + } + } + } + + // Throw some nested generics at the NodeMapper to ensure it works with them. + @Test + public void deserializesIntoTypedListWithNestedGenerics() { + Node value = Node.parse("[ [[{\"a\": true}]], [[{\"a\": false}]] ]"); + NodeMapper mapper = new NodeMapper(); + NestedFooList result = mapper.deserialize(value, NestedFooList.class); + + assertThat(result, hasSize(2)); + assertThat(result.get(0), instanceOf(ArrayList.class)); + assertThat(result.get(0), hasSize(1)); + assertThat(result.get(0).get(0), hasSize(1)); + assertThat(result.get(0).get(0), instanceOf(FooList.class)); + assertThat(result.get(0).get(0).get(0), instanceOf(FooList.Foo.class)); + + assertThat(result.get(1), instanceOf(ArrayList.class)); + assertThat(result.get(1), hasSize(1)); + assertThat(result.get(1).get(0), hasSize(1)); + assertThat(result.get(1).get(0), instanceOf(FooList.class)); + assertThat(result.get(1).get(0).get(0), instanceOf(FooList.Foo.class)); + } + + public static final class NestedFooList extends ArrayList> {} + @Test public void deserializedIntoMap() { Node heterogeneous = Node.parse("{\"foo\":\"foo\",\"baz\":10}");