From 6db535bd2e0efa904d0444b49d77f224ac98730b Mon Sep 17 00:00:00 2001 From: Lyubomyr Shaydariv Date: Mon, 16 Apr 2018 15:46:08 +0300 Subject: [PATCH 1/6] Object and Number type adapters number deserialization can be configured --- gson/src/main/java/com/google/gson/Gson.java | 46 +++++- .../java/com/google/gson/GsonBuilder.java | 28 +++- .../java/com/google/gson/ToNumberPolicy.java | 99 +++++++++++++ .../com/google/gson/ToNumberStrategy.java | 70 +++++++++ .../gson/internal/bind/ObjectTypeAdapter.java | 32 +++-- .../test/java/com/google/gson/GsonTest.java | 9 +- .../com/google/gson/ToNumberPolicyTest.java | 115 +++++++++++++++ .../ToNumberPolicyFunctionalTest.java | 134 ++++++++++++++++++ 8 files changed, 516 insertions(+), 17 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/ToNumberPolicy.java create mode 100644 gson/src/main/java/com/google/gson/ToNumberStrategy.java create mode 100644 gson/src/test/java/com/google/gson/ToNumberPolicyTest.java create mode 100644 gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index a7938c84e6..5106ab4468 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -146,6 +146,8 @@ public final class Gson { final LongSerializationPolicy longSerializationPolicy; final List builderFactories; final List builderHierarchyFactories; + final ToNumberStrategy objectToNumberStrategy; + final ToNumberStrategy numberToNumberStrategy; /** * Constructs a Gson object with default configuration. The default configuration has the @@ -188,7 +190,7 @@ public Gson() { DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, Collections.emptyList(), Collections.emptyList(), - Collections.emptyList()); + Collections.emptyList(), ToNumberPolicy.DOUBLE, ToNumberPolicy.LAZILY_PARSED_NUMBER); } Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy, @@ -198,7 +200,8 @@ public Gson() { LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle, int timeStyle, List builderFactories, List builderHierarchyFactories, - List factoriesToBeAdded) { + List factoriesToBeAdded, + ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) { this.excluder = excluder; this.fieldNamingStrategy = fieldNamingStrategy; this.instanceCreators = instanceCreators; @@ -216,12 +219,14 @@ public Gson() { this.timeStyle = timeStyle; this.builderFactories = builderFactories; this.builderHierarchyFactories = builderHierarchyFactories; + this.objectToNumberStrategy = objectToNumberStrategy; + this.numberToNumberStrategy = numberToNumberStrategy; List factories = new ArrayList(); // built-in type adapters that cannot be overridden factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); - factories.add(ObjectTypeAdapter.FACTORY); + factories.add(objectAdapterFactory(objectToNumberStrategy)); // the excluder must precede all adapters that handle user-defined types factories.add(excluder); @@ -241,7 +246,7 @@ public Gson() { doubleAdapter(serializeSpecialFloatingPointValues))); factories.add(TypeAdapters.newFactory(float.class, Float.class, floatAdapter(serializeSpecialFloatingPointValues))); - factories.add(TypeAdapters.NUMBER_FACTORY); + factories.add(numberAdapterFactory(numberToNumberStrategy)); factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY); factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY); factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter))); @@ -365,6 +370,39 @@ static void checkValidFloatingPoint(double value) { } } + private static TypeAdapterFactory objectAdapterFactory(ToNumberStrategy objectToNumberStrategy) { + if (objectToNumberStrategy == ToNumberPolicy.DOUBLE) { + return ObjectTypeAdapter.FACTORY; + } + return ObjectTypeAdapter.newFactory(objectToNumberStrategy); + } + + private static TypeAdapterFactory numberAdapterFactory(final ToNumberStrategy numberToNumberStrategy) { + if (numberToNumberStrategy == ToNumberPolicy.LAZILY_PARSED_NUMBER) { + return TypeAdapters.NUMBER_FACTORY; + } + return TypeAdapters.newFactory(Number.class, new TypeAdapter() { + @Override + public Number read(JsonReader in) throws IOException { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { + case NULL: + in.nextNull(); + return null; + case NUMBER: + case STRING: + return numberToNumberStrategy.readNumber(in); + default: + throw new JsonSyntaxException("Expecting number, got: " + jsonToken); + } + } + @Override + public void write(JsonWriter out, Number value) throws IOException { + out.value(value); + } + }); + } + private static TypeAdapter longAdapter(LongSerializationPolicy longSerializationPolicy) { if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) { return TypeAdapters.LONG; diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index b2fd74edec..1874e7de9b 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -95,6 +95,8 @@ public final class GsonBuilder { private boolean prettyPrinting = DEFAULT_PRETTY_PRINT; private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; private boolean lenient = DEFAULT_LENIENT; + private ToNumberStrategy objectToNumberStrategy = ToNumberPolicy.DOUBLE; + private ToNumberStrategy numberToNumberStrategy = ToNumberPolicy.LAZILY_PARSED_NUMBER; /** * Creates a GsonBuilder instance that can be used to build Gson with various configuration @@ -326,6 +328,30 @@ public GsonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrateg return this; } + /** + * Configures Gson to apply a specific number strategy during deserialization of {@link Object}. + * + * @param objectToNumberStrategy the actual object-to-number strategy + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @see ToNumberPolicy#DOUBLE The default object-to-number strategy + */ + public GsonBuilder setObjectToNumberStrategy(ToNumberStrategy objectToNumberStrategy) { + this.objectToNumberStrategy = objectToNumberStrategy; + return this; + } + + /** + * Configures Gson to apply a specific number strategy during deserialization of {@link Number}. + * + * @param numberToNumberStrategy the actual number-to-number strategy + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @see ToNumberPolicy#LAZILY_PARSED_NUMBER The default number-to-number strategy + */ + public GsonBuilder setNumberToNumberStrategy(ToNumberStrategy numberToNumberStrategy) { + this.numberToNumberStrategy = numberToNumberStrategy; + return this; + } + /** * Configures Gson to apply a set of exclusion strategies during both serialization and * deserialization. Each of the {@code strategies} will be applied as a disjunction rule. @@ -600,7 +626,7 @@ public Gson create() { generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient, serializeSpecialFloatingPointValues, longSerializationPolicy, datePattern, dateStyle, timeStyle, - this.factories, this.hierarchyFactories, factories); + this.factories, this.hierarchyFactories, factories, objectToNumberStrategy, numberToNumberStrategy); } private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, diff --git a/gson/src/main/java/com/google/gson/ToNumberPolicy.java b/gson/src/main/java/com/google/gson/ToNumberPolicy.java new file mode 100644 index 0000000000..db8b0be85a --- /dev/null +++ b/gson/src/main/java/com/google/gson/ToNumberPolicy.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.io.IOException; +import java.math.BigDecimal; + +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.MalformedJsonException; + +/** + * An enumeration that defines two standard number reading strategies and a couple of + * strategies to overcome some historical Gson limitations while deserializing numbers as + * {@link Object} and {@link Number}. + * + * @see ToNumberStrategy + */ +public enum ToNumberPolicy implements ToNumberStrategy { + + /** + * Using this policy will ensure that numbers will be read as {@link Double} values. + * This is the default strategy used during deserialization of numbers as {@link Object}. + */ + DOUBLE { + @Override public Double readNumber(JsonReader in) throws IOException { + return in.nextDouble(); + } + }, + + /** + * Using this policy will ensure that numbers will be read as a lazily parsed number backed + * by a string. This is the default strategy used during deserialization of numbers as + * {@link Number}. + */ + LAZILY_PARSED_NUMBER { + @Override public Number readNumber(JsonReader in) throws IOException { + return new LazilyParsedNumber(in.nextString()); + } + }, + + /** + * Using this policy will ensure that numbers will be read as {@link Long} or {@link Double} + * values depending on how JSON numbers are represented: {@link Long} if the JSON number can + * be parsed as a {@link Long} value, or otherwise {@link Double} if it can be parsed as a + * {@link Double} value. If the parsed double-precision number results in a positive or negative + * infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the + * {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} + * is thrown. + */ + LONG_OR_DOUBLE { + @Override public Number readNumber(JsonReader in) throws IOException, JsonParseException { + String value = in.nextString(); + try { + return Long.parseLong(value); + } catch (NumberFormatException longE) { + try { + Double d = Double.valueOf(value); + if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { + throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + in); + } + return d; + } catch (NumberFormatException doubleE) { + throw new JsonParseException("Cannot parse " + value, doubleE); + } + } + } + }, + + /** + * Using this policy will ensure that numbers will be read as numbers of arbitrary length + * using {@link BigDecimal}. + */ + BIG_DECIMAL { + @Override public BigDecimal readNumber(JsonReader in) throws IOException { + String value = in.nextString(); + try { + return new BigDecimal(value); + } catch (NumberFormatException e) { + throw new JsonParseException("Cannot parse " + value, e); + } + } + } + +} diff --git a/gson/src/main/java/com/google/gson/ToNumberStrategy.java b/gson/src/main/java/com/google/gson/ToNumberStrategy.java new file mode 100644 index 0000000000..32a836d185 --- /dev/null +++ b/gson/src/main/java/com/google/gson/ToNumberStrategy.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.io.IOException; + +import com.google.gson.stream.JsonReader; + +/** + * A strategy that is used to control how numbers should be deserialized for {@link Object} and {@link Number} + * when a concrete type of the deserialized number is unknown in advance. By default, Gson uses the following + * deserialization strategies: + * + *
    + *
  • {@link Double} values are returned for JSON numbers if the deserialization type is declared as + * {@link Object};
  • + *
  • Lazily parsed number values are returned if the deserialization type is declared as {@link Number}.
  • + *
+ * + *

For historical reasons, Gson does not support deserialization of arbitrary-length numbers as its stated in + * RFC 8259 for {@link Object} and {@link Number} + * causing some data loss while deserialization:

+ * + *
+ *   This specification allows implementations to set limits on the range
+ *   and precision of numbers accepted.  Since software that implements
+ *   IEEE 754 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.  A JSON number such as 1E400
+ *   or 3.141592653589793238462643383279 may indicate potential
+ *   interoperability problems, since it suggests that the software that
+ *   created it expects receiving software to have greater capabilities
+ *   for numeric magnitude and precision than is widely available.
+ * 
+ * + *

Use for example, {@link ToNumberPolicy#LONG_OR_DOUBLE} or {@link ToNumberPolicy#BIG_DECIMAL} to overcome + * possible data loss.

+ * + * @see ToNumberPolicy + * @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy) + * @see GsonBuilder#setNumberToNumberStrategy(ToNumberStrategy) + */ +public interface ToNumberStrategy { + + /** + * Reads a number from the given JSON reader. A strategy is supposed to read a single value from the + * reader, and the read value is guaranteed never to be {@code null}. + * + * @param in JSON reader to read a number from + * @return number read from the JSON reader. + * @throws IOException + */ + public Number readNumber(JsonReader in) throws IOException; +} diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java index ec42e04826..6cccc92ecd 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -17,6 +17,8 @@ package com.google.gson.internal.bind; import com.google.gson.Gson; +import com.google.gson.ToNumberStrategy; +import com.google.gson.ToNumberPolicy; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.LinkedTreeMap; @@ -35,20 +37,30 @@ * serialization and a primitive/Map/List on deserialization. */ public final class ObjectTypeAdapter extends TypeAdapter { - public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { - @SuppressWarnings("unchecked") - @Override public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getRawType() == Object.class) { - return (TypeAdapter) new ObjectTypeAdapter(gson); - } - return null; - } - }; + public static final TypeAdapterFactory FACTORY = newFactory(ToNumberPolicy.DOUBLE); private final Gson gson; + private final ToNumberStrategy toNumberStrategy; ObjectTypeAdapter(Gson gson) { + this(gson, ToNumberPolicy.DOUBLE); + } + + ObjectTypeAdapter(Gson gson, ToNumberStrategy toNumberStrategy) { this.gson = gson; + this.toNumberStrategy = toNumberStrategy; + } + + public static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { + return new TypeAdapterFactory() { + @SuppressWarnings("unchecked") + @Override public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() == Object.class) { + return (TypeAdapter) new ObjectTypeAdapter(gson, toNumberStrategy); + } + return null; + } + }; } @Override public Object read(JsonReader in) throws IOException { @@ -76,7 +88,7 @@ public final class ObjectTypeAdapter extends TypeAdapter { return in.nextString(); case NUMBER: - return in.nextDouble(); + return toNumberStrategy.readNumber(in); case BOOLEAN: return in.nextBoolean(); diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index eec2ec91ca..d537f7ad5b 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -44,12 +44,16 @@ public final class GsonTest extends TestCase { } }; + private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE; + private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER; + public void testOverridesDefaultExcluder() { Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), - new ArrayList(), new ArrayList()); + new ArrayList(), new ArrayList(), + CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY); assertEquals(CUSTOM_EXCLUDER, gson.excluder()); assertEquals(CUSTOM_FIELD_NAMING_STRATEGY, gson.fieldNamingStrategy()); @@ -62,7 +66,8 @@ public void testClonedTypeAdapterFactoryListsAreIndependent() { new HashMap>(), true, false, true, false, true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), - new ArrayList(), new ArrayList()); + new ArrayList(), new ArrayList(), + CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY); Gson clone = original.newBuilder() .registerTypeAdapter(Object.class, new TestTypeAdapter()) diff --git a/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java b/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java new file mode 100644 index 0000000000..d4f77f2905 --- /dev/null +++ b/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.MalformedJsonException; +import junit.framework.TestCase; + +public class ToNumberPolicyTest extends TestCase { + public void testDouble() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.DOUBLE; + assertEquals(10.1, strategy.readNumber(fromString("10.1"))); + assertEquals(3.141592653589793D, strategy.readNumber(fromString("3.141592653589793238462643383279"))); + try { + strategy.readNumber(fromString("1e400")); + fail(); + } catch (MalformedJsonException expected) { + } + } + + public void testLazilyParsedNumber() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.LAZILY_PARSED_NUMBER; + assertEquals(new LazilyParsedNumber("10.1"), strategy.readNumber(fromString("10.1"))); + assertEquals(new LazilyParsedNumber("3.141592653589793238462643383279"), strategy.readNumber(fromString("3.141592653589793238462643383279"))); + assertEquals(new LazilyParsedNumber("1e400"), strategy.readNumber(fromString("1e400"))); + } + + public void testLongOrDouble() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.LONG_OR_DOUBLE; + assertEquals(10L, strategy.readNumber(fromString("10"))); + assertEquals(10.1, strategy.readNumber(fromString("10.1"))); + assertEquals(3.141592653589793D, strategy.readNumber(fromString("3.141592653589793238462643383279"))); + try { + strategy.readNumber(fromString("1e400")); + fail(); + } catch (MalformedJsonException expected) { + } + assertEquals(Double.NaN, strategy.readNumber(fromStringLenient("NaN"))); + assertEquals(Double.POSITIVE_INFINITY, strategy.readNumber(fromStringLenient("Infinity"))); + assertEquals(Double.NEGATIVE_INFINITY, strategy.readNumber(fromStringLenient("-Infinity"))); + try { + strategy.readNumber(fromString("NaN")); + fail(); + } catch (MalformedJsonException expected) { + } + try { + strategy.readNumber(fromString("Infinity")); + fail(); + } catch (MalformedJsonException expected) { + } + try { + strategy.readNumber(fromString("-Infinity")); + fail(); + } catch (MalformedJsonException expected) { + } + } + + public void testBigDecimal() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.BIG_DECIMAL; + assertEquals(new BigDecimal("10.1"), strategy.readNumber(fromString("10.1"))); + assertEquals(new BigDecimal("3.141592653589793238462643383279"), strategy.readNumber(fromString("3.141592653589793238462643383279"))); + assertEquals(new BigDecimal("1e400"), strategy.readNumber(fromString("1e400"))); + } + + public void testNullsAreNeverExpected() throws IOException { + try { + ToNumberPolicy.DOUBLE.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + try { + ToNumberPolicy.LAZILY_PARSED_NUMBER.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + try { + ToNumberPolicy.LONG_OR_DOUBLE.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + try { + ToNumberPolicy.BIG_DECIMAL.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + } + + private static JsonReader fromString(String json) { + return new JsonReader(new StringReader(json)); + } + + private static JsonReader fromStringLenient(String json) { + JsonReader jsonReader = fromString(json); + jsonReader.setLenient(true); + return jsonReader; + } +} diff --git a/gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java b/gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java new file mode 100644 index 0000000000..07d99b812b --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.functional; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; +import com.google.gson.ToNumberStrategy; +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import junit.framework.TestCase; + +public class ToNumberPolicyFunctionalTest extends TestCase { + public void testDefault() { + Gson gson = new Gson(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(10D, gson.fromJson("10", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(new LazilyParsedNumber("10"), gson.fromJson("10", Number.class)); + } + + public void testAsDoubles() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.DOUBLE) + .setNumberToNumberStrategy(ToNumberPolicy.DOUBLE) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(10.0, gson.fromJson("10", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(10.0, gson.fromJson("10", Number.class)); + } + + public void testAsLazilyParsedNumbers() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER) + .setNumberToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(new LazilyParsedNumber("10"), gson.fromJson("10", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(new LazilyParsedNumber("10"), gson.fromJson("10", Number.class)); + } + + public void testAsLongsOrDoubles() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(10L, gson.fromJson("10", Object.class)); + assertEquals(10.0, gson.fromJson("10.0", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(10L, gson.fromJson("10", Number.class)); + assertEquals(10.0, gson.fromJson("10.0", Number.class)); + } + + public void testAsBigDecimals() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL) + .setNumberToNumberStrategy(ToNumberPolicy.BIG_DECIMAL) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(new BigDecimal("10"), gson.fromJson("10", Object.class)); + assertEquals(new BigDecimal("10.0"), gson.fromJson("10.0", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(new BigDecimal("10"), gson.fromJson("10", Number.class)); + assertEquals(new BigDecimal("10.0"), gson.fromJson("10.0", Number.class)); + assertEquals(new BigDecimal("3.141592653589793238462643383279"), gson.fromJson("3.141592653589793238462643383279", BigDecimal.class)); + assertEquals(new BigDecimal("1e400"), gson.fromJson("1e400", BigDecimal.class)); + } + + public void testAsListOfLongsOrDoubles() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create(); + List expected = new LinkedList(); + expected.add(null); + expected.add(10L); + expected.add(10.0); + Type objectCollectionType = new TypeToken>() { }.getType(); + Collection objects = gson.fromJson("[null,10,10.0]", objectCollectionType); + assertEquals(expected, objects); + Type numberCollectionType = new TypeToken>() { }.getType(); + Collection numbers = gson.fromJson("[null,10,10.0]", numberCollectionType); + assertEquals(expected, numbers); + } + + public void testCustomStrategiesCannotAffectConcreteDeclaredNumbers() { + ToNumberStrategy fail = new ToNumberStrategy() { + @Override + public Byte readNumber(JsonReader in) { + throw new UnsupportedOperationException(); + } + }; + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(fail) + .setNumberToNumberStrategy(fail) + .create(); + List numbers = gson.fromJson("[null, 10, 20, 30]", new TypeToken>() {}.getType()); + assertEquals(Arrays.asList(null, (byte) 10, (byte) 20, (byte) 30), numbers); + try { + gson.fromJson("[null, 10, 20, 30]", new TypeToken>() {}.getType()); + fail(); + } catch (UnsupportedOperationException ex) { + } + try { + gson.fromJson("[null, 10, 20, 30]", new TypeToken>() {}.getType()); + fail(); + } catch (UnsupportedOperationException ex) { + } + } +} From f485c518c6e6007b7ca36c478f8a624c6d557588 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 22 Jun 2020 00:41:18 +0200 Subject: [PATCH 2/6] Change wording of ToNumberStrategy documentation --- .../main/java/com/google/gson/ToNumberStrategy.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gson/src/main/java/com/google/gson/ToNumberStrategy.java b/gson/src/main/java/com/google/gson/ToNumberStrategy.java index 32a836d185..cbfb4c7644 100644 --- a/gson/src/main/java/com/google/gson/ToNumberStrategy.java +++ b/gson/src/main/java/com/google/gson/ToNumberStrategy.java @@ -31,9 +31,9 @@ *
  • Lazily parsed number values are returned if the deserialization type is declared as {@link Number}.
  • * * - *

    For historical reasons, Gson does not support deserialization of arbitrary-length numbers as its stated in - * RFC 8259 for {@link Object} and {@link Number} - * causing some data loss while deserialization:

    + *

    For historical reasons, Gson does not support deserialization of arbitrary-length numbers for + * {@link Object} and {@link Number} by default, potentially causing precision loss. However, + * RFC 8259 permits this: * *

      *   This specification allows implementations to set limits on the range
    @@ -49,8 +49,8 @@
      *   for numeric magnitude and precision than is widely available.
      * 
    * - *

    Use for example, {@link ToNumberPolicy#LONG_OR_DOUBLE} or {@link ToNumberPolicy#BIG_DECIMAL} to overcome - * possible data loss.

    + *

    To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or + * {@link ToNumberPolicy#BIG_DECIMAL}.

    * * @see ToNumberPolicy * @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy) From 5644fc71ce26a2599274f0062c849b80186f380a Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 22 Jun 2020 00:46:47 +0200 Subject: [PATCH 3/6] Use inline links in doc sparingly If the element has already been linked before, don't create a link for every subsequent occurrence. See also (slightly dated) https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#inlinelinks --- gson/src/main/java/com/google/gson/ToNumberPolicy.java | 6 +++--- gson/src/main/java/com/google/gson/ToNumberStrategy.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gson/src/main/java/com/google/gson/ToNumberPolicy.java b/gson/src/main/java/com/google/gson/ToNumberPolicy.java index db8b0be85a..1c6f349dc5 100644 --- a/gson/src/main/java/com/google/gson/ToNumberPolicy.java +++ b/gson/src/main/java/com/google/gson/ToNumberPolicy.java @@ -55,9 +55,9 @@ public enum ToNumberPolicy implements ToNumberStrategy { /** * Using this policy will ensure that numbers will be read as {@link Long} or {@link Double} - * values depending on how JSON numbers are represented: {@link Long} if the JSON number can - * be parsed as a {@link Long} value, or otherwise {@link Double} if it can be parsed as a - * {@link Double} value. If the parsed double-precision number results in a positive or negative + * values depending on how JSON numbers are represented: {@code Long} if the JSON number can + * be parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a + * {@code Double} value. If the parsed double-precision number results in a positive or negative * infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the * {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} * is thrown. diff --git a/gson/src/main/java/com/google/gson/ToNumberStrategy.java b/gson/src/main/java/com/google/gson/ToNumberStrategy.java index cbfb4c7644..f96c539bc9 100644 --- a/gson/src/main/java/com/google/gson/ToNumberStrategy.java +++ b/gson/src/main/java/com/google/gson/ToNumberStrategy.java @@ -27,12 +27,12 @@ * *
      *
    • {@link Double} values are returned for JSON numbers if the deserialization type is declared as - * {@link Object};
    • - *
    • Lazily parsed number values are returned if the deserialization type is declared as {@link Number}.
    • + * {@code Object}; + *
    • Lazily parsed number values are returned if the deserialization type is declared as {@code Number}.
    • *
    * *

    For historical reasons, Gson does not support deserialization of arbitrary-length numbers for - * {@link Object} and {@link Number} by default, potentially causing precision loss. However, + * {@code Object} and {@code Number} by default, potentially causing precision loss. However, * RFC 8259 permits this: * *

    
    From 91de9d84e3f6f65ffc02f47f7f6379fa44de4b8d Mon Sep 17 00:00:00 2001
    From: Marcono1234 
    Date: Mon, 22 Jun 2020 00:52:46 +0200
    Subject: [PATCH 4/6] Link to default to-number policies in ToNumberStrategy
     doc
    
    ---
     gson/src/main/java/com/google/gson/ToNumberStrategy.java | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/gson/src/main/java/com/google/gson/ToNumberStrategy.java b/gson/src/main/java/com/google/gson/ToNumberStrategy.java
    index f96c539bc9..db42a4efe6 100644
    --- a/gson/src/main/java/com/google/gson/ToNumberStrategy.java
    +++ b/gson/src/main/java/com/google/gson/ToNumberStrategy.java
    @@ -27,8 +27,9 @@
      *
      * 
      *
    • {@link Double} values are returned for JSON numbers if the deserialization type is declared as - * {@code Object};
    • - *
    • Lazily parsed number values are returned if the deserialization type is declared as {@code Number}.
    • + * {@code Object}, see {@link ToNumberPolicy#DOUBLE}; + *
    • Lazily parsed number values are returned if the deserialization type is declared as {@code Number}, + * see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.
    • *
    * *

    For historical reasons, Gson does not support deserialization of arbitrary-length numbers for From c27dbd3924fd1a9f007efe705f8b780538d21a57 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 22 Jun 2020 01:13:23 +0200 Subject: [PATCH 5/6] Reduce code duplication for deserializing Number --- gson/src/main/java/com/google/gson/Gson.java | 24 +----- .../gson/internal/bind/NumberTypeAdapter.java | 76 +++++++++++++++++++ .../gson/internal/bind/ObjectTypeAdapter.java | 9 +-- .../gson/internal/bind/TypeAdapters.java | 23 ------ 4 files changed, 83 insertions(+), 49 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 5106ab4468..a1e8be127c 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -47,6 +47,7 @@ import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.internal.bind.JsonTreeWriter; import com.google.gson.internal.bind.MapTypeAdapterFactory; +import com.google.gson.internal.bind.NumberTypeAdapter; import com.google.gson.internal.bind.ObjectTypeAdapter; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.internal.bind.TypeAdapters; @@ -379,28 +380,9 @@ private static TypeAdapterFactory objectAdapterFactory(ToNumberStrategy objectTo private static TypeAdapterFactory numberAdapterFactory(final ToNumberStrategy numberToNumberStrategy) { if (numberToNumberStrategy == ToNumberPolicy.LAZILY_PARSED_NUMBER) { - return TypeAdapters.NUMBER_FACTORY; + return NumberTypeAdapter.FACTORY; } - return TypeAdapters.newFactory(Number.class, new TypeAdapter() { - @Override - public Number read(JsonReader in) throws IOException { - JsonToken jsonToken = in.peek(); - switch (jsonToken) { - case NULL: - in.nextNull(); - return null; - case NUMBER: - case STRING: - return numberToNumberStrategy.readNumber(in); - default: - throw new JsonSyntaxException("Expecting number, got: " + jsonToken); - } - } - @Override - public void write(JsonWriter out, Number value) throws IOException { - out.value(value); - } - }); + return NumberTypeAdapter.newFactory(numberToNumberStrategy); } private static TypeAdapter longAdapter(LongSerializationPolicy longSerializationPolicy) { diff --git a/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java new file mode 100644 index 0000000000..139a9ea8b5 --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal.bind; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.ToNumberStrategy; +import com.google.gson.ToNumberPolicy; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +/** + * Type adapter for {@link Number}. + */ +public final class NumberTypeAdapter extends TypeAdapter { + /** + * Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}. + */ + public static final TypeAdapterFactory FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER); + + private final ToNumberStrategy toNumberStrategy; + + private NumberTypeAdapter(ToNumberStrategy toNumberStrategy) { + this.toNumberStrategy = toNumberStrategy; + } + + public static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { + return new TypeAdapterFactory() { + @SuppressWarnings("unchecked") + @Override public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() == Number.class) { + return (TypeAdapter) new NumberTypeAdapter(toNumberStrategy); + } + return null; + } + }; + } + + @Override public Number read(JsonReader in) throws IOException { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { + case NULL: + in.nextNull(); + return null; + case NUMBER: + case STRING: + return toNumberStrategy.readNumber(in); + default: + throw new JsonSyntaxException("Expecting number, got: " + jsonToken); + } + } + + @Override public void write(JsonWriter out, Number value) throws IOException { + out.value(value); + } +} diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java index 6cccc92ecd..aadb919d0b 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -37,16 +37,15 @@ * serialization and a primitive/Map/List on deserialization. */ public final class ObjectTypeAdapter extends TypeAdapter { + /** + * Gson default factory using {@link ToNumberPolicy#DOUBLE}. + */ public static final TypeAdapterFactory FACTORY = newFactory(ToNumberPolicy.DOUBLE); private final Gson gson; private final ToNumberStrategy toNumberStrategy; - ObjectTypeAdapter(Gson gson) { - this(gson, ToNumberPolicy.DOUBLE); - } - - ObjectTypeAdapter(Gson gson, ToNumberStrategy toNumberStrategy) { + private ObjectTypeAdapter(Gson gson, ToNumberStrategy toNumberStrategy) { this.gson = gson; this.toNumberStrategy = toNumberStrategy; } diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 81dda90359..cd5ba2e395 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -343,29 +343,6 @@ public void write(JsonWriter out, Number value) throws IOException { } }; - public static final TypeAdapter NUMBER = new TypeAdapter() { - @Override - public Number read(JsonReader in) throws IOException { - JsonToken jsonToken = in.peek(); - switch (jsonToken) { - case NULL: - in.nextNull(); - return null; - case NUMBER: - case STRING: - return new LazilyParsedNumber(in.nextString()); - default: - throw new JsonSyntaxException("Expecting number, got: " + jsonToken); - } - } - @Override - public void write(JsonWriter out, Number value) throws IOException { - out.value(value); - } - }; - - public static final TypeAdapterFactory NUMBER_FACTORY = newFactory(Number.class, NUMBER); - public static final TypeAdapter CHARACTER = new TypeAdapter() { @Override public Character read(JsonReader in) throws IOException { From 078ef00c49d6cb1c3dc43b5da5e9d4ee2a53040c Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 22 Jun 2020 16:54:41 +0200 Subject: [PATCH 6/6] Hide default factory constants of NumberTypeAdapter and ObjectTypeAdapter This encapsulates the logic a little bit better. Additionally refactored factory created by NumberTypeAdapter to only create TypeAdapter once and then have factory reuse that adapter for better performance. --- gson/src/main/java/com/google/gson/Gson.java | 18 ++---------------- .../gson/internal/bind/NumberTypeAdapter.java | 18 ++++++++++++------ .../gson/internal/bind/ObjectTypeAdapter.java | 12 ++++++++++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index a1e8be127c..1511bbb178 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -227,7 +227,7 @@ public Gson() { // built-in type adapters that cannot be overridden factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); - factories.add(objectAdapterFactory(objectToNumberStrategy)); + factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy)); // the excluder must precede all adapters that handle user-defined types factories.add(excluder); @@ -247,7 +247,7 @@ public Gson() { doubleAdapter(serializeSpecialFloatingPointValues))); factories.add(TypeAdapters.newFactory(float.class, Float.class, floatAdapter(serializeSpecialFloatingPointValues))); - factories.add(numberAdapterFactory(numberToNumberStrategy)); + factories.add(NumberTypeAdapter.getFactory(numberToNumberStrategy)); factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY); factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY); factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter))); @@ -371,20 +371,6 @@ static void checkValidFloatingPoint(double value) { } } - private static TypeAdapterFactory objectAdapterFactory(ToNumberStrategy objectToNumberStrategy) { - if (objectToNumberStrategy == ToNumberPolicy.DOUBLE) { - return ObjectTypeAdapter.FACTORY; - } - return ObjectTypeAdapter.newFactory(objectToNumberStrategy); - } - - private static TypeAdapterFactory numberAdapterFactory(final ToNumberStrategy numberToNumberStrategy) { - if (numberToNumberStrategy == ToNumberPolicy.LAZILY_PARSED_NUMBER) { - return NumberTypeAdapter.FACTORY; - } - return NumberTypeAdapter.newFactory(numberToNumberStrategy); - } - private static TypeAdapter longAdapter(LongSerializationPolicy longSerializationPolicy) { if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) { return TypeAdapters.LONG; diff --git a/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java index 139a9ea8b5..f5efff2825 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java @@ -36,7 +36,7 @@ public final class NumberTypeAdapter extends TypeAdapter { /** * Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}. */ - public static final TypeAdapterFactory FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER); + private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER); private final ToNumberStrategy toNumberStrategy; @@ -44,18 +44,24 @@ private NumberTypeAdapter(ToNumberStrategy toNumberStrategy) { this.toNumberStrategy = toNumberStrategy; } - public static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { + private static TypeAdapterFactory newFactory(ToNumberStrategy toNumberStrategy) { + final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy); return new TypeAdapterFactory() { @SuppressWarnings("unchecked") @Override public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getRawType() == Number.class) { - return (TypeAdapter) new NumberTypeAdapter(toNumberStrategy); - } - return null; + return type.getRawType() == Number.class ? (TypeAdapter) adapter : null; } }; } + public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { + if (toNumberStrategy == ToNumberPolicy.LAZILY_PARSED_NUMBER) { + return LAZILY_PARSED_NUMBER_FACTORY; + } else { + return newFactory(toNumberStrategy); + } + } + @Override public Number read(JsonReader in) throws IOException { JsonToken jsonToken = in.peek(); switch (jsonToken) { diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java index aadb919d0b..b50f61e12e 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -40,7 +40,7 @@ public final class ObjectTypeAdapter extends TypeAdapter { /** * Gson default factory using {@link ToNumberPolicy#DOUBLE}. */ - public static final TypeAdapterFactory FACTORY = newFactory(ToNumberPolicy.DOUBLE); + private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE); private final Gson gson; private final ToNumberStrategy toNumberStrategy; @@ -50,7 +50,7 @@ private ObjectTypeAdapter(Gson gson, ToNumberStrategy toNumberStrategy) { this.toNumberStrategy = toNumberStrategy; } - public static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { + private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { return new TypeAdapterFactory() { @SuppressWarnings("unchecked") @Override public TypeAdapter create(Gson gson, TypeToken type) { @@ -62,6 +62,14 @@ public static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrat }; } + public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { + if (toNumberStrategy == ToNumberPolicy.DOUBLE) { + return DOUBLE_FACTORY; + } else { + return newFactory(toNumberStrategy); + } + } + @Override public Object read(JsonReader in) throws IOException { JsonToken token = in.peek(); switch (token) {