Skip to content

CsvMapper applies alphabetic Property Order for Java Records #264

@alexpartsch

Description

@alexpartsch

I'm trying to use CsvMapper to parse a Java 16 Record out of a CSV file. While testing it I've encountered some strange behaviour:

It seems CsvMapper.schemaFor(AnyRecord.class).withHeader() always returns a schema where the columns are assumed to be sorted alphabetically by name and the header row is not considered.

Here's an example:

public record TestRecord(String c, String b, String a) {
}

Running the JUnit 5 test:

    @Test
    void testRecord() throws JsonProcessingException {
        CsvMapper csvMapper = new CsvMapper();
        var schema = csvMapper.schemaFor(TestRecord.class).withHeader();
        var csv = """
                c,b,a
                C,B,A""";
        TestRecord tr = csvMapper.readerFor(TestRecord.class).with(schema).readValue(csv);
        Assertions.assertEquals(new TestRecord("C", "B", "A"), tr);
    }

Will fail with:

expected: <TestRecord[c=C, b=B, a=A]> but was: <TestRecord[c=A, b=B, a=C]>
Expected :TestRecord[c=C, b=B, a=A]
Actual   :TestRecord[c=A, b=B, a=C]

So, the parsed instance of TestRecord was initialised in the wrong order.

Doing the same thing with a class:

import java.util.Objects;

public class TestClass {

    public String a;
    public String b;
    public String c;

    public TestClass(String a, String b, String c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public TestClass() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestClass testClass = (TestClass) o;
        return Objects.equals(a, testClass.a) && Objects.equals(b, testClass.b) && Objects.equals(c, testClass.c);
    }

    @Override
    public int hashCode() {
        return Objects.hash(a, b, c);
    }
}

Running the test:

    @Test
    void testClass() throws JsonProcessingException {
        CsvMapper csvMapper = new CsvMapper();
        var schema = csvMapper.schemaFor(TestClass.class).withHeader();
        var csv = """
                c,b,a
                C,B,A""";
        TestClass tr = csvMapper.readerFor(TestClass.class).with(schema).readValue(csv);
        Assertions.assertEquals(new TestClass("C", "B", "A"), tr);
    }

Which passes fine!

Having a record where the properties are ordered alphabetically by name:

public record TestRecordWithSortedProperties(String a, String b, String c) {
}

A similar test case to the first works fine (not the CSV data is still ordered c,b,a):

    @Test
    void testRecordWithSortedProperties() throws JsonProcessingException {
        CsvMapper csvMapper = new CsvMapper();
        var schema = csvMapper.schemaFor(TestRecordWithSortedProperties.class).withHeader();
        var csv = """
                c,b,a
                C,B,A""";
        TestRecordWithSortedProperties tr = csvMapper.readerFor(TestRecordWithSortedProperties.class).with(schema).readValue(csv);
        Assertions.assertEquals(new TestRecordWithSortedProperties("C", "B", "A"), tr);
    }

Also defining a schema directly will work:

    @Test
    void testRecordWithManualSchema() throws JsonProcessingException {
        CsvMapper csvMapper = new CsvMapper();
        var schema = CsvSchema.builder()
                .addColumn("c")
                .addColumn("b")
                .addColumn("a")
                .build()
                .withHeader();
        var csv = """
                c,b,a
                C,B,A""";
        TestRecord tr = csvMapper.readerFor(TestRecord.class).with(schema).readValue(csv);
        Assertions.assertEquals(new TestRecord("C", "B", "A"), tr);
    }

While an annotated record won't again:

public record AnnotatedTestRecord(@JsonProperty("c") String c, @JsonProperty("b") String b, @JsonProperty("a") String a) {
}

Using @JsonPropertyOrder works well!

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({"c", "b", "a"})
public record TestRecord(String c, String b, String a) {
}

I guess the actual errors lies somewhere in the CSV deserilaizer, since alphabetical property order seems to be the default in jackson. I tried to debug into the whole process but couldn't pin point it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions