package com.fasterxml.jackson.databind.cfg;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.type.LogicalType;
Since: 2.12
/**
* @since 2.12
*/
public class CoercionConfigs
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private final static int TARGET_TYPE_COUNT = LogicalType.values().length;
Global default for cases not explicitly covered
/**
* Global default for cases not explicitly covered
*/
protected CoercionAction _defaultAction;
Default coercion definitions used if no overrides found
by logical or physical type.
/**
* Default coercion definitions used if no overrides found
* by logical or physical type.
*/
protected final MutableCoercionConfig _defaultCoercions;
Coercion definitions by logical type (LogicalType
) /**
* Coercion definitions by logical type ({@link LogicalType})
*/
protected MutableCoercionConfig[] _perTypeCoercions;
Coercion definitions by physical type (Class).
/**
* Coercion definitions by physical type (Class).
*/
protected Map<Class<?>, MutableCoercionConfig> _perClassCoercions;
/*
/**********************************************************************
/* Life cycle
/**********************************************************************
*/
public CoercionConfigs() {
this(CoercionAction.TryConvert, new MutableCoercionConfig(),
null, null);
}
protected CoercionConfigs(CoercionAction defaultAction,
MutableCoercionConfig defaultCoercions,
MutableCoercionConfig[] perTypeCoercions,
Map<Class<?>, MutableCoercionConfig> perClassCoercions)
{
_defaultCoercions = defaultCoercions;
_defaultAction = defaultAction;
_perTypeCoercions = perTypeCoercions;
_perClassCoercions = perClassCoercions;
}
Method called to create a non-shared copy of configuration settings, to be used by another ObjectMapper
instance. Returns: A non-shared copy of configuration settings
/**
* Method called to create a non-shared copy of configuration settings,
* to be used by another {@link com.fasterxml.jackson.databind.ObjectMapper}
* instance.
*
* @return A non-shared copy of configuration settings
*/
public CoercionConfigs copy()
{
MutableCoercionConfig[] newPerType;
if (_perTypeCoercions == null) {
newPerType = null;
} else {
final int size = _perTypeCoercions.length;
newPerType = new MutableCoercionConfig[size];
for (int i = 0; i < size; ++i) {
newPerType[i] = _copy(_perTypeCoercions[i]);
}
}
Map<Class<?>, MutableCoercionConfig> newPerClass;
if (_perClassCoercions == null) {
newPerClass = null;
} else {
newPerClass = new HashMap<>();
for (Map.Entry<Class<?>, MutableCoercionConfig> entry : _perClassCoercions.entrySet()) {
newPerClass.put(entry.getKey(), entry.getValue().copy());
}
}
return new CoercionConfigs(_defaultAction, _defaultCoercions.copy(),
newPerType, newPerClass);
}
private static MutableCoercionConfig _copy(MutableCoercionConfig src) {
if (src == null) {
return null;
}
return src.copy();
}
/*
/**********************************************************************
/* Mutators: global defaults
/**********************************************************************
*/
public MutableCoercionConfig defaultCoercions() {
return _defaultCoercions;
}
/*
/**********************************************************************
/* Mutators: per type
/**********************************************************************
*/
public MutableCoercionConfig findOrCreateCoercion(LogicalType type) {
if (_perTypeCoercions == null) {
_perTypeCoercions = new MutableCoercionConfig[TARGET_TYPE_COUNT];
}
MutableCoercionConfig config = _perTypeCoercions[type.ordinal()];
if (config == null) {
_perTypeCoercions[type.ordinal()] = config = new MutableCoercionConfig();
}
return config;
}
public MutableCoercionConfig findOrCreateCoercion(Class<?> type) {
if (_perClassCoercions == null) {
_perClassCoercions = new HashMap<>();
}
MutableCoercionConfig config = _perClassCoercions.get(type);
if (config == null) {
config = new MutableCoercionConfig();
_perClassCoercions.put(type, config);
}
return config;
}
/*
/**********************************************************************
/* Access
/**********************************************************************
*/
General-purpose accessor for finding what to do when specified coercion
from shape that is now always allowed to be coerced from is requested.
Params: - config – Currently active deserialization configuration
- targetType – Logical target type of coercion
- targetClass – Physical target type of coercion
- inputShape – Input shape to coerce from
Returns: CoercionAction configured for specified coercion Since: 2.12
/**
* General-purpose accessor for finding what to do when specified coercion
* from shape that is now always allowed to be coerced from is requested.
*
* @param config Currently active deserialization configuration
* @param targetType Logical target type of coercion
* @param targetClass Physical target type of coercion
* @param inputShape Input shape to coerce from
*
* @return CoercionAction configured for specified coercion
*
* @since 2.12
*/
public CoercionAction findCoercion(DeserializationConfig config,
LogicalType targetType,
Class<?> targetClass, CoercionInputShape inputShape)
{
// First, see if there is exact match for physical type
if ((_perClassCoercions != null) && (targetClass != null)) {
MutableCoercionConfig cc = _perClassCoercions.get(targetClass);
if (cc != null) {
CoercionAction act = cc.findAction(inputShape);
if (act != null) {
return act;
}
}
}
// If not, maybe by logical type
if ((_perTypeCoercions != null) && (targetType != null)) {
MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()];
if (cc != null) {
CoercionAction act = cc.findAction(inputShape);
if (act != null) {
return act;
}
}
}
// Barring that, default coercion for input shape?
CoercionAction act = _defaultCoercions.findAction(inputShape);
if (act != null) {
return act;
}
// Otherwise there are some legacy features that can provide answer
switch (inputShape) {
case EmptyArray:
// Default for setting is false
return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT) ?
CoercionAction.AsNull : CoercionAction.Fail;
case Float:
if (targetType == LogicalType.Integer) {
// Default for setting in 2.x is true
return config.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT) ?
CoercionAction.TryConvert : CoercionAction.Fail;
}
break;
case Integer:
if (targetType == LogicalType.Enum) {
if (config.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
return CoercionAction.Fail;
}
}
break;
default:
}
// classic scalars are numbers, booleans; but date/time also considered
// scalar for this particular purpose
final boolean baseScalar = _isScalarType(targetType);
if (baseScalar) {
// Default for setting in 2.x is true
if (!config.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
return CoercionAction.Fail;
}
}
if (inputShape == CoercionInputShape.EmptyString) {
// Since coercion of scalar must be enabled (see check above), allow empty-string
// coercions by default even without this setting
if (baseScalar
// Default for setting is false
|| config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
return CoercionAction.AsNull;
}
// 09-Jun-2020, tatu: Seems necessary to support backwards-compatibility with
// 2.11, wrt "FromStringDeserializer" supported types
if (targetType == LogicalType.OtherScalar) {
return CoercionAction.TryConvert;
}
// But block from allowing structured types like POJOs, Maps etc
return CoercionAction.Fail;
}
// and all else failing, return default
return _defaultAction;
}
More specialized accessor called in case of input being a blank String (one consisting of only white space characters with length of at least one). Will basically first determine if "blank as empty" is allowed: if not, returns actionIfBlankNotAllowed
, otherwise returns action for CoercionInputShape.EmptyString
. Params: - config – Currently active deserialization configuration
- targetType – Logical target type of coercion
- targetClass – Physical target type of coercion
- actionIfBlankNotAllowed – Return value to use in case "blanks as empty"
is not allowed
Returns: CoercionAction configured for specified coercion from blank string
/**
* More specialized accessor called in case of input being a blank
* String (one consisting of only white space characters with length of at least one).
* Will basically first determine if "blank as empty" is allowed: if not,
* returns {@code actionIfBlankNotAllowed}, otherwise returns action for
* {@link CoercionInputShape#EmptyString}.
*
* @param config Currently active deserialization configuration
* @param targetType Logical target type of coercion
* @param targetClass Physical target type of coercion
* @param actionIfBlankNotAllowed Return value to use in case "blanks as empty"
* is not allowed
*
* @return CoercionAction configured for specified coercion from blank string
*/
public CoercionAction findCoercionFromBlankString(DeserializationConfig config,
LogicalType targetType,
Class<?> targetClass,
CoercionAction actionIfBlankNotAllowed)
{
Boolean acceptBlankAsEmpty = null;
CoercionAction action = null;
// First, see if there is exact match for physical type
if ((_perClassCoercions != null) && (targetClass != null)) {
MutableCoercionConfig cc = _perClassCoercions.get(targetClass);
if (cc != null) {
acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty();
action = cc.findAction(CoercionInputShape.EmptyString);
}
}
// If not, maybe by logical type
if ((_perTypeCoercions != null) && (targetType != null)) {
MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()];
if (cc != null) {
if (acceptBlankAsEmpty == null) {
acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty();
}
if (action == null) {
action = cc.findAction(CoercionInputShape.EmptyString);
}
}
}
// Barring that, default coercion for input shape?
if (acceptBlankAsEmpty == null) {
acceptBlankAsEmpty = _defaultCoercions.getAcceptBlankAsEmpty();
}
if (action == null) {
action = _defaultCoercions.findAction(CoercionInputShape.EmptyString);
}
// First: if using blank as empty is no-go, return what caller specified
if (Boolean.FALSE.equals(acceptBlankAsEmpty)) {
return actionIfBlankNotAllowed;
}
// Otherwise, if action found, return that
if (action != null) {
return action;
}
// 23-Sep-2021, tatu: [databind#3234] Should default to "allow" for Scalar types
// for backwards compatibility
if (_isScalarType(targetType)) {
return CoercionAction.AsNull;
}
// If not, one specific legacy setting to consider...
if (config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
return CoercionAction.AsNull;
}
// But finally consider ultimate default to be "false" and so:
return actionIfBlankNotAllowed;
}
protected boolean _isScalarType(LogicalType targetType) {
return (targetType == LogicalType.Float)
|| (targetType == LogicalType.Integer)
|| (targetType == LogicalType.Boolean)
|| (targetType == LogicalType.DateTime);
}
}