-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathCoercionConfigs.java
339 lines (303 loc) · 12.6 KB
/
CoercionConfigs.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
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
*/
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
*/
protected CoercionAction _defaultAction;
/**
* Default coercion definitions used if no overrides found
* by logical or physical type.
*/
protected final MutableCoercionConfig _defaultCoercions;
/**
* Coercion definitions by logical type ({@link LogicalType})
*/
protected MutableCoercionConfig[] _perTypeCoercions;
/**
* 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 {@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.
*
* @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
&& !config.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)
// 12-Oct-2022, carterkozak: As per [databind#3624]: Coercion from integer-shaped
// data into a floating point type is not banned by the
// ALLOW_COERCION_OF_SCALARS feature because '1' is a valid JSON representation of
// '1.0' in a way that other types of coercion do not satisfy.
&& (targetType != LogicalType.Float || inputShape != CoercionInputShape.Integer)) {
return CoercionAction.Fail;
}
if (inputShape == CoercionInputShape.EmptyString) {
// 09-Jun-2020, tatu: Seems necessary to support backwards-compatibility with
// 2.11, wrt "FromStringDeserializer" supported types
// 06-Jul-2023, tatu: For 2.16, moved before the other check to prevent coercion
// to null where conversion allowed/expected
if (targetType == LogicalType.OtherScalar) {
return CoercionAction.TryConvert;
}
// 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;
}
// 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 {@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;
}
// Whether this is "classic" scalar; a strict small subset and does NOT
// include "OtherScalar"
protected boolean _isScalarType(LogicalType targetType) {
return (targetType == LogicalType.Float)
|| (targetType == LogicalType.Integer)
|| (targetType == LogicalType.Boolean)
|| (targetType == LogicalType.DateTime);
}
}