Cucumber-JVM 4 DataTable Conversion with Jackson ObjectMapper

Introduction

XStream was removed from Cucumber-JVM in version 3 for various reasons. Though it reduced a lot of complexities, it removed functionality which automatically transformed a DataTable into a collection of objects or String to a object. This now requires code for each transformation to define a DataTableType or ParamterType in the TypeRegistry for these cases. This has been made easier in Cucumber-JVM 4.0.0 by using Jackson ObjectMapper for DataTables and Anonymous Parameter Types for Parameters in version 4.2.0. Refer to official announcements for version 4.0.0 and version 4.2.0.

This article explains in brief the migration of DataTable type. Refer these for a detailed explanation of migration from version 2 to version 3 of Parameter and Datatable.

Source Code & Versions

This article uses the Cucumber-JVM version 4.2.0. The source code for the article is located here. The source code for version 3.x and 2.x examples is located here.

DataTable Conversion & Object Mapper

Cucumber 4.x uses Jackson ObjectMapper by default to make life easier for DataTable type conversion for most common use cases. The cases are the ones that required TableEntryTransformer and TableCellTransformer transformer objects in Cucumber 3.x for registering explicit DataTableType. The cases that required TableRowTransformer and TableTransformer need to be handled explicitly as before.

First, one needs to create a class which implements the TableEntryByTypeTransformer and TableCellByTypeTransformer interface. This can also implement the ParameterByTypeTransformer interface required for Anonymous Parameter Types to work. Second, one needs to pass an object of this class to the setDefaultDataTableEntryTransformer and the setDefaultDataTableCellTransformer method of the TypeRegistry object.

Jackson will create the whole object chain as long as any contained object has the appropriate single arg constructor that takes a String parameter.

The basic class that implements TypeRegistryConfigurer that is needed for Jackson ObjectMapper is mentioned below.

public class Configurer implements TypeRegistryConfigurer {
   @Override
   public void configureTypeRegistry(TypeRegistry registry) {

      JacksonTableTransformer jacksonTableTransformer = new JacksonTableTransformer();
      registry.setDefaultDataTableEntryTransformer(jacksonTableTransformer);
      registry.setDefaultDataTableCellTransformer(jacksonTableTransformer);
   }

   @Override
   public Locale locale() {
      return Locale.ENGLISH;
   }

   private static final class JacksonTableTransformer implements TableEntryByTypeTransformer, TableCellByTypeTransformer {
      private final ObjectMapper objectMapper = new ObjectMapper();
      @Override
      public <T> T transform(Map<String, String> entry, Class<T> type, TableCellByTypeTransformer cellTransformer) {
         return objectMapper.convertValue(entry, type);
      }
      @Override
      public <T> T transform(String value, Class<T> cellType) {
         return objectMapper.convertValue(value, cellType);
      }
   }
}

Migration from 3.x to 4.x

Many of the explicit DataTableType definitions with the TableEntryTransformer and TableCellTransformer can be removed. The transformer class implementation and default transformer object registration will be added.

Let’s look at the similar feature files from both versions (3.x and 4.x) and look at each scenario in more detail. The code for class implementing TypeRegistryConfigurer are located at 3.x and 4.x.

Below are some of the common conversions of DataTable to a Java collection detailed in this article dealing with migration of DataTable from version 2.x to 3.x. These will be used as a base for migrating to 4.x.

DataTable TO List of list of primitives

This will be handled automatically. The Cucumber 3.x code details – step and stepdefinition. The Cucumber 4.x code details – step and stepdefiniton.

Given the list string lecture details are
| Jane | 40 | Assistant |
| Doe  | 30 | Associate |

@Given("the list string lecture details are")
public void theListStringLectureDetailsAre(List<List<String>> lectures) { }

DataTable TO List of objects with primitive fields

The destination class variables are all primitives. In Cucumber 4.x, explicit DataTableType definition can be removed. The Cucumber 3.x code details – stepstep definitiondestination classtransformation method and parameter registration. The Cucumber 4.x code – step, stepdefinition and destination class.

public class LecturePrimitive {
    private String profName;	
    private int size;		
    private String profLevel;
}

Given the list primitive lecture details are
| profName | size | profLevel |
| Jane     | 40   | ASSISTANT |
| Doe      | 30   | ASSOCIATE |

@Given("the list primitive lecture details are")
public void thePrimitiveLectureDetailsAre(List<LecturePrimitive> lectures) { }

