Support arbitrary Number implementation for Object and Number deserialization (#1290)
* Object and Number type adapters number deserialization can be configured * Change wording of ToNumberStrategy documentation * 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 * Link to default to-number policies in ToNumberStrategy doc * Reduce code duplication for deserializing Number * 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. Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
This commit is contained in:
parent
1cc1627423
commit
fe30b85224
@ -47,6 +47,7 @@ import com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory;
|
||||
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;
|
||||
@ -146,6 +147,8 @@ public final class Gson {
|
||||
final LongSerializationPolicy longSerializationPolicy;
|
||||
final List<TypeAdapterFactory> builderFactories;
|
||||
final List<TypeAdapterFactory> builderHierarchyFactories;
|
||||
final ToNumberStrategy objectToNumberStrategy;
|
||||
final ToNumberStrategy numberToNumberStrategy;
|
||||
|
||||
/**
|
||||
* Constructs a Gson object with default configuration. The default configuration has the
|
||||
@ -188,7 +191,7 @@ public final class Gson {
|
||||
DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
|
||||
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT,
|
||||
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
|
||||
Collections.<TypeAdapterFactory>emptyList());
|
||||
Collections.<TypeAdapterFactory>emptyList(), ToNumberPolicy.DOUBLE, ToNumberPolicy.LAZILY_PARSED_NUMBER);
|
||||
}
|
||||
|
||||
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
|
||||
@ -198,7 +201,8 @@ public final class Gson {
|
||||
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
|
||||
int timeStyle, List<TypeAdapterFactory> builderFactories,
|
||||
List<TypeAdapterFactory> builderHierarchyFactories,
|
||||
List<TypeAdapterFactory> factoriesToBeAdded) {
|
||||
List<TypeAdapterFactory> factoriesToBeAdded,
|
||||
ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) {
|
||||
this.excluder = excluder;
|
||||
this.fieldNamingStrategy = fieldNamingStrategy;
|
||||
this.instanceCreators = instanceCreators;
|
||||
@ -216,12 +220,14 @@ public final class Gson {
|
||||
this.timeStyle = timeStyle;
|
||||
this.builderFactories = builderFactories;
|
||||
this.builderHierarchyFactories = builderHierarchyFactories;
|
||||
this.objectToNumberStrategy = objectToNumberStrategy;
|
||||
this.numberToNumberStrategy = numberToNumberStrategy;
|
||||
|
||||
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
|
||||
|
||||
// built-in type adapters that cannot be overridden
|
||||
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
|
||||
factories.add(ObjectTypeAdapter.FACTORY);
|
||||
factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy));
|
||||
|
||||
// the excluder must precede all adapters that handle user-defined types
|
||||
factories.add(excluder);
|
||||
@ -241,7 +247,7 @@ public final class Gson {
|
||||
doubleAdapter(serializeSpecialFloatingPointValues)));
|
||||
factories.add(TypeAdapters.newFactory(float.class, Float.class,
|
||||
floatAdapter(serializeSpecialFloatingPointValues)));
|
||||
factories.add(TypeAdapters.NUMBER_FACTORY);
|
||||
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)));
|
||||
|
@ -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 final class GsonBuilder {
|
||||
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 final class GsonBuilder {
|
||||
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,
|
||||
|
99
gson/src/main/java/com/google/gson/ToNumberPolicy.java
Normal file
99
gson/src/main/java/com/google/gson/ToNumberPolicy.java
Normal file
@ -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: {@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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
71
gson/src/main/java/com/google/gson/ToNumberStrategy.java
Normal file
71
gson/src/main/java/com/google/gson/ToNumberStrategy.java
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Double} values are returned for JSON numbers if the deserialization type is declared as
|
||||
* {@code Object}, see {@link ToNumberPolicy#DOUBLE};</li>
|
||||
* <li>Lazily parsed number values are returned if the deserialization type is declared as {@code Number},
|
||||
* see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>For historical reasons, Gson does not support deserialization of arbitrary-length numbers for
|
||||
* {@code Object} and {@code Number} by default, potentially causing precision loss. However,
|
||||
* <a href="https://tools.ietf.org/html/rfc8259#section-6">RFC 8259</a> permits this:
|
||||
*
|
||||
* <pre>
|
||||
* 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.
|
||||
* </pre>
|
||||
*
|
||||
* <p>To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or
|
||||
* {@link ToNumberPolicy#BIG_DECIMAL}.</p>
|
||||
*
|
||||
* @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;
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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<Number> {
|
||||
/**
|
||||
* Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.
|
||||
*/
|
||||
private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER);
|
||||
|
||||
private final ToNumberStrategy toNumberStrategy;
|
||||
|
||||
private NumberTypeAdapter(ToNumberStrategy toNumberStrategy) {
|
||||
this.toNumberStrategy = toNumberStrategy;
|
||||
}
|
||||
|
||||
private static TypeAdapterFactory newFactory(ToNumberStrategy toNumberStrategy) {
|
||||
final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy);
|
||||
return new TypeAdapterFactory() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
return type.getRawType() == Number.class ? (TypeAdapter<T>) 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) {
|
||||
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);
|
||||
}
|
||||
}
|
@ -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,37 @@ import java.util.Map;
|
||||
* serialization and a primitive/Map/List on deserialization.
|
||||
*/
|
||||
public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
if (type.getRawType() == Object.class) {
|
||||
return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Gson default factory using {@link ToNumberPolicy#DOUBLE}.
|
||||
*/
|
||||
private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE);
|
||||
|
||||
private final Gson gson;
|
||||
private final ToNumberStrategy toNumberStrategy;
|
||||
|
||||
ObjectTypeAdapter(Gson gson) {
|
||||
private ObjectTypeAdapter(Gson gson, ToNumberStrategy toNumberStrategy) {
|
||||
this.gson = gson;
|
||||
this.toNumberStrategy = toNumberStrategy;
|
||||
}
|
||||
|
||||
private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) {
|
||||
return new TypeAdapterFactory() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
if (type.getRawType() == Object.class) {
|
||||
return (TypeAdapter<T>) new ObjectTypeAdapter(gson, toNumberStrategy);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -76,7 +95,7 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||
return in.nextString();
|
||||
|
||||
case NUMBER:
|
||||
return in.nextDouble();
|
||||
return toNumberStrategy.readNumber(in);
|
||||
|
||||
case BOOLEAN:
|
||||
return in.nextBoolean();
|
||||
|
@ -343,29 +343,6 @@ public final class TypeAdapters {
|
||||
}
|
||||
};
|
||||
|
||||
public static final TypeAdapter<Number> NUMBER = new TypeAdapter<Number>() {
|
||||
@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> CHARACTER = new TypeAdapter<Character>() {
|
||||
@Override
|
||||
public Character read(JsonReader in) throws IOException {
|
||||
|
@ -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<Type, InstanceCreator<?>>(), true, false, true, false,
|
||||
true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>());
|
||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
||||
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 final class GsonTest extends TestCase {
|
||||
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
|
||||
true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>());
|
||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
||||
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY);
|
||||
|
||||
Gson clone = original.newBuilder()
|
||||
.registerTypeAdapter(Object.class, new TestTypeAdapter())
|
||||
|
115
gson/src/test/java/com/google/gson/ToNumberPolicyTest.java
Normal file
115
gson/src/test/java/com/google/gson/ToNumberPolicyTest.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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<Object> expected = new LinkedList<Object>();
|
||||
expected.add(null);
|
||||
expected.add(10L);
|
||||
expected.add(10.0);
|
||||
Type objectCollectionType = new TypeToken<Collection<Object>>() { }.getType();
|
||||
Collection<Object> objects = gson.fromJson("[null,10,10.0]", objectCollectionType);
|
||||
assertEquals(expected, objects);
|
||||
Type numberCollectionType = new TypeToken<Collection<Number>>() { }.getType();
|
||||
Collection<Object> 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<Object> numbers = gson.fromJson("[null, 10, 20, 30]", new TypeToken<List<Byte>>() {}.getType());
|
||||
assertEquals(Arrays.asList(null, (byte) 10, (byte) 20, (byte) 30), numbers);
|
||||
try {
|
||||
gson.fromJson("[null, 10, 20, 30]", new TypeToken<List<Object>>() {}.getType());
|
||||
fail();
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
}
|
||||
try {
|
||||
gson.fromJson("[null, 10, 20, 30]", new TypeToken<List<Number>>() {}.getType());
|
||||
fail();
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user