Skip to content

Commit

Permalink
Fix NodeMapper handling of lists with generic types
Browse files Browse the repository at this point in the history
We previously deserialized lists with generic types incorrectly,
causing the list to be created, but storing invalid types in the list.
This caused the use of the list at runtime to fail due to a
ClassCastException.
  • Loading branch information
mtdowling committed Feb 28, 2023
1 parent e0a5417 commit 815e2e5
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -940,6 +942,59 @@ public static final class NonZeroArgConstructorCollection extends ArrayList<Stri
public NonZeroArgConstructorCollection(String notGonnaWork) {}
}

// We previously reported this kind of deserialization as working, but it actually was storing objects of the
// wrong type in collections, causing ClassCastExceptions at runtime.
@Test
public void deserializesIntoTypedList() {
Node value = Node.parse("[{\"a\": true}, {\"a\": false}]");
NodeMapper mapper = new NodeMapper();

FooList result = mapper.deserialize(value, FooList.class);

assertThat(result, hasSize(2));
assertThat(result.get(0), instanceOf(FooList.Foo.class));
assertThat(result.get(0).a(), is(true));
assertThat(result.get(1), instanceOf(FooList.Foo.class));
assertThat(result.get(1).a(), is(false));
}

public static final class FooList extends ArrayList<FooList.Foo> {
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<ArrayList<FooList>> {}

@Test
public void deserializedIntoMap() {
Node heterogeneous = Node.parse("{\"foo\":\"foo\",\"baz\":10}");
Expand Down

0 comments on commit 815e2e5

Please sign in to comment.