DataTable TO List of objects with primitive and enum fields

The destination class variables contain an enum and primitives. In Cucumber 4.x, explicit DataTableType definition can be removed and the enum will be automatically created. The Cucumber 3.x code details – stepstepdefinitiondestination classenumtransformation method and parameter registration. The Cucumber 4.x code details – step, stepdefinition, destination class and enum.

public class LecturePrimitiveEnum {
    private String profName;
    private int size;
    private ProfLevels profLevel;
}

public enum ProfLevels { ASSISTANT, ASSOCIATE, PROFESSOR }

Given the list primitive enum lecture details are
| profName | size | profLevel  |
| Jane     |   40 | Assistant  |
| Doe      |   30 | Associate  |

@Given("the list primitive enum lecture details are")
public void thePrimitiveEnumLectureDetailsAre(List<LecturePrimitiveEnum> lectures) { }

DataTable TO List of objects with primitive and object and enum fields

The destination class variables contain an object, enum and primitives. In Cucumber 4.x, explicit DataTableType definition can be removed, the instance variable will be automatically created if it has a single arg constructor and the enum will be automatically created. The Cucumber 3.x code details – stepstepdefinitiondestination classcontained object classenumtransformation method and parameter registration. The Cucumber 4.x code details – stepstepdefinitiondestination classcontained object class and enum.

public class LectureSimple {
    private Professor profName;
    private int size;
    private ProfLevels profLevel;
}

public class Professor {
    private String profName;
    public Professor(String profName) {
        this.profName = profName;
    }
}

public enum ProfLevels { ASSISTANT, ASSOCIATE, PROFESSOR }

Given the list simple lecture object details are
| profName | size | profLevel |
| Jane     |   40 | ASSISTANT |
| Doe      |   30 | ASSOCIATE |

@Given("the list simple lecture object details are")
public void theSimpleLectureObjectDetailsAre(List<LectureSimple> lectures) {}

DataTable TO List of objects with other objects as fields

The destination class instance variables contains objects. In Cucumber 4.x, explicit DataTableType definition can be removed and instance variables will be automatically created if it has a single arg constructor. The Cucumber 3.x code details – stepstep definitiondestination class, contained object classes – Professor, TopicRoomsRoomtransformation method and parameter registration. The Cucumber 4.x code details – stepstepdefinitiondestination class, contained object classes – Professor, TopicRoomsRoom.

public class Lecture {
    private Professor profName;
    private Topic topic;
    private int size;
    private int frequency;
    private Rooms rooms;
}

Given the list lecture details are
| profName | topic         | size | frequency | rooms     |
| Jack     | A1:Topic One  |   40 |         3 | 101A,302C |
| Daniels  | B5:Topic Five |   30 |         2 | 220E,419D |

Given("^the lecture details are$")
public void theLectureDetailsAre(List<Lecture> lectures) {}

DataTable without header TO List of Object (or any other collection)

This needs a TableRowTransformer, as the table does not have headers, and is not handled by Jackson ObjectMapper, so the explicit definition remains.

The Cucumber 3.x code details – stepstepdefinitiondestination classtransformation method and parameter registration. The Cucumber 4.x code details – stepstepdefinitiondestination classtransformation method and parameter registration.

public class LectureLite {
    private Professor profName;
    private Topic topic;
    private int size;
    private int frequency;
    private Rooms rooms;
}

Given the list no header lecture details are
| John Doe | A1:Topic One  | 40 | 3 | 101A,302C |
| Jane Doe | B5:Topic Five | 30 | 2 | 220E,419D |

@Given("the list no header lecture details are")
public void theListNoHeaderLectureDetailsAre(List<LectureLite> lectures) {}

registry.defineDataTableType(new DataTableType(LectureLite.class, new TableRowTransformer<LectureLite>() {
    @Override
    public LectureLite transform(List<String> row) throws Throwable {
        return LectureLite.createLectureLite(row);
 }
}));

DataTable with 2 columns TO Map with primitive key and value

This will be handled automatically. The Cucumber 3.x code details – step and stepdefinition. The Cucumber 4.x code details – step and stepdefinition.

Given the map primitive key value
| Jane | Assistant |
| John | Associate |

@Given("the map primitive key value")
public void theMapPrimitiveKeyValue(Map<String,String> profs) {}

DataTable with 2 columns TO Map with custom object key and value

