Skip to content

Commit e661088

Browse files
committed
fix: convert parameterized objects correctly with @DefaultDataTableEntryTransformer
Refs: #2994
1 parent 1008bee commit e661088

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

datatable/src/main/java/io/cucumber/datatable/DataTableTypeRegistryTableConverter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.cucumber.datatable.TypeFactory.MapType;
77
import io.cucumber.datatable.TypeFactory.OptionalType;
88
import io.cucumber.datatable.TypeFactory.OtherType;
9+
import io.cucumber.datatable.TypeFactory.Parameterized;
910
import org.apiguardian.api.API;
1011

1112
import java.lang.reflect.Type;
@@ -87,7 +88,7 @@ public <T> T convert(DataTable dataTable, Type type, boolean transposed) {
8788
return (T) Optional.ofNullable(singleton);
8889
}
8990

90-
if (javaType instanceof OtherType) {
91+
if (javaType instanceof OtherType || javaType instanceof Parameterized) {
9192
return toSingleton(dataTable, javaType);
9293
}
9394

@@ -106,7 +107,8 @@ public <T> T convert(DataTable dataTable, Type type, boolean transposed) {
106107
return (T) toLists(dataTable, listElement.getElementType());
107108
}
108109

109-
assert listElementType instanceof OtherType || listElementType instanceof OptionalType;
110+
assert listElementType instanceof OtherType || listElementType instanceof OptionalType
111+
|| listElementType instanceof Parameterized;
110112
return (T) toList(dataTable, listElementType);
111113
}
112114

datatable/src/test/java/io/cucumber/datatable/DataTableTypeRegistryTableConverterTest.java

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.junit.jupiter.api.Test;
99

1010
import java.beans.ConstructorProperties;
11+
import java.lang.reflect.ParameterizedType;
1112
import java.lang.reflect.Type;
1213
import java.math.BigDecimal;
1314
import java.math.BigInteger;
@@ -17,6 +18,7 @@
1718
import java.util.HashMap;
1819
import java.util.List;
1920
import java.util.Map;
21+
import java.util.Objects;
2022
import java.util.Optional;
2123
import java.util.TimeZone;
2224

@@ -30,6 +32,7 @@
3032
import static java.util.Collections.emptyMap;
3133
import static java.util.Collections.singletonList;
3234
import static java.util.Locale.ENGLISH;
35+
import static java.util.stream.Collectors.toMap;
3336
import static org.hamcrest.CoreMatchers.is;
3437
import static org.hamcrest.CoreMatchers.startsWith;
3538
import static org.hamcrest.MatcherAssert.assertThat;
@@ -93,7 +96,11 @@ class DataTableTypeRegistryTableConverterTest {
9396
}.getType();
9497
private static final Type MAP_OF_STRING_TO_MAP_OF_INTEGER_TO_PIECE = new TypeReference<Map<String, Map<Integer, Piece>>>() {
9598
}.getType();
96-
public static final Type OPTIONAL_CHESS_BOARD_TYPE = new TypeReference<Optional<ChessBoard>>() {
99+
private static final Type OPTIONAL_CHESS_BOARD_TYPE = new TypeReference<Optional<ChessBoard>>() {
100+
}.getType();
101+
private static final Type NUMBERED_AUTHOR = new TypeReference<NumberedObject<Author>>() {
102+
}.getType();
103+
private static final Type LIST_OF_NUMBERED_AUTHOR = new TypeReference<List<NumberedObject<Author>>>() {
97104
}.getType();
98105
private static final TableTransformer<ChessBoard> CHESS_BOARD_TABLE_TRANSFORMER = table -> new ChessBoard(
99106
table.subTable(1, 1).values());
@@ -120,11 +127,26 @@ class DataTableTypeRegistryTableConverterTest {
120127
};
121128
private static final TableEntryByTypeTransformer JACKSON_TABLE_ENTRY_BY_TYPE_CONVERTER = (entry, type,
122129
cellTransformer) -> objectMapper.convertValue(entry, objectMapper.constructType(type));
130+
private static final TableEntryByTypeTransformer JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER = (entry, type,
131+
cellTransformer) -> {
132+
if (type instanceof ParameterizedType && NumberedObject.class.equals(((ParameterizedType) type).getRawType())) {
133+
return convertToNumberedObject(entry, ((ParameterizedType) type).getActualTypeArguments()[0]);
134+
}
135+
return objectMapper.convertValue(entry, objectMapper.constructType(type));
136+
};
123137
private static final TableCellByTypeTransformer JACKSON_TABLE_CELL_BY_TYPE_CONVERTER = (value,
124138
cellType) -> objectMapper.convertValue(value, objectMapper.constructType(cellType));
125139
private static final DataTableType DATE_TABLE_CELL_TRANSFORMER = new DataTableType(Date.class,
126140
(TableCellTransformer<Date>) SIMPLE_DATE_FORMAT::parse);
127141

142+
private static Object convertToNumberedObject(Map<String, String> numberedEntry, Type type) {
143+
int number = Integer.parseInt(numberedEntry.get("#"));
144+
Map<String, String> entry = numberedEntry.entrySet().stream()
145+
.filter(e -> !"#".equals(e.getKey()))
146+
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
147+
return new NumberedObject<>(number, objectMapper.convertValue(entry, objectMapper.constructType(type)));
148+
}
149+
128150
static {
129151
SIMPLE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
130152
}
@@ -427,6 +449,26 @@ void convert_to_empty_list_of_object__using_default_converter__throws_exception(
427449
"Note: Usually solving one is enough"));
428450
}
429451

