Skip to content

Commit 884af2e

Browse files
authored
Deserialize integer/long JSON into double single-arg constructors (#4474)
1 parent 642d622 commit 884af2e

File tree

4 files changed

+211
-1
lines changed

4 files changed

+211
-1
lines changed

release-notes/CREDITS-2.x

+5
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,11 @@ Oddbjørn Kvalsund (oddbjornkvalsund@github)
17711771
in `DeserializerCache` to avoid deadlock on pinning
17721772
(2.17.1)
17731773

1774+
David Moten (davidmoten@github)
1775+
* Contributed #4453: Allow JSON Integer to deserialize into a single-arg constructor of
1776+
parameter type `double`
1777+
(2.18.0)
1778+
17741779
Teodor Danciu (teodord@github)
17751780
* Reported #4464: When `Include.NON_DEFAULT` setting is used, `isEmpty()` method is
17761781
not called on the serializer

release-notes/VERSION-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Project: jackson-databind
66

77
2.18.0 (not yet released)
88

9+
#4453: Allow JSON Integer to deserialize into a single-arg constructor of
10+
parameter type `double`
11+
(contributed by David M)
912
#4456: Rework locking in `DeserializerCache`
1013
(contributed by @pjfanning)
1114
#4458: Rework synchronized block from `BeanDeserializerBase`

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

+25-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,18 @@ arg, rewrapCtorProblem(ctxt, t0)
383383
);
384384
}
385385
}
386-
386+
387+
if (_fromDoubleCreator != null) {
388+
Object arg = Double.valueOf(value);
389+
try {
390+
return _fromDoubleCreator.call1(arg);
391+
} catch (Exception t0) {
392+
return ctxt.handleInstantiationProblem(_fromDoubleCreator.getDeclaringClass(),
393+
arg, rewrapCtorProblem(ctxt, t0)
394+
);
395+
}
396+
}
397+
387398
return super.createFromInt(ctxt, value);
388399
}
389400

@@ -411,6 +422,19 @@ arg, rewrapCtorProblem(ctxt, t0)
411422
);
412423
}
413424
}
425+
426+
// [databind#4453]: Note: can lose precision (since double is 64-bits of which
427+
// only part is for mantissa). But already the case with regular properties.
428+
if (_fromDoubleCreator != null) {
429+
Object arg = Double.valueOf(value);
430+
try {
431+
return _fromDoubleCreator.call1(arg);
432+
} catch (Exception t0) {
433+
return ctxt.handleInstantiationProblem(_fromDoubleCreator.getDeclaringClass(),
434+
arg, rewrapCtorProblem(ctxt, t0)
435+
);
436+
}
437+
}
414438

415439
return super.createFromLong(ctxt, value);
416440
}

src/test/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiatorTest.java

+178
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.fasterxml.jackson.databind.deser.std;
22

33
import java.math.BigDecimal;
4+
import java.math.BigInteger;
45

56
import org.junit.jupiter.api.Test;
67

8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
10+
711
import static org.junit.jupiter.api.Assertions.assertEquals;
812
import static org.junit.jupiter.api.Assertions.assertNull;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
import static org.junit.jupiter.api.Assertions.fail;
915

