RFC 8259, 6 Numbers allows numbers of arbitrary range and precision specifying that a particular implementation may set some limits for numeric values. As of Gson 2.8.4 uses the following deserialization strategies:
- If the result type is declared as
Object
, Gson always returns Double
s for all encountered JSON numbers. In this case, the minimum and maximum hold number value fits the limits of Double
which cannot hold exact Long
values that exceed the Double
exact precision limits (2^53+1
).
- If the result type is declared as
Number
and there are no custom Number
adapters, Gson always returns LazilyParsedNumber
s that hold original string values parsing them to a requested type lazily allowing to use the whole range of Long
. However, it does not allow to use numbers of arbitrary range and precision, and does not expose its hold string as a BigDecimal
.
In order to fix these limitations and preserve backwards compatibility, some sort of "to-number" strategies might be accepted in GsonBuilder
to override the default behavior of Gson. This pull-request introduces such a strategy interface to be used in the built-in Object
and Number
type adapters. There are also four strategies implemented in this PR (two standard to keep the backwards compatibility and two enhanced to overcome the limitations) using an enumeration:
ToNumberPolicy.DOUBLE
that implements the default behavior of the Object
type adapter returning Double
s only.
ToNumberPolicy.LAZILY_PARSED_NUMBER
that implements the default behavior of the Number
type adapter.
ToNumberPolicy.LONG_OR_DOUBLE
that tries to parse a number as a Long
, otherwise then tries to parse it as a Double
, if the number cannot be parsed as a Long
.
ToNumberPolicy.BIG_DECIMAL
that can parse numbers of arbitrary range and precision.
Call-site is expected to extract proper values using the methods declared in java.lang.Number
.
Examples of use:
Default behavior, backwards-compatible with the previous versions of Gson
Gson gson = new GsonBuilder()
.setObjectToNumberStrategy(ToNumberPolicy.DOUBLE) // explicit default, may be omitted
.create();
List<Object> actual = gson.fromJson("[null, 10, 10.0]", new TypeToken<List<Object>>() {}.getType());
List<Double> expected = Arrays.asList(null, 10.0, 10.0);
assertEquals(expected, actual);
Gson gson = new GsonBuilder()
.setNumberToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER) // explicit default, may be omitted
.create();
List<Number> actual = gson.fromJson("[null, 10, 10.0]", new TypeToken<List<Number>>() {}.getType());
List<Object> expected = Arrays.<Object>asList(null, new LazilyParsedNumber("10"), new LazilyParsedNumber("10.0"));
assertEquals(expected, actual);
Object
-declared numbers are always LazilyParsedNumber
s
Gson gson = new GsonBuilder()
.setObjectToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER)
.create();
List<Object> actual = gson.fromJson("[null, 10, 10.0]", new TypeToken<List<Object>>() {}.getType());
List<LazilyParsedNumber> expected = Arrays.asList(null, new LazilyParsedNumber("10"), new LazilyParsedNumber("10.0"));
assertEquals(expected, actual);
Number
-declared numbers are always Double
s
Gson gson = new GsonBuilder()
.setNumberToNumberStrategy(ToNumberPolicy.DOUBLE)
.create();
List<Number> actual = gson.fromJson("[null, 10, 10.0]", new TypeToken<List<Number>>() {}.getType());
List<Double> expected = Arrays.asList(null, 10.0, 10.0);
assertEquals(expected, actual);
Object
-declared numbers are either Long
or Double
Gson gson = new GsonBuilder()
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
.create();
List<Object> actual = gson.fromJson("[null, 10, 10.0]", new TypeToken<List<Object>>() {}.getType());
List<? extends Number> expected = Arrays.asList(null, 10L, 10.0);
assertEquals(expected, actual);
Object
-declared numbers are BigDecimal
s
Gson gson = new GsonBuilder()
.setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL)
.create();
List<Object> actual = gson.fromJson("[null, 3.141592653589793238462643383279, 1e400]", new TypeToken<List<Object>>() {}.getType());
List<BigDecimal> expected = Arrays.asList(null, new BigDecimal("3.141592653589793238462643383279"), new BigDecimal("1e400"));
assertEquals(expected, actual);
Custom bytes-only:
Gson gson = new GsonBuilder()
.setObjectToNumberStrategy(new ToNumberStrategy() {
@Override
public Byte toNumber(final JsonReader in)
throws IOException {
return (byte) in.nextInt();
}
})
.create();
List<Object> actual = gson.fromJson("[null, 10, 20, 30]", new TypeToken<List<Object>>() {}.getType());
List<Byte> expected = Arrays.asList(null, (byte) 10, (byte) 20, (byte) 30);
assertEquals(expected, actual);
Custom deserialization does not affect Byte
-declared deserialization:
ToNumberStrategy fail = new ToNumberStrategy() {
@Override
public Byte toNumber(final JsonReader in) {
throw new AssertionError();
}
};
Gson gson = new GsonBuilder()
.setObjectToNumberStrategy(fail)
.setNumberToNumberStrategy(fail)
.create();
List<Object> actual = gson.fromJson("[null, 10, 20, 30]", new TypeToken<List<Byte>>() {}.getType());
List<Byte> expected = Arrays.asList(null, (byte) 10, (byte) 20, (byte) 30);
assertEquals(expected, actual);
cla: yes