452+
@Test
453+
void convert_to_list_of_parameterized_object__using_default_converter() {
454+
DataTable table = parse("",
455+
"| # | firstName | lastName | birthDate |",
456+
"| 1 | Annie M. G. | Schmidt | 1911-03-20 |",
457+
"| 2 | Roald | Dahl | 1916-09-13 |",
458+
"| 3 | Astrid | Lindgren | 1907-11-14 |");
459+
460+
registry.setDefaultDataTableEntryTransformer(JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER);
461+
registry.setDefaultDataTableCellTransformer(TABLE_CELL_BY_TYPE_CONVERTER_SHOULD_NOT_BE_USED);
462+
463+
List<NumberedObject<Author>> expected = asList(
464+
new NumberedObject<>(1, new Author("Annie M. G.", "Schmidt", "1911-03-20")),
465+
new NumberedObject<>(2, new Author("Roald", "Dahl", "1916-09-13")),
466+
new NumberedObject<>(3, new Author("Astrid", "Lindgren", "1907-11-14")));
467+
468+
assertEquals(expected, converter.toList(table, NUMBERED_AUTHOR));
469+
assertEquals(expected, converter.convert(table, LIST_OF_NUMBERED_AUTHOR));
470+
}
471+
430472
@Test
431473
void convert_to_list_of_primitive() {
432474
DataTable table = parse("",
@@ -1222,6 +1264,20 @@ void convert_to_single_object__single_cell__using_default_transformer() {
12221264
assertEquals(Piece.BLACK_BISHOP, converter.convert(table, Piece.class));
12231265
}
12241266

1267+
@Test
1268+
void convert_to_parameterized_object__using_default_converter() {
1269+
DataTable table = parse("",
1270+
"| # | firstName | lastName | birthDate |",
1271+
"| 1 | Annie M. G. | Schmidt | 1911-03-20 |");
1272+
1273+
registry.setDefaultDataTableEntryTransformer(JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER);
1274+
registry.setDefaultDataTableCellTransformer(TABLE_CELL_BY_TYPE_CONVERTER_SHOULD_NOT_BE_USED);
1275+
1276+
NumberedObject<Author> expected = new NumberedObject<>(1, new Author("Annie M. G.", "Schmidt", "1911-03-20"));
1277+
1278+
assertEquals(expected, converter.convert(table, NUMBERED_AUTHOR));
1279+
}
1280+
12251281
@Test
12261282
void convert_to_table__table_transformer_takes_precedence_over_identity_transform() {
12271283
DataTable table = parse("",
@@ -1713,6 +1769,28 @@ void to_maps_of_unknown_value_type__throws_exception__register_table_cell_transf
17131769
"Note: Usually solving one is enough"));
17141770
}
17151771

1772+
private static class NumberedObject<T> {
1773+
private final int number;
1774+
private final T value;
1775+
1776+
private NumberedObject(int number, T value) {
1777+
this.number = number;
1778+
this.value = value;
1779+
}
1780+
1781+
@Override
1782+
public boolean equals(Object obj) {
1783+
return obj instanceof NumberedObject
1784+
&& ((NumberedObject<?>) obj).number == number
1785+
&& Objects.equals(((NumberedObject<?>) obj).value, value);
1786+
}
1787+
1788+
@Override
1789+
public String toString() {
1790+
return String.format("%d: %s", number, value);
1791+
}
1792+
}
1793+
17161794
private enum Piece {
17171795
BLACK_PAWN("♟"),
17181796
BLACK_BISHOP("♝"),

0 commit comments

Comments
 (0)