Skip to content

Commit 747258b

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 9dab814 commit 747258b

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed

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

+36
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public class StdValueInstantiator
2323
{
2424
private static final long serialVersionUID = 1L;
2525

26+
private static final BigInteger BIG_INTEGER_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
27+
private static final BigInteger BIG_INTEGER_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
28+
2629
/**
2730
* Type of values that are instantiated; used
2831
* for error reporting purposes.
@@ -418,9 +421,25 @@ value, rewrapCtorProblem(ctxt, t)
418421
}
419422
}
420423

424+
if (_fromLongCreator != null && isValidLong(value)) {
425+
Object arg = value.longValue();
426+
try {
427+
return _fromLongCreator.call1(arg);
428+
} catch (Throwable t0) {
429+
return ctxt.handleInstantiationProblem(_fromLongCreator.getDeclaringClass(),
430+
arg,
431+
rewrapCtorProblem(ctxt, t0)
432+
);
433+
}
434+
}
421435
return super.createFromBigInteger(ctxt, value);
422436
}
423437

438+
// Validate teh BigInteger may be represented by a 64-bit integer
439+
static boolean isValidLong(BigInteger value) {
440+
return BIG_INTEGER_MIN_LONG.compareTo(value) <= 0 && BIG_INTEGER_MAX_LONG.compareTo(value) >= 0;
441+
}
442+
424443
@Override
425444
public Object createFromDouble(DeserializationContext ctxt, double value) throws IOException
426445
{
@@ -460,9 +479,26 @@ value, rewrapCtorProblem(ctxt, t)
460479
}
461480
}
462481

482+
if(_fromDoubleCreator != null && isValidDouble(value)) {
483+
Object arg = value.doubleValue();
484+
try {
485+
return _fromDoubleCreator.call1(arg);
486+
} catch (Throwable t0) {
487+
return ctxt.handleInstantiationProblem(_fromDoubleCreator.getDeclaringClass(),
488+
arg, rewrapCtorProblem(ctxt, t0));
489+
}
490+
}
491+
463492
return super.createFromBigDecimal(ctxt, value);
464493
}
465494

495+
// BigDecimal cannot represent special values NaN, positive infinity, or negative infinity.
496+
// When the value cannot be represented as a double, positive or negative infinity is returned.
497+
static boolean isValidDouble(BigDecimal value) {
498+
double doubleValue = value.doubleValue();
499+
return !Double.isInfinite(doubleValue);
500+
}
501+
466502
@Override
467503
public Object createFromBoolean(DeserializationContext ctxt, boolean value) throws IOException
468504
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.fasterxml.jackson.databind.deser.std;
2+
3+
import com.fasterxml.jackson.databind.BaseMapTest;
4+
5+
import java.math.BigDecimal;
6+
import java.math.BigInteger;
7+
8+
public class StdValueInstantiatorTest extends BaseMapTest {
9+
10+
public void testLongValidation_valid() {
11+
assertTrue(StdValueInstantiator.isValidLong(BigInteger.TEN));
12+
assertTrue(StdValueInstantiator.isValidLong(BigInteger.ZERO));
13+
assertTrue(StdValueInstantiator.isValidLong(BigInteger.valueOf(Long.MIN_VALUE)));
14+
assertTrue(StdValueInstantiator.isValidLong(BigInteger.valueOf(Long.MAX_VALUE)));
15+
}
16+
17+
public void testLongValidation_invalid() {
18+
assertFalse(StdValueInstantiator.isValidLong(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE)));
19+
assertFalse(StdValueInstantiator.isValidLong(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE)));
20+
}
21+
22+
public void testDoubleValidation_valid() {
23+
assertTrue(StdValueInstantiator.isValidDouble(BigDecimal.ZERO));
24+
assertTrue(StdValueInstantiator.isValidDouble(BigDecimal.ONE));
25+
assertTrue(StdValueInstantiator.isValidDouble(BigDecimal.TEN));
26+
assertTrue(StdValueInstantiator.isValidDouble(BigDecimal.valueOf(-1.5D)));
27+
}
28+
29+
public void testDoubleValidation_invalid() {
30+
BigDecimal value = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.valueOf(Double.MAX_VALUE));
31+
assertFalse(StdValueInstantiator.isValidDouble(value));
32+
}
33+
}
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)