Skip to content

Commit cd2afd6

Browse files
authored
Fix failing double JsonCreators in jackson 2.12 (#2978)
The addition of Big numeric types, BigInteger and BigDecimal, prevents some tokens from being handled by existing `double` deserializers. This path occurs when data is buffered into a TokenBuffer while deserializing polymorphic types if the type name is not the first field received.
1 parent ae04d79 commit cd2afd6

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed

src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java

+21
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,30 @@ value, rewrapCtorProblem(ctxt, t)
459459
}
460460
}
461461

462+
// 13-Dec-2020, ckozak: Unlike other types, BigDecimal values may be represented
463+
// with less precision as doubles. When written to a TokenBuffer for polymorphic
464+
// deserialization the most specific type is recorded, though a less precise
465+
// floating point value may be needed.
466+
if(_fromDoubleCreator != null && canConvertToDouble(value)) {
467+
Object arg = value.doubleValue();
468+
try {
469+
return _fromDoubleCreator.call1(arg);
470+
} catch (Throwable t0) {
471+
return ctxt.handleInstantiationProblem(_fromDoubleCreator.getDeclaringClass(),
472+
arg, rewrapCtorProblem(ctxt, t0));
473+
}
474+
}
475+
462476
return super.createFromBigDecimal(ctxt, value);
463477
}
464478

