Skip to content

Support title case data table headers #1746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mpkorstanje opened this issue Aug 20, 2019 · 3 comments
Closed

Support title case data table headers #1746

mpkorstanje opened this issue Aug 20, 2019 · 3 comments
Labels
good first issue Good for newcomers 🙏 help wanted Help wanted - not prioritized by core team ⚡ enhancement Request for new functionality
Milestone

Comments

@mpkorstanje
Copy link
Contributor

mpkorstanje commented Aug 20, 2019

Summary

When using Jackson as a default data table entry transformer it is possible to automatically convert data tables to lists of specific objects. Doing so however requires that headers are written in camel case. They must match the property names of the object.

Expected Behavior

When using Jacksons ObjectMapper as a default table entry converter then some data table with title case headers should be convertible to a list of authors.

| First Name | Last Name | Day of Birth |
| Astrid     | Lindgren  |  1907-12-14  | 
public class Author {
  public String firstName;
  public String lastName;
  public LocalDate dayOfBirth;
}

Current Behavior

Jackson will complain about

Caused by: java.lang.IllegalArgumentException: Unrecognized field "First Name" (class io.cucumber.skeleton.Stepdefs$Author), not marked as ignorable (2 known properties: "previousDayOfWeek", "a"])
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: io.cucumber.skeleton.Stepdefs$Bean["Previous Day Of Week"])

Workaround 1

Configure Jackson with a space case naming strategy.

A limitation of this strategy is that it converts property names to data table names.
This means that for a given property name there is exactly one correct data table
way of writing a data table header. Converting in the other direction like the cucumber
1/2.x implementation did, made writing headers in title case possible e.g:

Jackson:

  • Greetings From Earth --> greetingsFromEarth

Cucumber:

  • Greetings From Earth --> greetingsFromEarth
  • Greetings from Earth --> greetingsFromEarth
package io.cucumber.skeleton;

import cucumber.api.TypeRegistry;
import cucumber.api.TypeRegistryConfigurer;
import io.cucumber.datatable.TableCellByTypeTransformer;
import io.cucumber.datatable.TableEntryByTypeTransformer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;

import java.util.Locale;
import java.util.Map;

import static java.lang.Character.toUpperCase;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;

public class TypeConfiguration implements TypeRegistryConfigurer {
    @Override
    public Locale locale() {
        return Locale.ENGLISH;
    }

    private final ObjectMapper objectMapper = new ObjectMapper()
            .setPropertyNamingStrategy(new SpaceCaseNamingStrategy());

    @Override
    public void configureTypeRegistry(TypeRegistry typeRegistry) {
        typeRegistry.setDefaultDataTableEntryTransformer(new TableEntryByTypeTransformer() {
            @Override
            public <T> T transform(Map<String, String> entry, Class<T> type, TableCellByTypeTransformer cellTransformer) {
                return objectMapper.convertValue(entry, type);
            }
        });
    }

    private static class SpaceCaseNamingStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase {

        private final KebabCaseStrategy kebabCaseStrategy = new KebabCaseStrategy();

        @Override
        public String translate(String propertyName) {
            if (propertyName == null) return null;
            String parts = kebabCaseStrategy.translate(propertyName);
            return stream(parts.split("-"))
                    .map(this::capitalize)
                    .collect(joining(" "));
        }

        private String capitalize(String s) {
            return toUpperCase(s.charAt(0)) + s.substring(1);
        }
    }
}

Workaround 2

Annotate the Author class with JsonProperty and provide the header name. While surprisingly simple doesn't scale so well once many tables are involved. Can be combined with the first work around.

    public class Author {
        @JsonProperty("First Name")
        public String firstName;
        @JsonProperty("Last Name")
        public String lastName;
        @JsonProperty("Day of Birth")
        public LocalDate dayOfBirth;
    }

Possible Solution

Add a field to the @DefaultDataTableEntryTransformer annotation. When this field is set (to some value t.b.d.) the JavaDefaultDataTableEntryTransformerDefinition will convert the title case header names to camel case property names before passing them on to the annotated method.

@DefaultDataTableEntryTransformer
public Object transform(Map<String, String> entry, Type toValueType){
   // once here the entry will have all title case keys renamed to camel case
  return objectMapper.convertValue(entry, objectMapper.constructType(toValueType));
}

For a reference of the title case to camel case strategy see the CamelCaseStringConverter from Cucumber 1/2.x

Note: if this feature is not introduced in a major release it must be off by default.

Context & Motivation

The infrastructure provided by #1677 makes it possible to give #1388 another shot

@mpkorstanje mpkorstanje added Data Tables ⚡ enhancement Request for new functionality 🙏 help wanted Help wanted - not prioritized by core team good first issue Good for newcomers labels Aug 20, 2019
@rasklaad
Copy link

Am I right, that following table won't work neither?

     Given data table
    |first Name |last Name|
    |some       | data    |
public class Table {
    public String firstName;
    public String lastName;
}

Just tried it, and still got IllegalArgumentException

@mpkorstanje
Copy link
Contributor Author

mpkorstanje commented Aug 22, 2019

Not unless you add @JsonProperty("first Name").

@rasklaad
Copy link

rasklaad commented Sep 6, 2019

Can we close this issue? The feature is in the master right now.

@mpkorstanje mpkorstanje added this to the 5.0.0 milestone Sep 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers 🙏 help wanted Help wanted - not prioritized by core team ⚡ enhancement Request for new functionality
Projects
None yet
Development

No branches or pull requests

2 participants