[英]jackson-dataformat-csv: Mapping number value without POJO
I'm trying to parse a CSV file using jackson-dataformat-csv
and I want to map the numeric column to the Number java type.我正在尝试使用
jackson-dataformat-csv
解析 CSV 文件,并且我想将数字列映射到 Number java 类型。
CsvSchema schema = CsvSchema.builder().setUseHeader(true)
.addColumn("firstName", CsvSchema.ColumnType.STRING)
.addColumn("lastName", CsvSchema.ColumnType.STRING)
.addColumn("age", CsvSchema.ColumnType.NUMBER)
.build();
CsvMapper csvMapper = new CsvMapper();
MappingIterator<Map<String, Object>> mappingIterator = csvMapper
.readerFor(Map.class)
.with(schema)
.readValues(is);
while (mappingIterator.hasNext()) {
Map<String, Object> entryMap = mappingIterator.next();
Number age = (Number) entryMap.get("age");
}
I'm expecting entryMap.get("age")
should be a Number
, but I get String
instead.我期待
entryMap.get("age")
应该是一个Number
,但我得到的是String
。
My CSV file:我的 CSV 文件:
firstName,lastName,age
John,Doe,21
Error,Name,-10
I know that CsvSchema
works fine with POJOs, but I need to process arbitrary CSV schemas, so I can't create a new java class for every case.我知道
CsvSchema
适用于 POJO,但我需要处理任意 CSV 模式,因此我无法为每种情况创建一个新的 java 类。
Any way to parse CSV into a typed Map
or Array
?有什么方法可以将 CSV 解析为类型化的
Map
或Array
?
Right now it is not possible to configure Map
deserialisation using CsvSchema
.现在无法使用
CsvSchema
配置Map
反序列CsvSchema
。 Process uses com.fasterxml.jackson.databind.deser.std.MapDeserializer
which right now does not check schema.进程使用
com.fasterxml.jackson.databind.deser.std.MapDeserializer
现在不检查模式。 We could write custom Map
deserialiser.我们可以编写自定义
Map
反序列化器。 There is a question on GitHub:CsvMapper does not respect CsvSchema.ColumnType when using @JsonAnySetter where cowtowncoder
answered: GitHub 上有一个问题:CsvMapper 在使用 @JsonAnySetter 时不尊重 CsvSchema.ColumnType,其中
cowtowncoder
回答:
At this point schema type is not used much for anything, but I agree it should.
在这一点上,模式类型并没有用于任何事情,但我同意它应该。
I decided to take a look closer what we can do with that fact that com.fasterxml.jackson.databind.deser.std.MapDeserializer
is used behind the scene.我决定仔细研究一下我们可以用
com.fasterxml.jackson.databind.deser.std.MapDeserializer
在幕后使用这一事实来做什么。 Implementing custom Map
deserialiser which will take care about types would be tricky to implement and register but we can use knowledge about ValueInstantiator
.实现关注类型的自定义
Map
反序列化器实现和注册会很棘手,但我们可以使用有关ValueInstantiator
知识。 Let's define new Map
type which knows what to do with ColumnType
info:让我们定义新的
Map
类型,它知道如何处理ColumnType
信息:
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;
}
}
For new type without no-arg
constructor we should create new ValueInstantiator
:对于没有
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;
}
}
Example usage:用法示例:
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() + ")");
}
}
}
Above code for below CSV
payload:以下
CSV
有效负载的以上代码:
firstName,lastName,age
John,Doe,21
Error,Name,-10.1
prints:印刷:
21 (class java.lang.Long)
-10.1 (class java.lang.Double)
It looks like a hack but I wanted to show this possibility.它看起来像一个黑客,但我想展示这种可能性。
You can use univocity-parsers for this sort of thing.您可以使用univocity-parsers来处理此类事情。 It's faster and way more flexible:
它更快,更灵活:
CsvParserSettingssettings = new CsvParserSettings(); //configure the parser if needed
CsvParser parser = new CsvParser(settings);
for (Record record : parser.iterateRecords(is)) {
Short age = record.getShort("age");
}
To get a typed map, tell the parser what is the type of the columns you are working with:要获得类型化映射,请告诉解析器您正在使用的列的类型是什么:
parser.getRecordMetadata().setTypeOfColumns(Short.class, "age" /*, and other column names*/);
//to get 0 instead of nulls when the field is empty in the file:
parser.getRecordMetadata().setDefaultValueOfColumns("0", "age", /*, and other column names*/);
// then parse
for (Record record : parser.iterateRecords(is)) {
Map<String,Object> map = record.toFieldMap();
}
Hope this helps希望这可以帮助
Disclaimer: I'm the author of this library.免责声明:我是这个库的作者。 It's open source and free (Apache 2.0 license)
它是开源且免费的(Apache 2.0 许可)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.