В настоящее время невозможно настроить десериализацию Map
с использованием CsvSchema
. Процесс использует com.fasterxml.jackson.databind.deser.std.MapDeserializer
, который сейчас не проверяет схему. Мы могли бы написать кастомный десериализатор Map
. На GitHub есть вопрос: CsvMapper не учитывает CsvSchema.ColumnType при использовании @ JsonAnySetter , где cowtowncoder
ответил:
На данный момент тип схемы ни для чего не используется, но я согласен
это должно.
EDIT
Я решил посмотреть поближе, что мы можем сделать с тем фактом, что com.fasterxml.jackson.databind.deser.std.MapDeserializer
используется за сценой. Реализация пользовательского десериализатора Map
, который позаботится о типах, будет сложно реализовать и зарегистрировать, но мы можем использовать знания о ValueInstantiator
. Давайте определим новый тип Map
, который знает, что делать с ColumnType
info:
class CsvMap extends HashMap<String, Object> {
private final CsvSchema schema;
private final NumberFormat numberFormat = NumberFormat.getInstance();
public CsvMap(CsvSchema schema) {
this.schema = schema;
}
@Override
public Object put(String key, Object value) {
value = convertIfNeeded(key, value);
return super.put(key, value);
}
private Object convertIfNeeded(String key, Object value) {
CsvSchema.Column column = schema.column(key);
if (column.getType() == CsvSchema.ColumnType.NUMBER) {
try {
return numberFormat.parse(value.toString());
} catch (ParseException e) {
// leave it as it is
}
}
return value;
}
}
Для нового типа без no-arg
конструктора мы должны создать новый ValueInstantiator
:
class CsvMapInstantiator extends ValueInstantiator.Base {
private final CsvSchema schema;
public CsvMapInstantiator(CsvSchema schema) {
super(CsvMap.class);
this.schema = schema;
}
@Override
public Object createUsingDefault(DeserializationContext ctxt) {
return new CsvMap(schema);
}
@Override
public boolean canCreateUsingDefault() {
return true;
}
}
Пример использования:
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.HashMap;
public class CsvApp {
public static void main(String[] args) throws IOException {
File csvFile = new File("./resource/test.csv").getAbsoluteFile();
CsvSchema schema = CsvSchema.builder()
.addColumn("firstName", CsvSchema.ColumnType.STRING)
.addColumn("lastName", CsvSchema.ColumnType.STRING)
.addColumn("age", CsvSchema.ColumnType.NUMBER)
.build().withHeader();
// Create schema aware map module
SimpleModule csvMapModule = new SimpleModule();
csvMapModule.addValueInstantiator(CsvMap.class, new CsvMapInstantiator(schema));
// register map
CsvMapper csvMapper = new CsvMapper();
csvMapper.registerModule(csvMapModule);
// get reader for CsvMap + schema
ObjectReader objectReaderWithSchema = csvMapper
.readerWithSchemaFor(CsvMap.class)
.with(schema);
MappingIterator<CsvMap> mappingIterator = objectReaderWithSchema.readValues(csvFile);
while (mappingIterator.hasNext()) {
CsvMap entryMap = mappingIterator.next();
Number age = (Number) entryMap.get("age");
System.out.println(age + " (" + age.getClass() + ")");
}
}
}
Над кодом ниже CSV
полезная нагрузка:
firstName,lastName,age
John,Doe,21
Error,Name,-10.1
печать:
21 (class java.lang.Long)
-10.1 (class java.lang.Double)
Это похоже на взлом, но я хотел показать эту возможность.