0
# Transformers
1
2
Comprehensive transformation system for converting between string representations and Java objects. Includes parameter types, data table types, doc string types, and default transformers that integrate with object mappers for automatic conversion.
3
4
## Capabilities
5
6
### Parameter Types
7
8
Custom parameter types for extracting and transforming step definition parameters from regular expressions, supporting 1-9 capture groups with full type safety.
9
10
```java { .api }
11
/**
12
* Parameter type definitions with 1-9 parameter support
13
* Converts regex capture groups to custom Java objects
14
*/
15
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A1<R> body) { ... }
16
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A2<R> body) { ... }
17
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A3<R> body) { ... }
18
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A4<R> body) { ... }
19
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A5<R> body) { ... }
20
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A6<R> body) { ... }
21
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A7<R> body) { ... }
22
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A8<R> body) { ... }
23
default <R> void ParameterType(String name, String regexp, ParameterDefinitionBody.A9<R> body) { ... }
24
```
25
26
**Usage Examples:**
27
28
```java
29
import io.cucumber.java8.En;
30
import java.math.BigDecimal;
31
import java.util.Currency;
32
import java.time.LocalDate;
33
import java.time.format.DateTimeFormatter;
34
35
public class ParameterTypeDefinitions implements En {
36
37
public ParameterTypeDefinitions() {
38
// Single parameter type - Currency amount
39
ParameterType("amount", "(\\d+\\.\\d+)\\s([A-Z]{3})", (String value, String currency) ->
40
new Amount(new BigDecimal(value), Currency.getInstance(currency)));
41
42
// Multiple parameter type - Date with format
43
ParameterType("date", "(\\d{4})-(\\d{2})-(\\d{2})", (String year, String month, String day) ->
44
LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)));
45
46
// Complex parameter type - Person with multiple attributes
47
ParameterType("person", "([A-Za-z]+)\\s([A-Za-z]+)\\s(\\d+)\\syears\\sold",
48
(String firstName, String lastName, String age) ->
49
new Person(firstName, lastName, Integer.parseInt(age)));
50
51
// Coordinate parameter type
52
ParameterType("coordinate", "\\(([-\\d\\.]+),\\s*([-\\d\\.]+)\\)",
53
(String x, String y) -> new Point(Double.parseDouble(x), Double.parseDouble(y)));
54
55
// Color parameter type with validation
56
ParameterType("color", "(red|green|blue|yellow|purple|orange)", (String colorName) -> {
57
switch (colorName.toLowerCase()) {
58
case "red": return Color.RED;
59
case "green": return Color.GREEN;
60
case "blue": return Color.BLUE;
61
case "yellow": return Color.YELLOW;
62
case "purple": return Color.PURPLE;
63
case "orange": return Color.ORANGE;
64
default: throw new IllegalArgumentException("Unknown color: " + colorName);
65
}
66
});
67
}
68
}
69
70
// Usage in step definitions
71
public class StepDefinitions implements En {
72
public StepDefinitions() {
73
When("I transfer {amount} to the account", (Amount amount) -> {
74
bankingService.transfer(amount);
75
});
76
77
Then("the transaction date should be {date}", (LocalDate expectedDate) -> {
78
assertEquals(expectedDate, transaction.getDate());
79
});
80
81
Given("a {person} is registered", (Person person) -> {
82
userService.register(person);
83
});
84
85
When("I click at {coordinate}", (Point coordinates) -> {
86
mouseService.click(coordinates.getX(), coordinates.getY());
87
});
88
89
Then("the button should be {color}", (Color expectedColor) -> {
90
assertEquals(expectedColor, button.getColor());
91
});
92
}
93
}
94
```
95
96
### Parameter Definition Functional Interfaces
97
98
Functional interfaces for parameter type transformation lambdas supporting 1-9 parameters.
99
100
```java { .api }
101
/**
102
* Functional interfaces for parameter type transformations
103
* Each interface corresponds to the number of regex capture groups
104
*/
105
public interface ParameterDefinitionBody {
106
@FunctionalInterface
107
interface A1<R> {
108
/**
109
* Transform single parameter to return type
110
* @param arg1 First regex capture group
111
* @return Transformed object of type R
112
* @throws Throwable Any exception during transformation
113
*/
114
R accept(String arg1) throws Throwable;
115
}
116
117
@FunctionalInterface
118
interface A2<R> {
119
/**
120
* Transform two parameters to return type
121
* @param arg1 First regex capture group
122
* @param arg2 Second regex capture group
123
* @return Transformed object of type R
124
* @throws Throwable Any exception during transformation
125
*/
126
R accept(String arg1, String arg2) throws Throwable;
127
}
128
129
@FunctionalInterface
130
interface A3<R> {
131
R accept(String arg1, String arg2, String arg3) throws Throwable;
132
}
133
134
@FunctionalInterface
135
interface A4<R> {
136
R accept(String arg1, String arg2, String arg3, String arg4) throws Throwable;
137
}
138
139
@FunctionalInterface
140
interface A5<R> {
141
R accept(String arg1, String arg2, String arg3, String arg4, String arg5) throws Throwable;
142
}
143
144
@FunctionalInterface
145
interface A6<R> {
146
R accept(String arg1, String arg2, String arg3, String arg4, String arg5, String arg6) throws Throwable;
147
}
148
149
@FunctionalInterface
150
interface A7<R> {
151
R accept(String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, String arg7) throws Throwable;
152
}
153
154
@FunctionalInterface
155
interface A8<R> {
156
R accept(String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, String arg7, String arg8) throws Throwable;
157
}
158
159
@FunctionalInterface
160
interface A9<R> {
161
R accept(String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, String arg7, String arg8, String arg9) throws Throwable;
162
}
163
}
164
```
165
166
### Data Table Types
167
168
Transform Gherkin data tables into custom Java objects at different granularities: entire table, entry (row as Map), row (List), or individual cell.
169
170
```java { .api }
171
/**
172
* Data table type transformations for different table structures
173
*/
174
default <T> void DataTableType(DataTableDefinitionBody<T> body) { ... }
175
default <T> void DataTableType(DataTableEntryDefinitionBody<T> body) { ... }
176
default <T> void DataTableType(DataTableRowDefinitionBody<T> body) { ... }
177
default <T> void DataTableType(DataTableCellDefinitionBody<T> body) { ... }
178
179
/**
180
* Data table types with empty cell replacement
181
*/
182
default <T> void DataTableType(String replaceWithEmptyString, DataTableDefinitionBody<T> body) { ... }
183
default <T> void DataTableType(String replaceWithEmptyString, DataTableEntryDefinitionBody<T> body) { ... }
184
default <T> void DataTableType(String replaceWithEmptyString, DataTableRowDefinitionBody<T> body) { ... }
185
default <T> void DataTableType(String replaceWithEmptyString, DataTableCellDefinitionBody<T> body) { ... }
186
```
187
188
**Usage Examples:**
189
190
```java
191
public class DataTableTypeDefinitions implements En {
192
193
public DataTableTypeDefinitions() {
194
// Entry-level transformation (Map<String,String> -> Custom Object)
195
DataTableType((Map<String, String> entry) -> new User(
196
entry.get("name"),
197
entry.get("email"),
198
Integer.parseInt(entry.get("age")),
199
Boolean.parseBoolean(entry.get("active"))
200
));
201
202
// Row-level transformation (List<String> -> Custom Object)
203
DataTableType((List<String> row) -> new Product(
204
row.get(0), // name
205
new BigDecimal(row.get(1)), // price
206
Integer.parseInt(row.get(2)) // quantity
207
));
208
209
// Cell-level transformation (String -> Custom Object)
210
DataTableType((String cell) -> {
211
String[] parts = cell.split(":");
212
return new KeyValue(parts[0], parts[1]);
213
});
214
215
// Table-level transformation (entire DataTable -> Custom Object)
216
DataTableType((DataTable table) -> {
217
List<List<String>> rows = table.asLists();
218
return new Matrix(rows);
219
});
220
221
// Entry transformation with empty cell replacement
222
DataTableType("[blank]", (Map<String, String> entry) -> new Author(
223
entry.get("name"),
224
entry.get("first_publication") // "[blank]" becomes empty string
225
));
226
227
// Complex entry transformation with validation
228
DataTableType((Map<String, String> entry) -> {
229
validateRequiredFields(entry, "name", "email");
230
231
return User.builder()
232
.name(entry.get("name"))
233
.email(entry.get("email"))
234
.age(parseAge(entry.get("age")))
235
.roles(parseRoles(entry.get("roles")))
236
.createdAt(parseDate(entry.get("created_at")))
237
.build();
238
});
239
}
240
}
241
242
// Usage in step definitions
243
public class DataTableSteps implements En {
244
public DataTableSteps() {
245
Given("the following users exist:", (DataTable userTable) -> {
246
List<User> users = userTable.asList(User.class);
247
userService.createUsers(users);
248
});
249
250
When("I add these products:", (DataTable productTable) -> {
251
List<Product> products = productTable.asList(Product.class);
252
inventory.addProducts(products);
253
});
254
255
Then("the configuration should contain:", (DataTable configTable) -> {
256
List<KeyValue> config = configTable.asList(KeyValue.class);
257
assertConfigMatches(config);
258
});
259
260
// Using transposed tables
261
Given("the user details:", (DataTable userTable) -> {
262
User user = userTable.transpose().asList(User.class).get(0);
263
currentUser = user;
264
});
265
}
266
}
267
```
268
269
### Data Table Functional Interfaces
270
271
Functional interfaces for different levels of data table transformation.
272
273
```java { .api }
274
/**
275
* Transform entire data table to custom object
276
*/
277
@FunctionalInterface
278
public interface DataTableDefinitionBody<T> {
279
/**
280
* Transform complete DataTable to custom type
281
* @param table Complete data table with headers and rows
282
* @return Transformed object of type T
283
* @throws Throwable Any exception during transformation
284
*/
285
T accept(DataTable table) throws Throwable;
286
}
287
288
/**
289
* Transform data table entry (header-value map) to custom object
290
*/
291
@FunctionalInterface
292
public interface DataTableEntryDefinitionBody<T> {
293
/**
294
* Transform data table entry to custom type
295
* @param entry Map representing one table row with column headers as keys
296
* @return Transformed object of type T
297
* @throws Throwable Any exception during transformation
298
*/
299
T accept(Map<String, String> entry) throws Throwable;
300
}
301
302
/**
303
* Transform data table row (list of values) to custom object
304
*/
305
@FunctionalInterface
306
public interface DataTableRowDefinitionBody<T> {
307
/**
308
* Transform data table row to custom type
309
* @param row List of string values representing one table row
310
* @return Transformed object of type T
311
* @throws Throwable Any exception during transformation
312
*/
313
T accept(List<String> row) throws Throwable;
314
}
315
316
/**
317
* Transform individual data table cell to custom object
318
*/
319
@FunctionalInterface
320
public interface DataTableCellDefinitionBody<T> {
321
/**
322
* Transform individual cell value to custom type
323
* @param cell String value of a single table cell
324
* @return Transformed object of type T
325
* @throws Throwable Any exception during transformation
326
*/
327
T accept(String cell) throws Throwable;
328
}
329
```
330
331
### Doc String Types
332
333
Transform Gherkin doc strings (multi-line text blocks) into custom Java objects with optional content type filtering.
334
335
```java { .api }
336
/**
337
* Doc string type transformation for multi-line text content
338
*/
339
default <T> void DocStringType(String contentType, DocStringDefinitionBody<T> body) { ... }
340
```
341
342
**Usage Examples:**
343
344
```java
345
public class DocStringTypeDefinitions implements En {
346
347
public DocStringTypeDefinitions() {
348
// JSON doc string transformation
349
DocStringType("json", (String docString) -> {
350
ObjectMapper mapper = new ObjectMapper();
351
return mapper.readValue(docString, JsonNode.class);
352
});
353
354
// XML doc string transformation
355
DocStringType("xml", (String docString) -> {
356
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
357
return builder.parse(new ByteArrayInputStream(docString.getBytes()));
358
});
359
360
// CSV doc string transformation
361
DocStringType("csv", (String docString) -> {
362
List<List<String>> rows = new ArrayList<>();
363
String[] lines = docString.split("\\n");
364
for (String line : lines) {
365
rows.add(Arrays.asList(line.split(",")));
366
}
367
return rows;
368
});
369
370
// Custom configuration format
371
DocStringType("config", (String docString) -> {
372
Properties props = new Properties();
373
props.load(new StringReader(docString));
374
return new Configuration(props);
375
});
376
377
// SQL doc string transformation
378
DocStringType("sql", (String docString) -> {
379
return new SqlQuery(docString.trim());
380
});
381
}
382
}
383
384
// Usage in step definitions
385
public class DocStringSteps implements En {
386
public DocStringSteps() {
387
When("I send the following JSON request:", (JsonNode requestJson) -> {
388
apiClient.sendRequest(requestJson);
389
});
390
391
Then("the XML response should be:", (Document expectedXml) -> {
392
Document actualXml = apiClient.getLastXmlResponse();
393
assertXmlEquals(expectedXml, actualXml);
394
});
395
396
Given("the following CSV data:", (List<List<String>> csvData) -> {
397
dataProcessor.loadCsvData(csvData);
398
});
399
400
When("I apply this configuration:", (Configuration config) -> {
401
applicationContext.applyConfiguration(config);
402
});
403
404
Then("the database should contain:", (SqlQuery expectedQuery) -> {
405
List<Map<String, Object>> results = database.query(expectedQuery.getSql());
406
assertResultsMatch(expectedQuery, results);
407
});
408
}
409
}
410
```
411
412
### Doc String Functional Interface
413
414
```java { .api }
415
/**
416
* Transform doc string content to custom object
417
*/
418
@FunctionalInterface
419
public interface DocStringDefinitionBody<T> {
420
/**
421
* Transform doc string to custom type
422
* @param docString Multi-line string content from Gherkin doc string
423
* @return Transformed object of type T
424
* @throws Throwable Any exception during transformation
425
*/
426
T accept(String docString) throws Throwable;
427
}
428
```
429
430
### Default Transformers
431
432
Fallback transformers that handle conversions when no specific transformer is defined. Ideal for integration with object mappers like Jackson for automatic conversion.
433
434
```java { .api }
435
/**
436
* Default transformation fallbacks for parameters and data tables
437
*/
438
default void DefaultParameterTransformer(DefaultParameterTransformerBody body) { ... }
439
default void DefaultDataTableCellTransformer(DefaultDataTableCellTransformerBody body) { ... }
440
default void DefaultDataTableEntryTransformer(DefaultDataTableEntryTransformerBody body) { ... }
441
442
/**
443
* Default transformers with empty cell replacement
444
*/
445
default void DefaultDataTableCellTransformer(String replaceWithEmptyString, DefaultDataTableCellTransformerBody body) { ... }
446
default void DefaultDataTableEntryTransformer(String replaceWithEmptyString, DefaultDataTableEntryTransformerBody body) { ... }
447
```
448
449
**Usage Examples:**
450
451
```java
452
import com.fasterxml.jackson.databind.ObjectMapper;
453
import com.fasterxml.jackson.databind.JavaType;
454
455
public class DefaultTransformerDefinitions implements En {
456
private final ObjectMapper objectMapper = new ObjectMapper();
457
458
public DefaultTransformerDefinitions() {
459
// Default parameter transformer using Jackson
460
DefaultParameterTransformer((String fromValue, Type toValueType) -> {
461
JavaType javaType = objectMapper.getTypeFactory().constructType(toValueType);
462
return objectMapper.convertValue(fromValue, javaType);
463
});
464
465
// Default data table cell transformer
466
DefaultDataTableCellTransformer((String fromValue, Type toValueType) -> {
467
if (toValueType == Integer.class) {
468
return Integer.parseInt(fromValue);
469
}
470
if (toValueType == Double.class) {
471
return Double.parseDouble(fromValue);
472
}
473
if (toValueType == Boolean.class) {
474
return Boolean.parseBoolean(fromValue);
475
}
476
if (toValueType == LocalDate.class) {
477
return LocalDate.parse(fromValue);
478
}
479
return fromValue; // Fallback to string
480
});
481
482
// Default data table entry transformer using Jackson
483
DefaultDataTableEntryTransformer((Map<String, String> fromValue, Type toValueType) -> {
484
JavaType javaType = objectMapper.getTypeFactory().constructType(toValueType);
485
return objectMapper.convertValue(fromValue, javaType);
486
});
487
488
// Default transformer with empty cell replacement
489
DefaultDataTableEntryTransformer("[empty]", (Map<String, String> fromValue, Type toValueType) -> {
490
// "[empty]" in cells becomes empty string before transformation
491
JavaType javaType = objectMapper.getTypeFactory().constructType(toValueType);
492
return objectMapper.convertValue(fromValue, javaType);
493
});
494
}
495
}
496
```
497
498
### Default Transformer Functional Interfaces
499
500
```java { .api }
501
/**
502
* Default parameter transformer for unmatched parameter types
503
*/
504
@FunctionalInterface
505
public interface DefaultParameterTransformerBody {
506
/**
507
* Transform parameter value to target type when no specific transformer exists
508
* @param fromValue String value to transform
509
* @param toValueType Target Java type for transformation
510
* @return Transformed object of target type
511
* @throws Throwable Any exception during transformation
512
*/
513
Object accept(String fromValue, Type toValueType) throws Throwable;
514
}
515
516
/**
517
* Default data table cell transformer for unmatched cell types
518
*/
519
@FunctionalInterface
520
public interface DefaultDataTableCellTransformerBody {
521
/**
522
* Transform cell value to target type when no specific transformer exists
523
* @param fromValue String cell value to transform
524
* @param toValueType Target Java type for transformation
525
* @return Transformed object of target type
526
* @throws Throwable Any exception during transformation
527
*/
528
Object accept(String fromValue, Type toValueType) throws Throwable;
529
}
530
531
/**
532
* Default data table entry transformer for unmatched entry types
533
*/
534
@FunctionalInterface
535
public interface DefaultDataTableEntryTransformerBody {
536
/**
537
* Transform entry to target type when no specific transformer exists
538
* @param fromValue Map representing table row to transform
539
* @param toValueType Target Java type for transformation
540
* @return Transformed object of target type
541
* @throws Throwable Any exception during transformation
542
*/
543
Object accept(Map<String, String> fromValue, Type toValueType) throws Throwable;
544
}
545
```
546
547
### Empty Cell Handling
548
549
Data tables in Gherkin cannot represent null or empty strings unambiguously. Cucumber interprets empty cells as null, but you can configure replacement strings for empty values.
550
551
```java
552
// Configure replacement for empty cells
553
DataTableType("[blank]", (Map<String, String> entry) -> new User(
554
entry.get("name"),
555
entry.get("email") // "[blank]" becomes empty string, null stays null
556
));
557
558
// Gherkin table:
559
// | name | email |
560
// | Alice | alice@ex.com |
561
// | Bob | [blank] | <- becomes empty string
562
// | Carol | | <- becomes null
563
```