479+
// BigDecimal cannot represent special values NaN, positive infinity, or negative infinity.
480+
// When the value cannot be represented as a double, positive or negative infinity is returned.
481+
static boolean canConvertToDouble(BigDecimal value) {
482+
double doubleValue = value.doubleValue();
483+
return !Double.isInfinite(doubleValue);
484+
}
485+
465486
@Override
466487
public Object createFromBoolean(DeserializationContext ctxt, boolean value) throws IOException
467488
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.fasterxml.jackson.databind.deser.std;
2+
3+
import com.fasterxml.jackson.databind.BaseMapTest;
4+
5+
import java.math.BigDecimal;
6+
7+
public class StdValueInstantiatorTest extends BaseMapTest {
8+
9+
public void testDoubleValidation_valid() {
10+
assertTrue(StdValueInstantiator.canConvertToDouble(BigDecimal.ZERO));
11+
assertTrue(StdValueInstantiator.canConvertToDouble(BigDecimal.ONE));
12+
assertTrue(StdValueInstantiator.canConvertToDouble(BigDecimal.TEN));
13+
assertTrue(StdValueInstantiator.canConvertToDouble(BigDecimal.valueOf(-1.5D)));
14+
}
15+
16+
public void testDoubleValidation_invalid() {
17+
BigDecimal value = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.valueOf(Double.MAX_VALUE));
18+
assertFalse(StdValueInstantiator.canConvertToDouble(value));
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
package com.fasterxml.jackson.databind.jsontype;
2+
3+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
4+
import com.fasterxml.jackson.annotation.JsonAnySetter;
5+
import com.fasterxml.jackson.annotation.JsonCreator;
6+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
7+
import com.fasterxml.jackson.annotation.JsonProperty;
8+
import com.fasterxml.jackson.annotation.JsonSetter;
9+
import com.fasterxml.jackson.annotation.JsonSubTypes;
10+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
11+
import com.fasterxml.jackson.annotation.JsonTypeName;
12+
import com.fasterxml.jackson.annotation.JsonValue;
13+
import com.fasterxml.jackson.core.type.TypeReference;
14+
import com.fasterxml.jackson.databind.BaseMapTest;
15+
16+
import java.io.IOException;
17+
import java.util.HashMap;
18+
import java.util.Map;
19+
import java.util.Objects;
20+
21+
public class TestDoubleJsonCreator extends BaseMapTest {
22+
23+
public static final class UnionExample {
24+
private final Base value;
25+
26+
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
27+
private UnionExample(Base value) {
28+
this.value = value;
29+
}
30+
31+
@JsonValue
32+
private Base getValue() {
33+
return value;
34+
}
35+
36+
public static UnionExample double_(AliasDouble value) {
37+
return new UnionExample(new DoubleWrapper(value));
38+
}
39+
40+
public <T> T accept(Visitor<T> visitor) {
41+
return value.accept(visitor);
42+
}
43+
44+
@Override
45+
public boolean equals(Object other) {
46+
return this == other || (other instanceof UnionExample && equalTo((UnionExample) other));
47+
}
48+
49+
private boolean equalTo(UnionExample other) {
50+
return this.value.equals(other.value);
51+
}
52+
53+
@Override
54+
public int hashCode() {
55+
return Objects.hashCode(this.value);
56+
}
57+
58+
@Override
59+
public String toString() {
60+
return "UnionExample{value: " + value + '}';
61+
}
62+
63+
public interface Visitor<T> {
64+
T visitDouble(AliasDouble value);
65+
66+
T visitUnknown(String unknownType);
67+
}
68+
69+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true, defaultImpl = UnknownWrapper.class)
70+
@JsonSubTypes(@JsonSubTypes.Type(UnionExample.DoubleWrapper.class))
71+
@JsonIgnoreProperties(ignoreUnknown = true)
72+
private interface Base {
73+
<T> T accept(Visitor<T> visitor);
74+
}
75+
76+
@JsonTypeName("double")
77+
private static final class DoubleWrapper implements Base {
78+
private final AliasDouble value;
79+
80+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
81+
private DoubleWrapper(@JsonSetter("double") AliasDouble value) {
82+
Objects.requireNonNull(value, "double cannot be null");
83+
this.value = value;
84+
}
85+
86+
@JsonProperty("double")
87+
private AliasDouble getValue() {
88+
return value;
89+
}
90+
91+
@Override
92+
public <T> T accept(Visitor<T> visitor) {
93+
return visitor.visitDouble(value);
94+
}
95+
96+
@Override
97+
public boolean equals(Object other) {
98+
return this == other || (other instanceof DoubleWrapper && equalTo((DoubleWrapper) other));
99+
}
100+
101+
private boolean equalTo(DoubleWrapper other) {
102+
return this.value.equals(other.value);
103+
}
104+
105+
@Override
106+
public int hashCode() {
107+
return Objects.hashCode(this.value);
108+
}
109+
110+
@Override
111+
public String toString() {
112+
return "DoubleWrapper{value: " + value + '}';
113+
}
114+
}
115+
116+
@JsonTypeInfo(
117+
use = JsonTypeInfo.Id.NAME,
118+
include = JsonTypeInfo.As.EXISTING_PROPERTY,
119+
property = "type",
120+
visible = true)
121+
private static final class UnknownWrapper implements Base {
122+
private final String type;
123+
124+
private final Map<String, Object> value;
125+
126+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
127+
private UnknownWrapper(@JsonProperty("type") String type) {
128+
this(type, new HashMap<String, Object>());
129+
}
130+
131+
private UnknownWrapper(String type, Map<String, Object> value) {
132+
Objects.requireNonNull(type, "type cannot be null");
133+
Objects.requireNonNull(value, "value cannot be null");
134+
this.type = type;
135+
this.value = value;
136+
}
137+
138+
@JsonProperty
139+
private String getType() {
140+
return type;
141+
}
142+
143+
@JsonAnyGetter
144+
private Map<String, Object> getValue() {
145+
return value;
146+
}
147+
148+
@JsonAnySetter
149+
private void put(String key, Object val) {
150+
value.put(key, val);
151+
}
152+
153+
@Override
154+
public <T> T accept(Visitor<T> visitor) {
155+
return visitor.visitUnknown(type);
156+
}
157+
158+
@Override
159+
public boolean equals(Object other) {
160+
return this == other || (other instanceof UnknownWrapper && equalTo((UnknownWrapper) other));
161+
}
162+
163+
private boolean equalTo(UnknownWrapper other) {
164+
return this.type.equals(other.type) && this.value.equals(other.value);
165+
}
166+
167+
@Override
168+
public int hashCode() {
169+
return Objects.hash(this.type, this.value);
170+
}
171+
172+
@Override
173+
public String toString() {
174+
return "UnknownWrapper{type: " + type + ", value: " + value + '}';
175+
}
176+
}
177+
}
178+
179+
public static final class AliasDouble {
180+
private final double value;
181+
182+
private AliasDouble(double value) {
183+
this.value = value;
184+
}
185+
186+
@JsonValue
187+
public double get() {
188+
return value;
189+
}
190+
191+
@Override
192+
public String toString() {
193+
return String.valueOf(value);
194+
}
195+
196+
@Override
197+
public boolean equals(Object other) {
198+
return this == other
199+
|| (other instanceof AliasDouble
200+
&& Double.doubleToLongBits(this.value) == Double.doubleToLongBits(((AliasDouble) other).value));
201+
}
202+
203+
@Override
204+
public int hashCode() {
205+
return Objects.hashCode(value);
206+
}
207+
208+
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
209+
public static AliasDouble of(double value) {
210+
return new AliasDouble(value);
211+
}
212+
}
213+
214+
public void testDeserializationTypeFieldLast() throws IOException {
215+
UnionExample expected = UnionExample.double_(AliasDouble.of(2.0D));
216+
UnionExample actual = objectMapper().readValue(
217+
a2q("{'double': 2.0,'type':'double'}"),
218+
new TypeReference<UnionExample>() {});
219+
assertEquals(expected, actual);
220+
}
221+
222+
public void testDeserializationTypeFieldFirst() throws IOException {
223+
UnionExample expected = UnionExample.double_(AliasDouble.of(2.0D));
224+
UnionExample actual = objectMapper().readValue(
225+
a2q("{'type':'double','double': 2.0}"),
226+
new TypeReference<UnionExample>() {});
227+
assertEquals(expected, actual);
228+
}
229+
}

0 commit comments

Comments
 (0)