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. 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 ParameterByTypeTransformer, 
      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.

Scenario No 1 (List<List<String>>) – Done automatically.

Scenario No 2 (List<LecturePrimitive>), No 3 (List<LecturePrimitiveEnum>), No 4 (List<LectureSimple>) – The explicit DataTableType definitions for the three classes can be removed. The Enum will be automatically converted. LectureSimple has a Professor field as a instance variable. The Professor class needs to have a single arg String constructor so it can be created automatically.

Scenario No 5 & No 6 (List<Lecture>) – The explicit DataTableType definitions for the Lecture class can be removed. Lecture has Topic and Rooms fields as instance variables. These classes need to have a single arg String constructor so it can be created automatically.

Scenario No 7 (List<LectureLite>) – This needs a TableRowTransformer, as the table does not have headers, and is not handled by Jackson ObjectMapper, so the explicit definition remains.

Scenario No 8 (Map<String,String>) – Done automatically.

Scenario No 9 (Map<Professor,ProfLevels>) – This needs 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.

Scenario No 10 (Map<String, Lecture>), No 11 & No 12 (DataTable -> Map<Lectureid, Lecture>) – The explicit definitions for LectureId and Lecture can be removed. LectureId is handled by the TableCellByTypeTransformer and needs to have a single arg constructor taking a String parameter.

Scenario No 13 (Lectures) – This needs a TableTransformer, as the table does not have headers, and is not handled by Jackson ObjectMapper, so the explicit definition remains.

Migration from 2.x to 4.x

A basic class that implements TypeRegistryConfigurer using the code above needs to be placed in the glue path. This should take care of most steps involving TableEntryTransformer and TableCellTransformer. All XStream annotations from classes and runners can be removed.

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

Scenario No 1 (List<List<String>>), No 2 (List<LecturePrimitive>), No 3 (List<LecturePrimitiveEnum>) – Done automatically.

Scenario No 4 (List<LectureSimple>) – The Enum will be automatically converted. LectureSimple has a Professor field as a instance variable. The Professor class needs to have a single arg String constructor so it can be created automatically.

Scenario No 5 & No 6 (List<Lecture>) – Lecture has Topic and Rooms fields as instance variables. These classes need to have a single arg String constructor so it can be created automatically.

Scenario No 7 (List<LectureLite>) – This needs a TableRowTransformer to be added, as the table does not have headers, and is not handled by Jackson ObjectMapper by default.

Scenario No 8 (Map<String,String>) – Done automatically.

Scenario No 9 (Map<Professor,ProfLevels>) – This needs explicit DataTable definition, using a TableCellTransformer, to be added for both key and value.

Scenario No 10 (Map<String, Lecture>), No 11 & No 12 (DataTable -> Map<Lectureid, Lecture>) – The DataTable needs a header with the column or row names for automatic conversion to work. The header row or column should leave the first row or column blank for the key to be picked up. LectureId is handled by the TableCellByTypeTransformer and needs to have a single arg String constructor.

Scenario No 13 (Lectures) – This needs explicit DataTable definitions, using a TableTransformer, to be added.

Leave a Reply

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