1016
// [databind#2978]
1117
public class StdValueInstantiatorTest
1218
{
19+
private static final long LONG_TEST_VALUE = 12345678901L;
20+
1321
@Test
1422
public void testDoubleValidation_valid() {
1523
assertEquals(0d, StdValueInstantiator.tryConvertToDouble(BigDecimal.ZERO));
@@ -23,4 +31,174 @@ public void testDoubleValidation_invalid() {
2331
BigDecimal value = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.valueOf(Double.MAX_VALUE));
2432
assertNull(StdValueInstantiator.tryConvertToDouble(value));
2533
}
34+
35+
@Test
36+
public void testJsonIntegerToDouble() throws Exception {
37+
ObjectMapper m = new ObjectMapper();
38+
Stuff a = m.readValue("5", Stuff.class);
39+
assertEquals(5, a.value);
40+
}
41+
42+
@Test
43+
public void testJsonLongToDouble() throws Exception {
44+
ObjectMapper m = new ObjectMapper();
45+
assertTrue(LONG_TEST_VALUE > Integer.MAX_VALUE);
46+
Stuff a = m.readValue(String.valueOf(LONG_TEST_VALUE), Stuff.class);
47+
assertEquals(LONG_TEST_VALUE, a.value);
48+
}
49+
50+
static class Stuff {
51+
final double value;
52+
53+
Stuff(double value) {
54+
this.value = value;
55+
}
56+
}
57+
58+
@Test
59+
public void testJsonIntegerDeserializationPrefersInt() throws Exception {
60+
ObjectMapper m = new ObjectMapper();
61+
A a = m.readValue("5", A.class);
62+
assertEquals(1, a.creatorType);
63+
}
64+
65+
static class A {
66+
final int creatorType;
67+
68+
A(int value) {
69+
this.creatorType = 1;
70+
}
71+
72+
A(long value) {
73+
this.creatorType = 2;
74+
}
75+
76+
A(BigInteger value) {
77+
this.creatorType = 3;
78+
}
79+
80+
A(double value) {
81+
this.creatorType = 4;
82+
}
83+
}
84+
85+
@Test
86+
public void testJsonIntegerDeserializationPrefersLong() throws Exception {
87+
ObjectMapper m = new ObjectMapper();
88+
B a = m.readValue("5", B.class);
89+
assertEquals(2, a.creatorType);
90+
}
91+
92+
static class B {
93+
final int creatorType;
94+
95+
B(long value) {
96+
this.creatorType = 2;
97+
}
98+
99+
B(BigInteger value) {
100+
this.creatorType = 3;
101+
}
102+
103+
B(double value) {
104+
this.creatorType = 4;
105+
}
106+
}
107+
108+
@Test
109+
public void testJsonIntegerDeserializationPrefersBigInteger() throws Exception {
110+
ObjectMapper m = new ObjectMapper();
111+
C a = m.readValue("5", C.class);
112+
assertEquals(3, a.creatorType);
113+
}
114+
115+
static class C {
116+
final int creatorType;
117+
118+
C(BigInteger value) {
119+
this.creatorType = 3;
120+
}
121+
122+
C(double value) {
123+
this.creatorType = 4;
124+
}
125+
}
126+
127+
@Test
128+
public void testJsonLongDeserializationPrefersLong() throws Exception {
129+
ObjectMapper m = new ObjectMapper();
130+
A2 a = m.readValue(String.valueOf(LONG_TEST_VALUE), A2.class);
131+
assertEquals(2, a.creatorType);
132+
}
133+
134+
static class A2 {
135+
final int creatorType;
136+
137+
A2(int value) {
138+
this.creatorType = 1;
139+
}
140+
141+
A2(long value) {
142+
this.creatorType = 2;
143+
}
144+
145+
A2(BigInteger value) {
146+
this.creatorType = 3;
147+
}
148+
149+
A2(double value) {
150+
this.creatorType = 4;
151+
}
152+
}
153+
154+
@Test
155+
public void testJsonLongDeserializationPrefersBigInteger() throws Exception {
156+
ObjectMapper m = new ObjectMapper();
157+
B2 a = m.readValue(String.valueOf(LONG_TEST_VALUE), B2.class);
158+
assertEquals(3, a.creatorType);
159+
}
160+
161+
static class B2 {
162+
final int creatorType;
163+
164+
B2(BigInteger value) {
165+
this.creatorType = 3;
166+
}
167+
168+
B2(double value) {
169+
this.creatorType = 4;
170+
}
171+
}
172+
173+
@Test
174+
public void testJsonIntegerIntoDoubleConstructorThrows() throws Exception {
175+
ObjectMapper m = new ObjectMapper();
176+
try {
177+
m.readValue("5", D.class);
178+
fail();
179+
} catch (ValueInstantiationException e) {
180+
assertTrue(e.getCause() instanceof IllegalArgumentException);
181+
assertEquals("boo", e.getCause().getMessage());
182+
}
183+
}
184+
185+
static final class D {
186+
187+
D(double value) {
188+
throw new IllegalArgumentException("boo");
189+
}
190+
}
191+
192+
@Test
193+
public void testJsonLongIntoDoubleConstructorThrows() throws Exception {
194+
ObjectMapper m = new ObjectMapper();
195+
try {
196+
m.readValue(String.valueOf(LONG_TEST_VALUE), D.class);
197+
fail();
198+
} catch (ValueInstantiationException e) {
199+
assertTrue(e.getCause() instanceof IllegalArgumentException);
200+
assertEquals("boo", e.getCause().getMessage());
201+
}
202+
}
203+
26204
}

0 commit comments

Comments
 (0)