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(byte.class, BYTE_TYPE_ADAPTER);
map.register(Character.class, CHARACTER_TYPE_ADAPTER); map.register(Character.class, CHARACTER_TYPE_ADAPTER);
map.register(char.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(Integer.class, INTEGER_TYPE_ADAPTER);
map.register(int.class, INTEGER_TYPE_ADAPTER); map.register(int.class, INTEGER_TYPE_ADAPTER);
map.register(Long.class, LONG_TYPE_ADAPTER); map.register(Long.class, LONG_TYPE_ADAPTER);
@ -211,6 +207,18 @@ final class DefaultTypeAdapters {
return new JsonDeserializerExceptionWrapper(deserializer); 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> { static class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
private final DateFormat format; private final DateFormat format;
@ -640,14 +648,26 @@ final class DefaultTypeAdapters {
} }
} }
private static class FloatTypeAdapter static class FloatSerializer implements JsonSerializer<Float> {
implements InstanceCreator<Float>, JsonSerializer<Float>, JsonDeserializer<Float> { private final boolean serializeSpecialFloatingPointValues;
FloatSerializer(boolean serializeSpecialDoubleValues) {
this.serializeSpecialFloatingPointValues = serializeSpecialDoubleValues;
}
public JsonElement serialize(Float src, Type typeOfSrc, JsonSerializationContext context) { public JsonElement serialize(Float src, Type typeOfSrc, JsonSerializationContext context) {
if (Float.isNaN(src) || Float.isInfinite(src)) { if (!serializeSpecialFloatingPointValues) {
throw new IllegalArgumentException(src + " is not a valid double value as per JavaScript specification."); 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); return new JsonPrimitive(src);
} }
}
private static class FloatTypeAdapter implements InstanceCreator<Float>, JsonDeserializer<Float> {
public Float deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) public Float deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException { throws JsonParseException {
@ -664,15 +684,27 @@ final class DefaultTypeAdapters {
} }
} }
private static class DoubleTypeAdapter static class DoubleSerializer implements JsonSerializer<Double> {
implements InstanceCreator<Double>, JsonSerializer<Double>, JsonDeserializer<Double> { private final boolean serializeSpecialFloatingPointValues;
DoubleSerializer(boolean serializeSpecialDoubleValues) {
this.serializeSpecialFloatingPointValues = serializeSpecialDoubleValues;
}
public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) { public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
if (Double.isNaN(src) || Double.isInfinite(src)) { if (!serializeSpecialFloatingPointValues) {
throw new IllegalArgumentException(src + " is not a valid double value as per JavaScript specification."); 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); return new JsonPrimitive(src);
} }
}
private static class DoubleTypeAdapter implements InstanceCreator<Double>,
JsonDeserializer<Double> {
public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException { throws JsonParseException {
return json.getAsDouble(); return json.getAsDouble();

View File

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

View File

@ -64,6 +64,7 @@ public final class GsonBuilder {
private String datePattern; private String datePattern;
private int dateStyle; private int dateStyle;
private int timeStyle; private int timeStyle;
private boolean serializeSpecialFloatingPointValues;
/** /**
* Creates a GsonBuilder instance that can be used to build Gson with various configuration * Creates a GsonBuilder instance that can be used to build Gson with various configuration
@ -85,6 +86,7 @@ public final class GsonBuilder {
serializeNulls = false; serializeNulls = false;
dateStyle = DateFormat.DEFAULT; dateStyle = DateFormat.DEFAULT;
timeStyle = DateFormat.DEFAULT; timeStyle = DateFormat.DEFAULT;
serializeSpecialFloatingPointValues = false;
} }
/** /**
@ -322,6 +324,31 @@ public final class GsonBuilder {
return this; 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 * 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. * side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
@ -346,8 +373,10 @@ public final class GsonBuilder {
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, customSerializers, addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, customSerializers,
customDeserializers); customDeserializers);
customSerializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_SERIALIZERS); customSerializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_SERIALIZERS);
DefaultTypeAdapters.registerSerializersForFloatingPoints(serializeSpecialFloatingPointValues,
customSerializers);
customDeserializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_DESERIALIZERS); customDeserializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_DESERIALIZERS);
ParameterizedTypeHandlerMap<InstanceCreator<?>> customInstanceCreators = ParameterizedTypeHandlerMap<InstanceCreator<?>> customInstanceCreators =
instanceCreators.copyOf(); instanceCreators.copyOf();
customInstanceCreators.registerIfAbsent(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS); customInstanceCreators.registerIfAbsent(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS);
@ -357,7 +386,7 @@ public final class GsonBuilder {
formatter, serializeNulls, customSerializers, customDeserializers); formatter, serializeNulls, customSerializers, customDeserializers);
return gson; return gson;
} }
private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers, ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers,
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers) { ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers) {

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() { public synchronized void makeUnmodifiable() {
modifiable = false; modifiable = false;
} }

View File

@ -349,8 +349,8 @@ public class PrimitiveTest extends TestCase {
return json.substring(json.indexOf('[') + 1, json.indexOf(']')); return json.substring(json.indexOf('[') + 1, json.indexOf(']'));
} }
public void testDoubleNaNSerializationNotSupported() { public void testDoubleNaNSerializationNotSupportedByDefault() {
double nan = (double) Double.NaN; double nan = Double.NaN;
try { try {
gson.toJson(nan); gson.toJson(nan);
gson.toJson(Double.NaN); gson.toJson(Double.NaN);
@ -358,13 +358,21 @@ public class PrimitiveTest extends TestCase {
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
} }
} }
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 testDoubleNaNDeserializationNotSupported() { public void testDoubleNaNDeserialization() {
assertTrue(Double.isNaN(gson.fromJson("NaN", Double.class))); assertTrue(Double.isNaN(gson.fromJson("NaN", Double.class)));
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 { try {
gson.toJson(nan); gson.toJson(nan);
gson.toJson(Float.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)));
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() { public void testBigDecimalNaNDeserializationNotSupported() {
try { try {
gson.fromJson("NaN", BigDecimal.class); 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) { } catch (JsonParseException expected) {
} }
} }
public void testDoubleInfinitySerializationNotSupported() { public void testDoubleInfinitySerializationNotSupportedByDefault() {
double infinity = (double)Double.POSITIVE_INFINITY; double infinity = Double.POSITIVE_INFINITY;
try { try {
gson.toJson(infinity); gson.toJson(infinity);
gson.toJson(Double.POSITIVE_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) { } 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)));
assertTrue(Double.isInfinite(gson.fromJson("Infinity", double.class))); assertTrue(Double.isInfinite(gson.fromJson("Infinity", double.class)));
} }
public void testFloatInfinitySerializationNotSupported() { public void testFloatInfinitySerializationNotSupportedByDefault() {
float infinity = (float) Float.POSITIVE_INFINITY; float infinity = Float.POSITIVE_INFINITY;
try { try {
gson.toJson(infinity); gson.toJson(infinity);
gson.toJson(Float.POSITIVE_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) { } 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)));
assertTrue(Float.isInfinite(gson.fromJson("Infinity", float.class))); assertTrue(Float.isInfinite(gson.fromJson("Infinity", float.class)));
} }
@ -419,46 +448,60 @@ public class PrimitiveTest extends TestCase {
public void testBigDecimalInfinityDeserializationNotSupported() { public void testBigDecimalInfinityDeserializationNotSupported() {
try { try {
gson.fromJson("Infinity", BigDecimal.class); 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) { } catch (JsonParseException expected) {
} }
} }
public void testNegativeInfinitySerializationNotSupported() { public void testNegativeInfinitySerializationNotSupportedByDefault() {
double negativeInfinity = (double)Double.NEGATIVE_INFINITY; double negativeInfinity = Double.NEGATIVE_INFINITY;
try { try {
gson.toJson(negativeInfinity); gson.toJson(negativeInfinity);
gson.toJson(Double.NEGATIVE_INFINITY); 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) { } 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)));
assertTrue(Double.isInfinite(gson.fromJson("-Infinity", Double.class))); assertTrue(Double.isInfinite(gson.fromJson("-Infinity", Double.class)));
} }
public void testNegativeInfinityFloatSerializationNotSupported() { public void testNegativeInfinityFloatSerializationNotSupportedByDefault() {
float negativeInfinity = (float) Float.NEGATIVE_INFINITY; float negativeInfinity = Float.NEGATIVE_INFINITY;
try { try {
gson.toJson(negativeInfinity); gson.toJson(negativeInfinity);
gson.toJson(Float.NEGATIVE_INFINITY); 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) { } 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)));
assertTrue(Float.isInfinite(gson.fromJson("-Infinity", Float.class))); assertTrue(Float.isInfinite(gson.fromJson("-Infinity", Float.class)));
} }
public void testNegativeInfinityBigDecimalDeserializationNotSupported() { public void testBigDecimalNegativeInfinityDeserializationNotSupported() {
try { try {
gson.fromJson("-Infinity", BigDecimal.class); gson.fromJson("-Infinity", BigDecimal.class);
fail("Gson should not accept positive infinity for deserialization"); fail("Gson should not accept positive infinity for deserialization");
} catch (JsonParseException expected) { } catch (JsonParseException expected) {
} }
} }
} }