owlike / genson

Genson a fast & modular Java <> Json library
http://owlike.github.io/genson/
219 stars 66 forks source link

Unable to round-trip BigInteger #134

Open rlubke opened 6 years ago

rlubke commented 6 years ago

Consider the following code:

public static class BigIntegerArrayHolder
        {
        @SuppressWarnings("unused")
        public BigIntegerArrayHolder() {}

        public BigIntegerArrayHolder(BigInteger[] array)
            {
            this.array = array;
            }

        public boolean equals(Object o)
            {
            if (o instanceof BigIntegerArrayHolder)
                {
                return Arrays.equals(array, ((BigIntegerArrayHolder) o).array);
                }
            return false;
            }

        @JsonProperty("array")
        public BigInteger[] array;
        }

This results in the following JSON:

{
  "array":[
    987654321987654321987654321
  ]
}

However, due to how Genson parses numerics, the BigInteger value is converted to a double prior to invoking the converter resulting in the following error:

com.owlike.genson.JsonBindingException: Could not deserialize to type class com.oracle.io.json.JsonSerializerTest$BigIntegerArrayHolder

at com.owlike.genson.Genson.deserialize(Genson.java:390) at com.owlike.genson.Genson.deserialize(Genson.java:344) at com.oracle.io.json.JsonSerializer.deserialize(JsonSerializer.java:132) at com.oracle.io.json.AbstractSerializerTest.assertRoundTrip(AbstractSerializerTest.java:99) at com.oracle.io.json.AbstractSerializerTest.assertRoundTrip(AbstractSerializerTest.java:61) at com.oracle.io.json.JsonSerializerTest.testBigInteger(JsonSerializerTest.java:196) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:239) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: com.owlike.genson.JsonBindingException: Could not deserialize to property 'array' of class class com.oracle.io.json.JsonSerializerTest$BigIntegerArrayHolder at com.owlike.genson.reflect.PropertyMutator.couldNotDeserialize(PropertyMutator.java:50) at com.owlike.genson.reflect.PropertyMutator.deserialize(PropertyMutator.java:33) at com.owlike.genson.reflect.BeanDescriptor.deserialize(BeanDescriptor.java:124) at com.owlike.genson.reflect.BeanDescriptor.deserialize(BeanDescriptor.java:103) at com.owlike.genson.convert.RuntimeTypeConverter.deserialize(RuntimeTypeConverter.java:53) at com.owlike.genson.convert.ClassMetadataConverter.deserialize(ClassMetadataConverter.java:100) at com.oracle.io.json.internal.SerializationSupportConverter.deserialize(SerializationSupportConverter.java:63) at com.owlike.genson.convert.NullConverterFactory$NullConverterWrapper.deserialize(NullConverterFactory.java:77) at com.owlike.genson.Genson.deserialize(Genson.java:388) ... 30 more Caused by: java.lang.NumberFormatException: For input string: "9." at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.base/java.lang.Integer.parseInt(Integer.java:652) at java.base/java.math.BigInteger.(BigInteger.java:537) at java.base/java.math.BigInteger.(BigInteger.java:675) at com.owlike.genson.convert.DefaultConverters$BigIntegerConverter.deserialize(DefaultConverters.java:1141) at com.owlike.genson.convert.DefaultConverters$BigIntegerConverter.deserialize(DefaultConverters.java:1131) at com.owlike.genson.convert.RuntimeTypeConverter.deserialize(RuntimeTypeConverter.java:53) at com.oracle.io.json.internal.SerializationSupportConverter.deserialize(SerializationSupportConverter.java:63) at com.owlike.genson.convert.NullConverterFactory$NullConverterWrapper.deserialize(NullConverterFactory.java:77) at com.owlike.genson.convert.DefaultConverters$ArrayConverter.deserialize(DefaultConverters.java:295) at com.owlike.genson.convert.RuntimeTypeConverter.deserialize(RuntimeTypeConverter.java:53) at com.oracle.io.json.internal.SerializationSupportConverter.deserialize(SerializationSupportConverter.java:63) at com.owlike.genson.convert.NullConverterFactory$NullConverterWrapper.deserialize(NullConverterFactory.java:77) at com.owlike.genson.reflect.PropertyMutator.deserialize(PropertyMutator.java:31) ... 37 more

See: https://tools.ietf.org/html/rfc7159#section-6

Specifically:

This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers [IEEE754] is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision.

Given this, it seems that Big{Integer,Decimal} should be serialized as objects to avoid loss of precision vs trying to fit them into a double.

Thoughts?

EugenCepoi commented 6 years ago

This is a bug, it should have been serialized as a string in order to not loose precision. Would you mind opening a PR? The fix is basically to write the value as a string here. Same goes for big decimals.

rlubke commented 6 years ago

Yes, will do. Just going through legal approvals on this end.