In Cucumber 4.x, the explicit DataTable definitions using a TableCellTransformer to be present for both key and value. But if only the TableCellByTypeTransformer is registered by using the setDefaultDataTableCellTransformer() method then the explicit definitions are not required. This is because TableEntryByTypeTransformer has priority.

The Cucumber 3.x code details – stepstepdefinition, destination classes – ProfessorProfLevels and parameter registration –  ProfessorProfLevels. The Cucumber 4.x code details – stepstepdefinition, destination classes – ProfessorProfLevels and parameter registration –  ProfessorProfLevels.

Given the map object key value
| Jane | ASSISTANT |
| John | ASSOCIATE |

@Given("the map object key value")
public void theMapObjectKeyValue(Map<Professor,ProfLevels> profs) { }

registry.defineDataTableType(new DataTableType(Professor.class, new TableCellTransformer<Professor>() {
@Override
public Professor transform(String cell) throws Throwable {
        return new Professor(cell);
    }
}));

registry.defineDataTableType(new DataTableType(ProfLevels.class, new TableCellTransformer<ProfLevels>() {
@Override
public ProfLevels transform(String cell) throws Throwable {
    return ProfLevels.valueOf(cell.toUpperCase());
    }
}));

DataTable with more than 2 columns TO Map with primitive key and custom object value

In Cucumber 4.x, explicit DataTableType definition can be removed. The Cucumber 3.x code details – stepstepdefinitiondestination classtransformation method and parameter registration. The Cucumber 4.x code details – stepstepdefinition and destination class.

Given the map primitive key lecture details are
|   | profName | topic         | size | frequency | rooms     |
| 1 | John Doe | A1:Topic One  |   40 |         3 | 101A,302C |
| 2 | Jane Doe | B5:Topic Five |   30 |         2 | 220E,419D |

@Given("the map primitive key lecture details are")
public void theMapPrimitiveKey(Map<String,Lecture> lectures) { }

DataTable with more than 2 columns TO Map with custom object key value

In Cucumber 4.x, key object is handled by the TableCellByTypeTransformer and needs to have a single arg constructor taking a String parameter. The Cucumber 3.x code details – stepstepdefinitiondestination classtransformation method and parameter registration – LectureIdLecture. The Cucumber 4.x code details – stepstepdefinitiondestination class.

public class LectureId {
    private int id;
    public LectureId(String id) {
	this.id = Integer.parseInt(id);
    }
}
Given the map lecture details are
|   | profName | topic         | size | frequency | rooms     |
| 1 | John Doe | A1:Topic One  |   40 |         3 | 101A,302C |
| 2 | Jane Doe | B5:Topic Five |   30 |         2 | 220E,419D |

@Given("the map lecture details are")
public void theMapLectureDetailsAre(Map<LectureId, Lecture> lectures) { }

DataTable TO a Single object

In Cucumber 4.x, this needs a TableTransformer, as the table does not have headers, and is not handled by Jackson ObjectMapper, so the explicit definition remains. The Cucumber 3.x code details – stepstepdefinitiondestination class and parameter registration. The Cucumber 4.x code details – stepstepdefinitiondestination class and parameter registration.

public class Lectures {
    private List<Lecture> lectures;
}

Given all lectures details
  | profName | topic          | size | frequency | rooms     |
  | John     | A1:Topic One   |   40 |         3 | 101A,302C |
  | Jane     | Z9:Topic Six   |   30 |         2 | 220E,419D |

@Given("all lectures details")
public void allLecturesDetails(Lectures lectures) {}

registry.defineDataTableType(new DataTableType(Lectures.class, new TableTransformer<Lectures>() {
    @Override
    public Lectures transform(DataTable table) throws Throwable {
        List<Lecture> lects = table.asMaps().stream().map(m -> Lecture.createLecture(m)).collect(Collectors.toList());
        return new Lectures(lects);
    }
}));

5 thoughts on “Cucumber-JVM 4 DataTable Conversion with Jackson ObjectMapper”

  1. An example showing the simplest use of typeRegistry would be useful, to map a parameter to a POJO, if all fields match.

    I struggled before being able to do it, while it was automatic in the first Cucumber versions :

    @Override
    public void configureTypeRegistry(TypeRegistry typeRegistry) {

    typeRegistry.defineDataTableType(DataTableType.entry(MyDomainEntity.class));

    }

Leave a Reply

Your email address will not be published. Required fields are marked *