Skip to content

Commit dd30aa0

Browse files
committed
Fix failing double JsonCreators in jackson 2.12
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 866b2b4 commit dd30aa0

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)