Search code examples
javacsvjacksonfasterxml

is there any way to retrieve the csvSchema used by a cvsMapper when it's read from the first line of a file?


I have a CsvSchema and CsvMapper set up to read CSV files with column names in the first line.

I need to be able to do some work on the data, alter specific fields, and then write the data back out while preserving the column order. I'm presuming that the CsvMapper has an internal schema that it creates from the first line, since the map has all the correct key-value pairs. So, is there any way to use the writer functions of CsvMapper that would use that schema to order the data for output?

CsvSchema schema = CsvSchema.emptySchema().withHeader();
CsvMapper mapper = new CsvMapper();
MappingIterator<Map<String, String>> it = this.mapper.readerFor(Map.class)
            .with(this.schema)
            .readValues(stream);

Right now, i'm getting a CsvMappingException for "Unrecognized column" on the first column of data, so it looks like it's not using the schema.


Solution

  • You can build SequenceWriter using schema from first row you read from input file. Jackson uses LinkedHashMap behind, so, order is kept. Assume your CSV input contains:

    C1,name,type
    I1,John,T1
    I2,Adam,T2
    

    You can read, update and write to console like on below example:

    import com.fasterxml.jackson.databind.MappingIterator;
    import com.fasterxml.jackson.databind.ObjectWriter;
    import com.fasterxml.jackson.databind.SequenceWriter;
    import com.fasterxml.jackson.dataformat.csv.CsvMapper;
    import com.fasterxml.jackson.dataformat.csv.CsvSchema;
    
    import java.io.Closeable;
    import java.io.File;
    import java.io.IOException;
    import java.util.Map;
    
    public class CsvApp {
    
        private CsvMapper csvMapper = new CsvMapper();
    
        private void handle() throws Exception {
            File csvFile = new File("./resource/test.csv").getAbsoluteFile();
    
            CsvSchema schema = CsvSchema.emptySchema().withHeader();
            MappingIterator<Map<String, String>> iterator = csvMapper.readerFor(Map.class).with(schema).readValues(csvFile);
    
            SequenceWriter writer = null;
            while (iterator.hasNext()) {
                final Map<String, String> row = iterator.next();
                if (writer == null) {
                    writer = createWriter(row).writeValues(System.out);
                }
    
                // do something ...
                for (Map.Entry<String, String> item : row.entrySet()) {
                    row.replace(item.getKey(), item.getValue() + "U");
                }
    
                // write back
                writer.write(row);
            }
    
            close(writer);
        }
    
        private ObjectWriter createWriter(Map<String, String> row) {
            CsvSchema.Builder writeSchema = new CsvSchema.Builder();
            writeSchema.setUseHeader(true);
            row.keySet().forEach(writeSchema::addColumn);
    
            return csvMapper.writerFor(Map.class).with(writeSchema.build());
        }
    
        private void close(Closeable closeable) throws IOException {
            if (closeable != null) {
                closeable.close();
            }
        }
    
        public static void main(String[] args) throws Exception {
            new CsvApp().handle();
        }
    }
    

    Above code prints:

    C1,name,type
    I1U,JohnU,T1U
    I2U,AdamU,T2U
    

    See also:

    1. jackson-dataformats-text - Overview