Added a GsonBuilder setting to enable serialization of special double types, NaN, Infinity, and -Infinity.

This commit is contained in:
Inderjeet Singh 2008-12-20 01:26:14 +00:00
parent 362a94ec74
commit 51881c7f4a
5 changed files with 163 additions and 42 deletions

View File

@ -111,10 +111,6 @@ final class DefaultTypeAdapters {
map.register(byte.class, BYTE_TYPE_ADAPTER);
map.register(Character.class, CHARACTER_TYPE_ADAPTER);
map.register(char.class, CHARACTER_TYPE_ADAPTER);
map.register(Double.class, DOUBLE_TYPE_ADAPTER);
map.register(double.class, DOUBLE_TYPE_ADAPTER);
map.register(Float.class, FLOAT_TYPE_ADAPTER);
map.register(float.class, FLOAT_TYPE_ADAPTER);
map.register(Integer.class, INTEGER_TYPE_ADAPTER);
map.register(int.class, INTEGER_TYPE_ADAPTER);
map.register(Long.class, LONG_TYPE_ADAPTER);
@ -211,6 +207,18 @@ final class DefaultTypeAdapters {
return new JsonDeserializerExceptionWrapper(deserializer);
}
static void registerSerializersForFloatingPoints(boolean serializeSpecialFloatingPointValues,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers) {
DefaultTypeAdapters.DoubleSerializer doubleSerializer =
new DefaultTypeAdapters.DoubleSerializer(serializeSpecialFloatingPointValues);
DefaultTypeAdapters.FloatSerializer floatSerializer =
new DefaultTypeAdapters.FloatSerializer(serializeSpecialFloatingPointValues);
serializers.registerIfAbsent(Double.class, doubleSerializer);
serializers.registerIfAbsent(double.class, doubleSerializer);
serializers.registerIfAbsent(Float.class, floatSerializer);
serializers.registerIfAbsent(float.class, floatSerializer);
}
static class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
private final DateFormat format;
@ -640,14 +648,26 @@ final class DefaultTypeAdapters {
}
}
private static class FloatTypeAdapter
implements InstanceCreator<Float>, JsonSerializer<Float>, JsonDeserializer<Float> {
static class FloatSerializer implements JsonSerializer<Float> {
private final boolean serializeSpecialFloatingPointValues;
FloatSerializer(boolean serializeSpecialDoubleValues) {
this.serializeSpecialFloatingPointValues = serializeSpecialDoubleValues;
}
public JsonElement serialize(Float src, Type typeOfSrc, JsonSerializationContext context) {
if (Float.isNaN(src) || Float.isInfinite(src)) {
throw new IllegalArgumentException(src + " is not a valid double value as per JavaScript specification.");
if (!serializeSpecialFloatingPointValues) {
if (Float.isNaN(src) || Float.isInfinite(src)) {
throw new IllegalArgumentException(src
+ " is not a valid float value as per JSON specification. To override this"
+ " behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method.");
}
}
return new JsonPrimitive(src);
}
}
private static class FloatTypeAdapter implements InstanceCreator<Float>, JsonDeserializer<Float> {
public Float deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
@ -664,14 +684,26 @@ final class DefaultTypeAdapters {
}
}
private static class DoubleTypeAdapter
implements InstanceCreator<Double>, JsonSerializer<Double>, JsonDeserializer<Double> {
static class DoubleSerializer implements JsonSerializer<Double> {
private final boolean serializeSpecialFloatingPointValues;
DoubleSerializer(boolean serializeSpecialDoubleValues) {
this.serializeSpecialFloatingPointValues = serializeSpecialDoubleValues;
}
public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
if (Double.isNaN(src) || Double.isInfinite(src)) {
throw new IllegalArgumentException(src + " is not a valid double value as per JavaScript specification.");
if (!serializeSpecialFloatingPointValues) {
if (Double.isNaN(src) || Double.isInfinite(src)) {
throw new IllegalArgumentException(src
+ " is not a valid double value as per JSON specification. To override this"
+ " behavior, use GsonBuilder.serializeSpecialDoubleValues() method.");
}
}
return new JsonPrimitive(src);
}
}
private static class DoubleTypeAdapter implements InstanceCreator<Double>,
JsonDeserializer<Double> {
public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {

View File

@ -141,9 +141,10 @@ public final class Gson {
* encountering inner class references.
*/
Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy) {
this(strategy, fieldNamingPolicy, createObjectConstructor(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS),
this(strategy, fieldNamingPolicy,
createObjectConstructor(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS),
DEFAULT_JSON_FORMATTER, false,
DefaultTypeAdapters.DEFAULT_SERIALIZERS, DefaultTypeAdapters.DEFAULT_DESERIALIZERS);
getDefaultSerializers(), DefaultTypeAdapters.DEFAULT_DESERIALIZERS);
}
Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy,
@ -159,6 +160,13 @@ public final class Gson {
this.deserializers = deserializers;
}
private static ParameterizedTypeHandlerMap<JsonSerializer<?>> getDefaultSerializers() {
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers =
DefaultTypeAdapters.DEFAULT_SERIALIZERS.copyOf();
DefaultTypeAdapters.registerSerializersForFloatingPoints(false, serializers);
return serializers;
}
static MappedObjectConstructor createObjectConstructor(
ParameterizedTypeHandlerMap<InstanceCreator<?>> instanceCreators) {
MappedObjectConstructor objectConstructor = new MappedObjectConstructor();

View File

@ -64,6 +64,7 @@ public final class GsonBuilder {
private String datePattern;
private int dateStyle;
private int timeStyle;
private boolean serializeSpecialFloatingPointValues;
/**
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
@ -85,6 +86,7 @@ public final class GsonBuilder {
serializeNulls = false;
dateStyle = DateFormat.DEFAULT;
timeStyle = DateFormat.DEFAULT;
serializeSpecialFloatingPointValues = false;
}
/**
@ -322,6 +324,31 @@ public final class GsonBuilder {
return this;
}
/**
* Section 2.4 of <a href="http://www.ietf.org/rfc/rfc4627.txt">JSON specification</a> disallows
* special double values (NaN, Infinity, -Infinity). However,
* <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
* specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
* values. Moreover, most JavaScript engines will accept these special values in JSON without
* problem. So, at a practical level, it makes sense to accept these values as valid JSON even
* though JSON specification disallows them.
*
* <p>Gson always accepts these special values during deserialization. However, it outputs
* strictly compliant JSON. Hence, if it encounters a float value {@link Float.NaN},
* {@link Float.POSITIVE_INFINITY}, {@link Float.NEGATIVE_INFINITY}, or a double value
* {@link Double.NaN}, {@link Double.POSITIVE_INFINITY}, {@link Double.NEGATIVE_INFINITY}, it
* will throw an {@link IllegalArgumentException}. This method provides a way to override the
* default behavior when you know that the JSON receiver will be able to handle these special
* values.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @Since 1.3
*/
public GsonBuilder serializeSpecialFloatingPointValues() {
this.serializeSpecialFloatingPointValues = true;
return this;
}
/**
* Creates a {@link Gson} instance based on the current configuration. This method is free of
* side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
@ -346,6 +373,8 @@ public final class GsonBuilder {
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, customSerializers,
customDeserializers);
customSerializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_SERIALIZERS);
DefaultTypeAdapters.registerSerializersForFloatingPoints(serializeSpecialFloatingPointValues,
customSerializers);
customDeserializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_DESERIALIZERS);
ParameterizedTypeHandlerMap<InstanceCreator<?>> customInstanceCreators =

View File

@ -58,6 +58,15 @@ final class ParameterizedTypeHandlerMap<T> {
}
}
public synchronized void registerIfAbsent(Type typeOfT, T value) {
if (!modifiable) {
throw new IllegalStateException("Attempted to modify an unmodifiable map.");
}
if (!map.containsKey(typeOfT)) {
register(typeOfT, value);
}
}
public synchronized void makeUnmodifiable() {
modifiable = false;
}

View File

@ -349,8 +349,8 @@ public class PrimitiveTest extends TestCase {
return json.substring(json.indexOf('[') + 1, json.indexOf(']'));
}
public void testDoubleNaNSerializationNotSupported() {
double nan = (double) Double.NaN;
public void testDoubleNaNSerializationNotSupportedByDefault() {
double nan = Double.NaN;
try {
gson.toJson(nan);
gson.toJson(Double.NaN);
@ -359,12 +359,20 @@ public class PrimitiveTest extends TestCase {
}
}
public void testDoubleNaNDeserializationNotSupported() {
public void testDoubleNaNSerialization() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
double nan = Double.NaN;
assertEquals("NaN", gson.toJson(nan));
assertEquals("NaN", gson.toJson(Double.NaN));
}
public void testDoubleNaNDeserialization() {
assertTrue(Double.isNaN(gson.fromJson("NaN", Double.class)));
assertTrue(Double.isNaN(gson.fromJson("NaN", double.class)));
}
public void testFloatNaNSerializationNotSupported() {
float nan = (float) Float.NaN;
public void testFloatNaNSerializationNotSupportedByDefault() {
float nan = Float.NaN;
try {
gson.toJson(nan);
gson.toJson(Float.NaN);
@ -373,7 +381,14 @@ public class PrimitiveTest extends TestCase {
}
}
public void testFloatNaNDeserializationNotSupported() {
public void testFloatNaNSerialization() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
float nan = Float.NaN;
assertEquals("NaN", gson.toJson(nan));
assertEquals("NaN", gson.toJson(Float.NaN));
}
public void testFloatNaNDeserialization() {
assertTrue(Float.isNaN(gson.fromJson("NaN", Float.class)));
assertTrue(Float.isNaN(gson.fromJson("NaN", float.class)));
}
@ -381,37 +396,51 @@ public class PrimitiveTest extends TestCase {
public void testBigDecimalNaNDeserializationNotSupported() {
try {
gson.fromJson("NaN", BigDecimal.class);
fail("Gson should not accept NaN for deserialization");
fail("Gson should not accept NaN for deserialization by default.");
} catch (JsonParseException expected) {
}
}
public void testDoubleInfinitySerializationNotSupported() {
double infinity = (double)Double.POSITIVE_INFINITY;
public void testDoubleInfinitySerializationNotSupportedByDefault() {
double infinity = Double.POSITIVE_INFINITY;
try {
gson.toJson(infinity);
gson.toJson(Double.POSITIVE_INFINITY);
fail("Gson should not accept positive infinity for serialization");
fail("Gson should not accept positive infinity for serialization by default.");
} catch (IllegalArgumentException expected) {
}
}
public void testDoubleInfinityDeserializationNotSupported() {
public void testDoubleInfinitySerialization() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
double infinity = Double.POSITIVE_INFINITY;
assertEquals("Infinity", gson.toJson(infinity));
assertEquals("Infinity", gson.toJson(Double.POSITIVE_INFINITY));
}
public void testDoubleInfinityDeserialization() {
assertTrue(Double.isInfinite(gson.fromJson("Infinity", Double.class)));
assertTrue(Double.isInfinite(gson.fromJson("Infinity", double.class)));
}
public void testFloatInfinitySerializationNotSupported() {
float infinity = (float) Float.POSITIVE_INFINITY;
public void testFloatInfinitySerializationNotSupportedByDefault() {
float infinity = Float.POSITIVE_INFINITY;
try {
gson.toJson(infinity);
gson.toJson(Float.POSITIVE_INFINITY);
fail("Gson should not accept positive infinity for serialization");
fail("Gson should not accept positive infinity for serialization by default");
} catch (IllegalArgumentException expected) {
}
}
public void testFloatInfinityDeserializationNotSupported() {
public void testFloatInfinitySerialization() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
float infinity = Float.POSITIVE_INFINITY;
assertEquals("Infinity", gson.toJson(infinity));
assertEquals("Infinity", gson.toJson(Float.POSITIVE_INFINITY));
}
public void testFloatInfinityDeserialization() {
assertTrue(Float.isInfinite(gson.fromJson("Infinity", Float.class)));
assertTrue(Float.isInfinite(gson.fromJson("Infinity", float.class)));
}
@ -419,42 +448,56 @@ public class PrimitiveTest extends TestCase {
public void testBigDecimalInfinityDeserializationNotSupported() {
try {
gson.fromJson("Infinity", BigDecimal.class);
fail("Gson should not accept positive infinity for deserialization");
fail("Gson should not accept positive infinity for deserialization with BigDecimal");
} catch (JsonParseException expected) {
}
}
public void testNegativeInfinitySerializationNotSupported() {
double negativeInfinity = (double)Double.NEGATIVE_INFINITY;
public void testNegativeInfinitySerializationNotSupportedByDefault() {
double negativeInfinity = Double.NEGATIVE_INFINITY;
try {
gson.toJson(negativeInfinity);
gson.toJson(Double.NEGATIVE_INFINITY);
fail("Gson should not accept positive infinity for serialization");
fail("Gson should not accept negative infinity for serialization by default");
} catch (IllegalArgumentException expected) {
}
}
public void testNegativeInfinityDeserializationNotSupported() {
public void testNegativeInfinitySerialization() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
double negativeInfinity = Double.NEGATIVE_INFINITY;
assertEquals("-Infinity", gson.toJson(negativeInfinity));
assertEquals("-Infinity", gson.toJson(Double.NEGATIVE_INFINITY));
}
public void testNegativeInfinityDeserialization() {
assertTrue(Double.isInfinite(gson.fromJson("-Infinity", double.class)));
assertTrue(Double.isInfinite(gson.fromJson("-Infinity", Double.class)));
}
public void testNegativeInfinityFloatSerializationNotSupported() {
float negativeInfinity = (float) Float.NEGATIVE_INFINITY;
public void testNegativeInfinityFloatSerializationNotSupportedByDefault() {
float negativeInfinity = Float.NEGATIVE_INFINITY;
try {
gson.toJson(negativeInfinity);
gson.toJson(Float.NEGATIVE_INFINITY);
fail("Gson should not accept positive infinity for serialization");
fail("Gson should not accept negative infinity for serialization by default");
} catch (IllegalArgumentException expected) {
}
}
public void testNegativeInfinityFloatDeserializationNotSupported() {
public void testNegativeInfinityFloatSerialization() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
float negativeInfinity = Float.NEGATIVE_INFINITY;
assertEquals("-Infinity", gson.toJson(negativeInfinity));
assertEquals("-Infinity", gson.toJson(Float.NEGATIVE_INFINITY));
}
public void testNegativeInfinityFloatDeserialization() {
assertTrue(Float.isInfinite(gson.fromJson("-Infinity", float.class)));
assertTrue(Float.isInfinite(gson.fromJson("-Infinity", Float.class)));
}
public void testNegativeInfinityBigDecimalDeserializationNotSupported() {
public void testBigDecimalNegativeInfinityDeserializationNotSupported() {
try {
gson.fromJson("-Infinity", BigDecimal.class);
fail("Gson should not accept positive infinity for deserialization");