Skip to content

Commit 4616a21

Browse files
authored
Convert parameterized objects with @DefaultDataTableEntryTransformer (#2995)
Fixes: #2994
1 parent 1008bee commit 4616a21

File tree

3 files changed

+90
-3
lines changed

3 files changed

+90
-3
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
## Fixed
14+
- [Core] Convert parameterized objects with `@DefaultDataTableEntryTransformer` ([#2995](https://github.com/cucumber/cucumber-jvm/pull/2995) Jean Tissot)
15+
1316
### Changed
1417
- [Archetype] Assume new projects are created with at least Java 17
1518

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

+4-2
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

+83-1
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,30 @@ 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)) {
133+
throw new IllegalArgumentException("Unsupported type " + type);
134+
}
135+
ParameterizedType parameterizedType = (ParameterizedType) type;
136+
if (!NumberedObject.class.equals(parameterizedType.getRawType())) {
137+
throw new IllegalArgumentException("Unsupported type " + parameterizedType);
138+
}
139+
return convertToNumberedObject(entry, parameterizedType.getActualTypeArguments()[0]);
140+
};
123141
private static final TableCellByTypeTransformer JACKSON_TABLE_CELL_BY_TYPE_CONVERTER = (value,
124142
cellType) -> objectMapper.convertValue(value, objectMapper.constructType(cellType));
125143
private static final DataTableType DATE_TABLE_CELL_TRANSFORMER = new DataTableType(Date.class,
126144
(TableCellTransformer<Date>) SIMPLE_DATE_FORMAT::parse);
127145

146+
private static Object convertToNumberedObject(Map<String, String> numberedEntry, Type type) {
147+
int number = Integer.parseInt(numberedEntry.get("#"));
148+
Map<String, String> entry = numberedEntry.entrySet().stream()
149+
.filter(e -> !"#".equals(e.getKey()))
150+
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
151+
return new NumberedObject<>(number, objectMapper.convertValue(entry, objectMapper.constructType(type)));
152+
}
153+
128154
static {
129155
SIMPLE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
130156
}
@@ -427,6 +453,26 @@ void convert_to_empty_list_of_object__using_default_converter__throws_exception(
427453
"Note: Usually solving one is enough"));
428454
}
429455

456+
@Test
457+
void convert_to_list_of_parameterized_object__using_default_converter() {
458+
DataTable table = parse("",
459+
"| # | firstName | lastName | birthDate |",
460+
"| 1 | Annie M. G. | Schmidt | 1911-03-20 |",
461+
"| 2 | Roald | Dahl | 1916-09-13 |",
462+
"| 3 | Astrid | Lindgren | 1907-11-14 |");
463+
464+
registry.setDefaultDataTableEntryTransformer(JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER);
465+
registry.setDefaultDataTableCellTransformer(TABLE_CELL_BY_TYPE_CONVERTER_SHOULD_NOT_BE_USED);
466+
467+
List<NumberedObject<Author>> expected = asList(
468+
new NumberedObject<>(1, new Author("Annie M. G.", "Schmidt", "1911-03-20")),
469+
new NumberedObject<>(2, new Author("Roald", "Dahl", "1916-09-13")),
470+
new NumberedObject<>(3, new Author("Astrid", "Lindgren", "1907-11-14")));
471+
472+
assertEquals(expected, converter.toList(table, NUMBERED_AUTHOR));
473+
assertEquals(expected, converter.convert(table, LIST_OF_NUMBERED_AUTHOR));
474+
}
475+
430476
@Test
431477
void convert_to_list_of_primitive() {
432478
DataTable table = parse("",
@@ -1222,6 +1268,20 @@ void convert_to_single_object__single_cell__using_default_transformer() {
12221268
assertEquals(Piece.BLACK_BISHOP, converter.convert(table, Piece.class));
12231269
}
12241270

1271+
@Test
1272+
void convert_to_parameterized_object__using_default_converter() {
1273+
DataTable table = parse("",
1274+
"| # | firstName | lastName | birthDate |",
1275+
"| 1 | Annie M. G. | Schmidt | 1911-03-20 |");
1276+
1277+
registry.setDefaultDataTableEntryTransformer(JACKSON_NUMBERED_OBJECT_TABLE_ENTRY_CONVERTER);
1278+
registry.setDefaultDataTableCellTransformer(TABLE_CELL_BY_TYPE_CONVERTER_SHOULD_NOT_BE_USED);
1279+
1280+
NumberedObject<Author> expected = new NumberedObject<>(1, new Author("Annie M. G.", "Schmidt", "1911-03-20"));
1281+
1282+
assertEquals(expected, converter.convert(table, NUMBERED_AUTHOR));
1283+
}
1284+
12251285
@Test
12261286
void convert_to_table__table_transformer_takes_precedence_over_identity_transform() {
12271287
DataTable table = parse("",
@@ -1713,6 +1773,28 @@ void to_maps_of_unknown_value_type__throws_exception__register_table_cell_transf
17131773
"Note: Usually solving one is enough"));
17141774
}
17151775

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

0 commit comments

Comments
 (0)