Skip to content

Commit

Permalink
Deserialize integer/long JSON into double single-arg constructors (#4474
Browse files Browse the repository at this point in the history
)
  • Loading branch information
davidmoten authored Apr 10, 2024
1 parent 642d622 commit 884af2e
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 1 deletion.
5 changes: 5 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,11 @@ Oddbjørn Kvalsund (oddbjornkvalsund@github)
in `DeserializerCache` to avoid deadlock on pinning
(2.17.1)

David Moten (davidmoten@github)
* Contributed #4453: Allow JSON Integer to deserialize into a single-arg constructor of
parameter type `double`
(2.18.0)

Teodor Danciu (teodord@github)
* Reported #4464: When `Include.NON_DEFAULT` setting is used, `isEmpty()` method is
not called on the serializer
Expand Down
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Project: jackson-databind

2.18.0 (not yet released)

#4453: Allow JSON Integer to deserialize into a single-arg constructor of
parameter type `double`
(contributed by David M)
#4456: Rework locking in `DeserializerCache`
(contributed by @pjfanning)
#4458: Rework synchronized block from `BeanDeserializerBase`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,18 @@ arg, rewrapCtorProblem(ctxt, t0)
);
}
}


if (_fromDoubleCreator != null) {
Object arg = Double.valueOf(value);
try {
return _fromDoubleCreator.call1(arg);
} catch (Exception t0) {
return ctxt.handleInstantiationProblem(_fromDoubleCreator.getDeclaringClass(),
arg, rewrapCtorProblem(ctxt, t0)
);
}
}

return super.createFromInt(ctxt, value);
}

Expand Down Expand Up @@ -411,6 +422,19 @@ arg, rewrapCtorProblem(ctxt, t0)
);
}
}

// [databind#4453]: Note: can lose precision (since double is 64-bits of which
// only part is for mantissa). But already the case with regular properties.
if (_fromDoubleCreator != null) {
Object arg = Double.valueOf(value);
try {
return _fromDoubleCreator.call1(arg);
} catch (Exception t0) {
return ctxt.handleInstantiationProblem(_fromDoubleCreator.getDeclaringClass(),
arg, rewrapCtorProblem(ctxt, t0)
);
}
}

return super.createFromLong(ctxt, value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package com.fasterxml.jackson.databind.deser.std;

import java.math.BigDecimal;
import java.math.BigInteger;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

// [databind#2978]
public class StdValueInstantiatorTest
{
private static final long LONG_TEST_VALUE = 12345678901L;

@Test
public void testDoubleValidation_valid() {
assertEquals(0d, StdValueInstantiator.tryConvertToDouble(BigDecimal.ZERO));
Expand All @@ -23,4 +31,174 @@ public void testDoubleValidation_invalid() {
BigDecimal value = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.valueOf(Double.MAX_VALUE));
assertNull(StdValueInstantiator.tryConvertToDouble(value));
}

@Test
public void testJsonIntegerToDouble() throws Exception {
ObjectMapper m = new ObjectMapper();
Stuff a = m.readValue("5", Stuff.class);
assertEquals(5, a.value);
}

@Test
public void testJsonLongToDouble() throws Exception {
ObjectMapper m = new ObjectMapper();
assertTrue(LONG_TEST_VALUE > Integer.MAX_VALUE);
Stuff a = m.readValue(String.valueOf(LONG_TEST_VALUE), Stuff.class);
assertEquals(LONG_TEST_VALUE, a.value);
}

static class Stuff {
final double value;

Stuff(double value) {
this.value = value;
}
}

@Test
public void testJsonIntegerDeserializationPrefersInt() throws Exception {
ObjectMapper m = new ObjectMapper();
A a = m.readValue("5", A.class);
assertEquals(1, a.creatorType);
}

static class A {
final int creatorType;

A(int value) {
this.creatorType = 1;
}

A(long value) {
this.creatorType = 2;
}

A(BigInteger value) {
this.creatorType = 3;
}

A(double value) {
this.creatorType = 4;
}
}

@Test
public void testJsonIntegerDeserializationPrefersLong() throws Exception {
ObjectMapper m = new ObjectMapper();
B a = m.readValue("5", B.class);
assertEquals(2, a.creatorType);
}

static class B {
final int creatorType;

B(long value) {
this.creatorType = 2;
}

B(BigInteger value) {
this.creatorType = 3;
}

B(double value) {
this.creatorType = 4;
}
}

@Test
public void testJsonIntegerDeserializationPrefersBigInteger() throws Exception {
ObjectMapper m = new ObjectMapper();
C a = m.readValue("5", C.class);
assertEquals(3, a.creatorType);
}

static class C {
final int creatorType;

C(BigInteger value) {
this.creatorType = 3;
}

C(double value) {
this.creatorType = 4;
}
}

@Test
public void testJsonLongDeserializationPrefersLong() throws Exception {
ObjectMapper m = new ObjectMapper();
A2 a = m.readValue(String.valueOf(LONG_TEST_VALUE), A2.class);
assertEquals(2, a.creatorType);
}

static class A2 {
final int creatorType;

A2(int value) {
this.creatorType = 1;
}

A2(long value) {
this.creatorType = 2;
}

A2(BigInteger value) {
this.creatorType = 3;
}

A2(double value) {
this.creatorType = 4;
}
}

@Test
public void testJsonLongDeserializationPrefersBigInteger() throws Exception {
ObjectMapper m = new ObjectMapper();
B2 a = m.readValue(String.valueOf(LONG_TEST_VALUE), B2.class);
assertEquals(3, a.creatorType);
}

static class B2 {
final int creatorType;

B2(BigInteger value) {
this.creatorType = 3;
}

B2(double value) {
this.creatorType = 4;
}
}

@Test
public void testJsonIntegerIntoDoubleConstructorThrows() throws Exception {
ObjectMapper m = new ObjectMapper();
try {
m.readValue("5", D.class);
fail();
} catch (ValueInstantiationException e) {
assertTrue(e.getCause() instanceof IllegalArgumentException);
assertEquals("boo", e.getCause().getMessage());
}
}

static final class D {

D(double value) {
throw new IllegalArgumentException("boo");
}
}

@Test
public void testJsonLongIntoDoubleConstructorThrows() throws Exception {
ObjectMapper m = new ObjectMapper();
try {
m.readValue(String.valueOf(LONG_TEST_VALUE), D.class);
fail();
} catch (ValueInstantiationException e) {
assertTrue(e.getCause() instanceof IllegalArgumentException);
assertEquals("boo", e.getCause().getMessage());
}
}

}

0 comments on commit 884af2e

Please sign in to comment.