com.fasterxml.jackson.dataformat.csv.CsvMapper Maven / Gradle / Ivy
Show all versions of jackson-dataformat-csv Show documentation
package com.fasterxml.jackson.dataformat.csv;
import java.util.Collection;
import java.util.Objects;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperBuilder;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.dataformat.csv.impl.LRUMap;
import com.fasterxml.jackson.databind.util.ViewMatcher;
/**
* Specialized {@link ObjectMapper}, with extended functionality to
* produce {@link CsvSchema} instances out of POJOs.
*/
public class CsvMapper extends ObjectMapper
{
private static final long serialVersionUID = 1;
/**
* Base implementation for "Vanilla" {@link ObjectMapper}, used with
* CSV backend.
*
* @since 2.10
*/
public static class Builder extends MapperBuilder
{
public Builder(CsvMapper m) {
super(m);
}
/*
/******************************************************************
/* Format features
/******************************************************************
*/
public Builder enable(CsvParser.Feature... features) {
for (CsvParser.Feature f : features) {
_mapper.enable(f);
}
return this;
}
public Builder disable(CsvParser.Feature... features) {
for (CsvParser.Feature f : features) {
_mapper.disable(f);
}
return this;
}
public Builder configure(CsvParser.Feature f, boolean state)
{
if (state) {
_mapper.enable(f);
} else {
_mapper.disable(f);
}
return this;
}
public Builder enable(CsvGenerator.Feature... features) {
for (CsvGenerator.Feature f : features) {
_mapper.enable(f);
}
return this;
}
public Builder disable(CsvGenerator.Feature... features) {
for (CsvGenerator.Feature f : features) {
_mapper.disable(f);
}
return this;
}
public Builder configure(CsvGenerator.Feature f, boolean state)
{
if (state) {
_mapper.enable(f);
} else {
_mapper.disable(f);
}
return this;
}
}
/**
* Simple class in order to create a map key based on {@link JavaType} and a given view.
* Used for caching associated schemas in {@code _untypedSchemas} and {@code _typedSchemas}.
*
* @since 2.14
*/
public static final class ViewKey
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private final JavaType _pojoType;
private final Class> _view;
private final int _hashCode;
public ViewKey(final JavaType pojoType, final Class> view)
{
_pojoType = pojoType;
_view = view;
_hashCode = Objects.hash(pojoType, view);
}
@Override
public int hashCode() { return _hashCode; }
@Override
public boolean equals(final Object o)
{
if (o == this) { return true; }
if (o == null || o.getClass() != getClass()) { return false; }
final ViewKey other = (ViewKey) o;
if (_hashCode != other._hashCode || _view != other._view) { return false; }
return Objects.equals(_pojoType, other._pojoType);
}
@Override
public String toString()
{
String viewName = _view != null ? _view.getName() : null;
return "[ViewKey: pojoType=" + _pojoType + ", view=" + viewName + "]";
}
}
/**
* Simple caching for schema instances, given that they are relatively expensive
* to construct; this one is for "loose" (non-typed) schemas
*/
protected final LRUMap _untypedSchemas;
/**
* Simple caching for schema instances, given that they are relatively expensive
* to construct; this one is for typed schemas
*/
protected final LRUMap _typedSchemas;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
public CsvMapper() {
this(new CsvFactory());
}
@SuppressWarnings("deprecation")
public CsvMapper(CsvFactory f)
{
super(f);
// As per #11: default to alphabetic ordering
enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
_untypedSchemas = new LRUMap<>(8,32);
_typedSchemas = new LRUMap<>(8,32);
}
/**
* Copy-constructor, mostly used to support {@link #copy}.
*
* NOTE: {@link ObjectMapper} had this method since 2.1.
*
* @since 2.5
*/
protected CsvMapper(CsvMapper src)
{
super(src);
_untypedSchemas = new LRUMap<>(8,32);
_typedSchemas = new LRUMap<>(8,32);
}
/**
* Short-cut for:
*
* return builder(new CsvFactory());
*
*
* @since 2.10
*/
public static CsvMapper.Builder csvBuilder() {
return new CsvMapper.Builder(new CsvMapper());
}
/**
* @since 2.10
*/
public static CsvMapper.Builder builder() {
return new CsvMapper.Builder(new CsvMapper());
}
/**
* @since 2.10
*/
public static CsvMapper.Builder builder(CsvFactory streamFactory) {
return new CsvMapper.Builder(new CsvMapper(streamFactory));
}
/**
* @since 2.5
*/
@Override
public CsvMapper copy()
{
_checkInvalidCopy(CsvMapper.class);
return new CsvMapper(this);
}
/*
/**********************************************************************
/* Configuration
/**********************************************************************
*/
public CsvMapper configure(CsvGenerator.Feature f, boolean state) {
return state ? enable(f) : disable(f);
}
public CsvMapper configure(CsvParser.Feature f, boolean state) {
return state ? enable(f) : disable(f);
}
public CsvMapper enable(CsvGenerator.Feature f) {
((CsvFactory)_jsonFactory).enable(f);
return this;
}
public CsvMapper enable(CsvParser.Feature f) {
((CsvFactory)_jsonFactory).enable(f);
return this;
}
public CsvMapper disable(CsvGenerator.Feature f) {
((CsvFactory)_jsonFactory).disable(f);
return this;
}
public CsvMapper disable(CsvParser.Feature f) {
((CsvFactory)_jsonFactory).disable(f);
return this;
}
/*
/**********************************************************************
/* Additional typed accessors
/**********************************************************************
*/
/**
* Overridden with more specific type, since factory we have
* is always of type {@link CsvFactory}
*/
@Override
public CsvFactory getFactory() {
return (CsvFactory) _jsonFactory;
}
/*
/**********************************************************************
/* Additional ObjectReader factory methods
/**********************************************************************
*/
/**
* Convenience method which is functionally equivalent to:
*
* reader(pojoType).withSchema(schemaFor(pojoType));
*
* that is, constructs a {@link ObjectReader} which both binds to
* specified type and uses "loose" {@link CsvSchema} introspected from
* specified type (one without strict inferred typing).
*
* @param pojoType Type used both for data-binding (result type) and for
* schema introspection. NOTE: must NOT be an array or Collection type, since
* these only make sense for data-binding (like arrays of objects to bind),
* but not for schema construction (no CSV types can be mapped to arrays
* or Collections)
*/
public ObjectReader readerWithSchemaFor(Class> pojoType)
{
JavaType type = constructType(pojoType);
/* sanity check: not useful for structured types, since
* schema type will need to differ from data-bind type
*/
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return readerFor(type).with(schemaFor(type));
}
/**
* Convenience method which is functionally equivalent to:
*
* reader(pojoType).withSchema(typedSchemaFor(pojoType));
*
* that is, constructs a {@link ObjectReader} which both binds to
* specified type and uses "strict" {@link CsvSchema} introspected from
* specified type (one where typing is inferred).
*/
public ObjectReader readerWithTypedSchemaFor(Class> pojoType)
{
JavaType type = constructType(pojoType);
// sanity check: not useful for structured types, since
// schema type will need to differ from data-bind type
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return readerFor(type).with(typedSchemaFor(type));
}
/*
/**********************************************************************
/* Additional ObjectWriter factory methods
/**********************************************************************
*/
/**
* Convenience method which is functionally equivalent to:
*
* writer(pojoType).with(schemaFor(pojoType));
*
* that is, constructs a {@link ObjectWriter} which both binds to
* specified type and uses "loose" {@link CsvSchema} introspected from
* specified type (one without strict inferred typing).
*
* @param pojoType Type used both for data-binding (result type) and for
* schema introspection. NOTE: must NOT be an array or Collection type, since
* these only make sense for data-binding (like arrays of objects to bind),
* but not for schema construction (no root-level CSV types can be mapped to arrays
* or Collections)
*/
public ObjectWriter writerWithSchemaFor(Class> pojoType)
{
JavaType type = constructType(pojoType);
// sanity check as per javadoc above
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return writerFor(type).with(schemaFor(type));
}
/**
* Convenience method which is functionally equivalent to:
*
* writer(pojoType).with(typedSchemaFor(pojoType));
*
* that is, constructs a {@link ObjectWriter} which both binds to
* specified type and uses "strict" {@link CsvSchema} introspected from
* specified type (one where typing is inferred).
*/
public ObjectWriter writerWithTypedSchemaFor(Class> pojoType)
{
JavaType type = constructType(pojoType);
// sanity check as per javadoc above
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return writerFor(type).with(typedSchemaFor(type));
}
/*
/**********************************************************************
/* CsvSchema construction; overrides, new methods
/**********************************************************************
*/
/**
* Convenience method that is same as
*
* CsvSchema.emptySchema().withHeader();
*
* and returns a {@link CsvSchema} instance that uses default configuration
* with additional setting that the first content line contains intended
* column names.
*
* @since 2.5
*/
public CsvSchema schemaWithHeader() {
return CsvSchema.emptySchema().withHeader();
}
/**
* Convenience method that is same as
*
* CsvSchema.emptySchema()
*
* that is, returns an "empty" Schema; one with default values and no
* column definitions.
*
* @since 2.5
*/
public CsvSchema schema() {
return CsvSchema.emptySchema();
}
/**
* Method that can be used to determine a CSV schema to use for given
* POJO type, using default serialization settings including ordering.
* Definition will not be strictly typed (that is, all columns are
* just defined to be exposed as String tokens).
*/
public CsvSchema schemaFor(JavaType pojoType) {
return _schemaFor(pojoType, _untypedSchemas, false, null);
}
public CsvSchema schemaForWithView(JavaType pojoType, Class> view) {
return _schemaFor(pojoType, _untypedSchemas, false, view);
}
public final CsvSchema schemaFor(Class> pojoType) {
return _schemaFor(constructType(pojoType), _untypedSchemas, false, null);
}
public final CsvSchema schemaForWithView(Class> pojoType, Class> view) {
return _schemaFor(constructType(pojoType), _untypedSchemas, false, view);
}
public final CsvSchema schemaFor(TypeReference> pojoTypeRef) {
return _schemaFor(constructType(pojoTypeRef.getType()), _untypedSchemas, false, null);
}
public final CsvSchema schemaForWithView(TypeReference> pojoTypeRef, Class> view) {
return _schemaFor(constructType(pojoTypeRef.getType()), _untypedSchemas, false, view);
}
/**
* Method that can be used to determine a CSV schema to use for given
* POJO type, using default serialization settings including ordering.
* Definition WILL be strictly typed: that is, code will try to
* determine type limitations which may make parsing more efficient
* (especially for numeric types like java.lang.Integer).
*/
public CsvSchema typedSchemaFor(JavaType pojoType) {
return _schemaFor(pojoType, _typedSchemas, true, null);
}
public CsvSchema typedSchemaForWithView(JavaType pojoType, Class> view) {
return _schemaFor(pojoType, _typedSchemas, true, view);
}
public final CsvSchema typedSchemaFor(Class> pojoType) {
return _schemaFor(constructType(pojoType), _typedSchemas, true, null);
}
public final CsvSchema typedSchemaForWithView(Class> pojoType, Class> view) {
return _schemaFor(constructType(pojoType), _typedSchemas, true, view);
}
public final CsvSchema typedSchemaFor(TypeReference> pojoTypeRef) {
return _schemaFor(constructType(pojoTypeRef.getType()), _typedSchemas, true, null);
}
public final CsvSchema typedSchemaForWithView(TypeReference> pojoTypeRef, Class> view) {
return _schemaFor(constructType(pojoTypeRef.getType()), _typedSchemas, true, view);
}
/*
/**********************************************************************
/* Internal methods
/**********************************************************************
*/
protected CsvSchema _schemaFor(JavaType pojoType, LRUMap schemas,
boolean typed, Class> view)
{
final ViewKey viewKey = new ViewKey(pojoType, view);
synchronized (schemas) {
CsvSchema s = schemas.get(viewKey);
if (s != null) {
return s;
}
}
final AnnotationIntrospector intr = _deserializationConfig.getAnnotationIntrospector();
CsvSchema.Builder builder = CsvSchema.builder();
_addSchemaProperties(builder, intr, typed, pojoType, null, view);
CsvSchema result = builder.build();
synchronized (schemas) {
schemas.put(viewKey, result);
}
return result;
}
@Deprecated // since 2.11 (remove from 3.0 at latest)
protected CsvSchema _schemaFor(JavaType pojoType, LRUMap schemas, boolean typed) {
return _schemaFor(pojoType, schemas, typed, null);
}
protected boolean _nonPojoType(JavaType t)
{
if (t.isPrimitive() || t.isEnumType()) {
return true;
}
Class> raw = t.getRawClass();
// Wrapper types for numbers
if (Number.class.isAssignableFrom(raw)) {
if ((raw == Byte.class)
|| (raw == Short.class)
|| (raw == Character.class)
|| (raw == Integer.class)
|| (raw == Long.class)
|| (raw == Float.class)
|| (raw == Double.class)
) {
return true;
}
}
// Some other well-known non-POJO types
if ((raw == Boolean.class)
|| (raw == String.class)
) {
return true;
}
return false;
}
protected void _addSchemaProperties(CsvSchema.Builder builder, AnnotationIntrospector intr,
boolean typed, JavaType pojoType, NameTransformer unwrapper, Class> view)
{
// 09-Aug-2015, tatu: From [dataformat-csv#87], realized that one can not have
// real schemas for primitive/wrapper
if (_nonPojoType(pojoType)) {
return;
}
BeanDescription beanDesc = getSerializationConfig().introspect(pojoType);
final boolean includeByDefault = isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
for (BeanPropertyDefinition prop : beanDesc.findProperties()) {
if (view != null) {
Class>[] views = prop.findViews();
if (views == null) {
views = beanDesc.findDefaultViews();
}
// If property defines no Views AND non-view-enabled included by default,
// should include
if ((views == null) && includeByDefault) {
;
} else if (!ViewMatcher.construct(views).isVisibleForView(view)) {
continue;
}
}
// ignore setter-only properties:
if (!prop.couldSerialize()) {
continue;
}
// [dataformat-csv#15]: handle unwrapped props
AnnotatedMember m = prop.getPrimaryMember();
if (m != null) {
NameTransformer nextUnwrapper = intr.findUnwrappingNameTransformer(prop.getPrimaryMember());
if (nextUnwrapper != null) {
if (unwrapper != null) {
nextUnwrapper = NameTransformer.chainedTransformer(unwrapper, nextUnwrapper);
}
JavaType nextType = m.getType();
_addSchemaProperties(builder, intr, typed, nextType, nextUnwrapper, view);
continue;
}
}
// Then name wrapping/unwrapping
String name = prop.getName();
if (unwrapper != null) {
name = unwrapper.transform(name);
}
if (typed && m != null) {
builder.addColumn(name, _determineType(m.getRawType()));
} else {
builder.addColumn(name);
}
}
}
// should not be null since couldSerialize() returned true, so:
protected CsvSchema.ColumnType _determineType(Class> propType)
{
// very first thing: arrays
if (propType.isArray()) {
// one exception; byte[] assumed to come in as Base64 encoded
if (propType == byte[].class) {
return CsvSchema.ColumnType.STRING;
}
return CsvSchema.ColumnType.ARRAY;
}
// First let's check certain cases that ought to be just presented as Strings...
if (propType == String.class
|| propType == Character.TYPE
|| propType == Character.class) {
return CsvSchema.ColumnType.STRING;
}
if (propType == Boolean.class
|| propType == Boolean.TYPE) {
return CsvSchema.ColumnType.BOOLEAN;
}
// all primitive types are good for NUMBER, since 'char', 'boolean' handled above
if (propType.isPrimitive()) {
return CsvSchema.ColumnType.NUMBER;
}
if (Number.class.isAssignableFrom(propType)) {
return CsvSchema.ColumnType.NUMBER;
}
if (Collection.class.isAssignableFrom(propType)) { // since 2.5
return CsvSchema.ColumnType.ARRAY;
}
// but in general we will just do what we can:
return CsvSchema.ColumnType.NUMBER_OR_STRING;
}
}