-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathNumberSerializer.java
213 lines (191 loc) · 7.73 KB
/
NumberSerializer.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
package com.fasterxml.jackson.databind.ser.std;
import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
* As a fallback, we may need to use this serializer for other
* types of {@link Number}s: both custom types and "big" numbers
* like {@link BigInteger} and {@link BigDecimal}.
*/
@JacksonStdImpl
@SuppressWarnings("serial")
public class NumberSerializer
extends StdScalarSerializer<Number>
implements ContextualSerializer
{
/**
* Static instance that is only to be used for {@link java.lang.Number}.
*/
public final static NumberSerializer instance = new NumberSerializer(Number.class);
/**
* Copied from `jackson-core` class `GeneratorBase`
*/
protected final static int MAX_BIG_DECIMAL_SCALE = 9999;
protected final boolean _isInt;
/**
* @since 2.17
*/
protected final NumberFormat _format;
/**
* @since 2.5
*/
public NumberSerializer(Class<? extends Number> rawType) {
super(rawType, false);
// since this will NOT be constructed for Integer or Long, only case is:
_isInt = (rawType == BigInteger.class);
_format = null;
}
/**
* @since 2.17
*/
public NumberSerializer(NumberSerializer src, NumberFormat format) {
super(src);
_isInt = src._isInt;
_format = format;
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov,
BeanProperty property) throws JsonMappingException
{
JsonFormat.Value format = findFormatOverrides(prov, property, handledType());
if (format != null) {
switch (format.getShape()) {
case STRING:
if (format.hasPattern()) {
DecimalFormat decimalFormat;
try {
if (format.hasLocale()) {
decimalFormat = new DecimalFormat(format.getPattern(),
DecimalFormatSymbols.getInstance(format.getLocale()));
} else {
decimalFormat = new DecimalFormat(format.getPattern());
}
} catch (IllegalArgumentException e) {
return prov.reportBadDefinition(handledType(),
String.format("Invalid `DecimalFormat`: \"%s\"", format.getPattern()));
}
return new NumberSerializer(this, decimalFormat);
}
// [databind#2264]: Need special handling for `BigDecimal`
if (((Class<?>) handledType()) == BigDecimal.class) {
return bigDecimalAsStringSerializer();
}
return ToStringSerializer.instance;
default:
}
}
return this;
}
@Override
public void serialize(Number value, JsonGenerator g, SerializerProvider provider) throws IOException
{
if (_format != null) {
g.writeString(_format.format(value));
return;
}
// should mostly come in as one of these two:
if (value instanceof BigDecimal) {
g.writeNumber((BigDecimal) value);
} else if (value instanceof BigInteger) {
g.writeNumber((BigInteger) value);
// These should not occur, as more specific methods should have been called; but
// just in case let's cover all bases:
} else if (value instanceof Long) {
g.writeNumber(value.longValue());
} else if (value instanceof Double) {
g.writeNumber(value.doubleValue());
} else if (value instanceof Float) {
g.writeNumber(value.floatValue());
} else if (value instanceof Integer || value instanceof Byte || value instanceof Short) {
g.writeNumber(value.intValue()); // doesn't need to be cast to smaller numbers
} else {
// We'll have to use fallback "untyped" number write method
g.writeNumber(value.toString());
}
}
/**
* @deprecated Since 2.15
*/
@Deprecated
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode(_isInt ? "integer" : "number", true);
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
{
if (_isInt) {
visitIntFormat(visitor, typeHint, JsonParser.NumberType.BIG_INTEGER);
} else {
if (((Class<?>) handledType()) == BigDecimal.class) {
visitFloatFormat(visitor, typeHint, JsonParser.NumberType.BIG_DECIMAL);
} else {
// otherwise bit unclear what to call... but let's try:
/*JsonNumberFormatVisitor v2 =*/ visitor.expectNumberFormat(typeHint);
}
}
}
/**
* @since 2.10
*/
public static JsonSerializer<?> bigDecimalAsStringSerializer() {
return BigDecimalAsStringSerializer.BD_INSTANCE;
}
final static class BigDecimalAsStringSerializer
extends ToStringSerializerBase
{
final static BigDecimalAsStringSerializer BD_INSTANCE = new BigDecimalAsStringSerializer();
public BigDecimalAsStringSerializer() {
super(BigDecimal.class);
}
@Override
public boolean isEmpty(SerializerProvider prov, Object value) {
// As per [databind#2513], should not delegate; also, never empty (numbers do
// have "default value" to filter by, just not "empty"
return false;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
final String text;
if (gen.isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN)) {
final BigDecimal bd = (BigDecimal) value;
// 24-Aug-2016, tatu: [core#315] prevent possible DoS vector, so we need this
if (!_verifyBigDecimalRange(gen, bd)) {
// ... but wouldn't it be nice to trigger error via generator? Alas,
// no method to do that. So we'll do...
final String errorMsg = String.format(
"Attempt to write plain `java.math.BigDecimal` (see JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN) with illegal scale (%d): needs to be between [-%d, %d]",
bd.scale(), MAX_BIG_DECIMAL_SCALE, MAX_BIG_DECIMAL_SCALE);
provider.reportMappingProblem(errorMsg);
}
text = bd.toPlainString();
} else {
text = value.toString();
}
gen.writeString(text);
}
@Override
public String valueToString(Object value) {
// should never be called
throw new IllegalStateException();
}
// 24-Aug-2016, tatu: [core#315] prevent possible DoS vector, so we need this
protected boolean _verifyBigDecimalRange(JsonGenerator gen, BigDecimal value) throws IOException {
int scale = value.scale();
return ((scale >= -MAX_BIG_DECIMAL_SCALE) && (scale <= MAX_BIG_DECIMAL_SCALE));
}
}
}