Can I ignore properties matching a naming pattern in source data when deserialized by Jackson?

59 Views Asked by At

I have a Java object that, among other things, is being deserialized from CSV format using jackson-dataformat-csv.

public record MyType(String myString, Integer myInt) {}
private List<MyType> readFile(Path file) throws IOException {
    CsvMapper mapper = CsvMapper.csvBuilder().build();
    CsvSchema schema = CsvSchema.emptySchema().withHeader();
    ObjectReader objectReader = mapper.readerFor(MyType.class).with(schema);

    try (MappingIterator<MyType> iterator = objectReader.readValues(file.toFile())) {
        return iterator.readAll();
    }
}
myString,myInt
A,1
B,2
C,3

In general, I want the deserialization to fail if an unknown property is found, as I don't want a typo to mess up the data by setting a property to null that should have a value. However, I would also like to be able to optionally include extra columns that help describe the data. The exact extra columns may vary between CSVs, and aren't known in advance. To distinguish these columns from other columns, I could use a CSV column naming convention to indicate the columns I wish to ignore (e.g. prefixed with an underscore).

myString,myInt,_description,_notes
A,1,Apple,
B,2,Ball,Only used in legacy data
C,3,Cat,Not used yet

That being said, I haven't figured out a clean way to tell Jackson to ignore columns based on prefix.

Ideally, I wouldn't want to add the annotation to the Java class itself, since the CSV deserialization is a very niche use of the object (though this is not a hard requirement).

Is there a way I can tell Jackson deserialization to ignore any properties with names starting with an underscore, but fail if any other unknown properties are found?

1

There are 1 best solutions below

0
M. Justin On

It's not as straightforward as I'd like, but one solution would be to transform the CSV to an unstructured type (ObjectNode or Map), strip out the values with underscore keys, and then convert that to the desired type. If there are any remaining unsupported properties, an UnrecognizedPropertyException will be thrown as desired.

private List<MyType> readFile(Path file) throws IOException {
    CsvMapper mapper = CsvMapper.csvBuilder().build();
    CsvSchema schema = CsvSchema.emptySchema().withHeader();
    ObjectReader objectReader = mapper.readerFor(ObjectNode.class).with(schema);

    try (MappingIterator<ObjectNode> iterator = objectReader.readValues(file.toFile())) {
        List<MyType> result = new ArrayList<>();
        while (iterator.hasNextValue()) {
            ObjectNode node = iterator.nextValue();
            List<String> underscoreFields = StreamSupport.stream(
                            Spliterators.spliteratorUnknownSize(
                                    node.fieldNames(), Spliterator.ORDERED), false)
                    .filter(f -> f.startsWith("_"))
                    .toList();
            node.remove(underscoreFields);
            result.add(mapper.treeToValue(node, MyType.class));
        }
        return result;
    }
}