88import org .junit .jupiter .api .Test ;
99
1010import java .beans .ConstructorProperties ;
11+ import java .lang .reflect .ParameterizedType ;
1112import java .lang .reflect .Type ;
1213import java .math .BigDecimal ;
1314import java .math .BigInteger ;
1718import java .util .HashMap ;
1819import java .util .List ;
1920import java .util .Map ;
21+ import java .util .Objects ;
2022import java .util .Optional ;
2123import java .util .TimeZone ;
2224
3032import static java .util .Collections .emptyMap ;
3133import static java .util .Collections .singletonList ;
3234import static java .util .Locale .ENGLISH ;
35+ import static java .util .stream .Collectors .toMap ;
3336import static org .hamcrest .CoreMatchers .is ;
3437import static org .hamcrest .CoreMatchers .startsWith ;
3538import 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