From 458f2baa2f79bbfe78d3db3dfa1dd7866d7cf7a9 Mon Sep 17 00:00:00 2001 From: Joel Leitch Date: Sun, 28 Dec 2008 03:23:36 +0000 Subject: [PATCH] Added special serialization of "Long". Now the client has the ability to output a long field as a JSON "String". This is useful for JavaScript clients that need to handle long values. As well, this change does a major clean up of the custom type adapter handling and ParameterizedTypeMap creation. --- .../com/google/gson/DefaultTypeAdapters.java | 107 +++++++++++++----- gson/src/main/java/com/google/gson/Gson.java | 13 +-- .../java/com/google/gson/GsonBuilder.java | 32 ++++-- .../functional/ParameterizedTypesTest.java | 14 +-- .../google/gson/functional/PrimitiveTest.java | 19 ++++ 5 files changed, 128 insertions(+), 57 deletions(-) diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index 615606f7..8594559d 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java @@ -53,7 +53,8 @@ import java.util.UUID; final class DefaultTypeAdapters { private static final DefaultDateTypeAdapter DATE_TYPE_ADAPTER = - new DefaultDateTypeAdapter(DateFormat.getDateTimeInstance()); + new DefaultDateTypeAdapter(DateFormat.getDateTimeInstance()); + @SuppressWarnings("unchecked") private static final EnumTypeAdapter ENUM_TYPE_ADAPTER = new EnumTypeAdapter(); private static final UrlTypeAdapter URL_TYPE_ADAPTER = new UrlTypeAdapter(); @@ -68,10 +69,10 @@ final class DefaultTypeAdapters { private static final BooleanTypeAdapter BOOLEAN_TYPE_ADAPTER = new BooleanTypeAdapter(); private static final ByteTypeAdapter BYTE_TYPE_ADAPTER = new ByteTypeAdapter(); private static final CharacterTypeAdapter CHARACTER_TYPE_ADAPTER = new CharacterTypeAdapter(); - private static final DoubleTypeAdapter DOUBLE_TYPE_ADAPTER = new DoubleTypeAdapter(); + private static final DoubleDeserializer DOUBLE_TYPE_ADAPTER = new DoubleDeserializer(); private static final FloatDeserializer FLOAT_TYPE_ADAPTER = new FloatDeserializer(); private static final IntegerTypeAdapter INTEGER_TYPE_ADAPTER = new IntegerTypeAdapter(); - private static final LongTypeAdapter LONG_TYPE_ADAPTER = new LongTypeAdapter(); + private static final LongDeserializer LONG_DESERIALIZER = new LongDeserializer(); private static final NumberTypeAdapter NUMBER_TYPE_ADAPTER = new NumberTypeAdapter(); private static final ShortTypeAdapter SHORT_TYPE_ADAPTER = new ShortTypeAdapter(); private static final StringTypeAdapter STRING_TYPE_ADAPTER = new StringTypeAdapter(); @@ -81,16 +82,16 @@ final class DefaultTypeAdapters { // The constants DEFAULT_SERIALIZERS, DEFAULT_DESERIALIZERS, and DEFAULT_INSTANCE_CREATORS // must be defined after the constants for the type adapters. Otherwise, the type adapter // constants will appear as nulls. - static final ParameterizedTypeHandlerMap> DEFAULT_SERIALIZERS = - getDefaultSerializers(); - static final ParameterizedTypeHandlerMap> DEFAULT_DESERIALIZERS = - getDefaultDeserializers(); - static final ParameterizedTypeHandlerMap> DEFAULT_INSTANCE_CREATORS = - getDefaultInstanceCreators(); + private static final ParameterizedTypeHandlerMap> DEFAULT_SERIALIZERS = + createDefaultSerializers(); + private static final ParameterizedTypeHandlerMap> DEFAULT_DESERIALIZERS = + createDefaultDeserializers(); + private static final ParameterizedTypeHandlerMap> DEFAULT_INSTANCE_CREATORS = + createDefaultInstanceCreators(); - private static ParameterizedTypeHandlerMap> getDefaultSerializers() { + private static ParameterizedTypeHandlerMap> createDefaultSerializers() { ParameterizedTypeHandlerMap> map = - new ParameterizedTypeHandlerMap>(); + new ParameterizedTypeHandlerMap>(); map.register(Enum.class, ENUM_TYPE_ADAPTER); map.register(URL.class, URL_TYPE_ADAPTER); @@ -112,8 +113,6 @@ final class DefaultTypeAdapters { map.register(char.class, CHARACTER_TYPE_ADAPTER); map.register(Integer.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); map.register(Number.class, NUMBER_TYPE_ADAPTER); map.register(Short.class, SHORT_TYPE_ADAPTER); map.register(short.class, SHORT_TYPE_ADAPTER); @@ -123,9 +122,9 @@ final class DefaultTypeAdapters { return map; } - private static ParameterizedTypeHandlerMap> getDefaultDeserializers() { + private static ParameterizedTypeHandlerMap> createDefaultDeserializers() { ParameterizedTypeHandlerMap> map = - new ParameterizedTypeHandlerMap>(); + new ParameterizedTypeHandlerMap>(); map.register(Enum.class, wrapDeserializer(ENUM_TYPE_ADAPTER)); map.register(URL.class, wrapDeserializer(URL_TYPE_ADAPTER)); map.register(URI.class, wrapDeserializer(URI_TYPE_ADAPTER)); @@ -150,8 +149,8 @@ final class DefaultTypeAdapters { map.register(float.class, wrapDeserializer(FLOAT_TYPE_ADAPTER)); map.register(Integer.class, wrapDeserializer(INTEGER_TYPE_ADAPTER)); map.register(int.class, wrapDeserializer(INTEGER_TYPE_ADAPTER)); - map.register(Long.class, wrapDeserializer(LONG_TYPE_ADAPTER)); - map.register(long.class, wrapDeserializer(LONG_TYPE_ADAPTER)); + map.register(Long.class, wrapDeserializer(LONG_DESERIALIZER)); + map.register(long.class, wrapDeserializer(LONG_DESERIALIZER)); map.register(Number.class, wrapDeserializer(NUMBER_TYPE_ADAPTER)); map.register(Short.class, wrapDeserializer(SHORT_TYPE_ADAPTER)); map.register(short.class, wrapDeserializer(SHORT_TYPE_ADAPTER)); @@ -161,7 +160,7 @@ final class DefaultTypeAdapters { return map; } - private static ParameterizedTypeHandlerMap> getDefaultInstanceCreators() { + private static ParameterizedTypeHandlerMap> createDefaultInstanceCreators() { ParameterizedTypeHandlerMap> map = new ParameterizedTypeHandlerMap>(); map.register(Enum.class, ENUM_TYPE_ADAPTER); @@ -183,16 +182,43 @@ final class DefaultTypeAdapters { return new JsonDeserializerExceptionWrapper(deserializer); } - static void registerSerializersForFloatingPoints(boolean serializeSpecialFloatingPointValues, - ParameterizedTypeHandlerMap> serializers) { + static ParameterizedTypeHandlerMap> getDefaultSerializers() { + return getDefaultSerializers(false, false); + } + + static ParameterizedTypeHandlerMap> getDefaultSerializers( + boolean serializeSpecialFloatingPointValues, boolean serializeLongsAsString) { + ParameterizedTypeHandlerMap> serializers = + new ParameterizedTypeHandlerMap>(); + + // Double primitive DefaultTypeAdapters.DoubleSerializer doubleSerializer = new DefaultTypeAdapters.DoubleSerializer(serializeSpecialFloatingPointValues); - DefaultTypeAdapters.FloatSerializer floatSerializer = - new DefaultTypeAdapters.FloatSerializer(serializeSpecialFloatingPointValues); serializers.registerIfAbsent(Double.class, doubleSerializer); serializers.registerIfAbsent(double.class, doubleSerializer); + + // Float primitive + DefaultTypeAdapters.FloatSerializer floatSerializer = + new DefaultTypeAdapters.FloatSerializer(serializeSpecialFloatingPointValues); serializers.registerIfAbsent(Float.class, floatSerializer); serializers.registerIfAbsent(float.class, floatSerializer); + + // Long primitive + DefaultTypeAdapters.LongSerializer longSerializer = + new DefaultTypeAdapters.LongSerializer(serializeLongsAsString); + serializers.registerIfAbsent(Long.class, longSerializer); + serializers.registerIfAbsent(long.class, longSerializer); + + serializers.registerIfAbsent(DEFAULT_SERIALIZERS); + return serializers; + } + + static ParameterizedTypeHandlerMap> getDefaultDeserializers() { + return DEFAULT_DESERIALIZERS; + } + + static ParameterizedTypeHandlerMap> getDefaultInstanceCreators() { + return DEFAULT_INSTANCE_CREATORS; } static class DefaultDateTypeAdapter implements JsonSerializer, JsonDeserializer { @@ -523,15 +549,34 @@ final class DefaultTypeAdapters { @Override public String toString() { - return LongTypeAdapter.class.getSimpleName(); + return NumberTypeAdapter.class.getSimpleName(); + } + } + + private static class LongSerializer implements JsonSerializer { + private final boolean serializeAsString; + + private LongSerializer(boolean serializeAsString) { + this.serializeAsString = serializeAsString; + } + + public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) { + if (src == null) { + return JsonNull.createJsonNull(); + } else if (serializeAsString) { + return new JsonPrimitive(String.valueOf(src)); + } else { + return new JsonPrimitive(src); + } + } + + @Override + public String toString() { + return LongSerializer.class.getSimpleName(); } } - private static class LongTypeAdapter implements JsonSerializer, JsonDeserializer { - public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src); - } - + private static class LongDeserializer implements JsonDeserializer { public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return json.getAsLong(); @@ -539,7 +584,7 @@ final class DefaultTypeAdapters { @Override public String toString() { - return LongTypeAdapter.class.getSimpleName(); + return LongDeserializer.class.getSimpleName(); } } @@ -643,7 +688,7 @@ final class DefaultTypeAdapters { } } - private static class DoubleTypeAdapter implements JsonDeserializer { + private static class DoubleDeserializer implements JsonDeserializer { public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return json.getAsDouble(); @@ -651,7 +696,7 @@ final class DefaultTypeAdapters { @Override public String toString() { - return DoubleTypeAdapter.class.getSimpleName(); + return DoubleDeserializer.class.getSimpleName(); } } diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 6331df64..60cf66f6 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -142,9 +142,9 @@ public final class Gson { */ Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy) { this(strategy, fieldNamingPolicy, - createObjectConstructor(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS), - DEFAULT_JSON_FORMATTER, false, - getDefaultSerializers(), DefaultTypeAdapters.DEFAULT_DESERIALIZERS); + createObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()), + DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(), + DefaultTypeAdapters.getDefaultDeserializers()); } Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy, @@ -159,13 +159,6 @@ public final class Gson { this.serializers = serializers; this.deserializers = deserializers; } - - private static ParameterizedTypeHandlerMap> getDefaultSerializers() { - ParameterizedTypeHandlerMap> serializers = - DefaultTypeAdapters.DEFAULT_SERIALIZERS.copyOf(); - DefaultTypeAdapters.registerSerializersForFloatingPoints(false, serializers); - return serializers; - } static MappedObjectConstructor createObjectConstructor( ParameterizedTypeHandlerMap> instanceCreators) { diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index abc86cd3..2f71042c 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -52,6 +52,7 @@ import java.util.List; public final class GsonBuilder { private double ignoreVersionsAfter; + private boolean serializeLongAsString; private ModifierBasedExclusionStrategy modifierBasedExclusionStrategy; private boolean serializeInnerClasses; private final AnonymousAndLocalClassExclusionStrategy anonAndLocalClassExclusionStrategy; @@ -77,6 +78,7 @@ public final class GsonBuilder { public GsonBuilder() { // setup default values ignoreVersionsAfter = VersionConstants.IGNORE_VERSIONS; + serializeLongAsString = false; serializeInnerClasses = true; anonAndLocalClassExclusionStrategy = new AnonymousAndLocalClassExclusionStrategy(); innerClassExclusionStrategy = new InnerClassExclusionStrategy(); @@ -146,7 +148,19 @@ public final class GsonBuilder { } /** - * Configures Gson to include or exclude inner classes + * Configures Gson to output fields of type {@code long} as {@code String}s instead of a number. + * + * @param value the boolean value on whether or not {@code Gson} should serialize a {@code long} + * field as a {@code String} + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder serializeLongFieldsAsString(boolean value) { + serializeLongAsString = value; + return this; + } + + /** + * Configures Gson to include or exclude inner classes. * * @param value the boolean value on whether or not {@code Gson} should serialize inner classes * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern @@ -389,24 +403,24 @@ public final class GsonBuilder { ParameterizedTypeHandlerMap> customSerializers = serializers.copyOf(); ParameterizedTypeHandlerMap> customDeserializers = deserializers.copyOf(); - - addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, customSerializers, + addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, customSerializers, customDeserializers); - customSerializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_SERIALIZERS); - DefaultTypeAdapters.registerSerializersForFloatingPoints(serializeSpecialFloatingPointValues, - customSerializers); - customDeserializers.registerIfAbsent(DefaultTypeAdapters.DEFAULT_DESERIALIZERS); + + customSerializers.registerIfAbsent(DefaultTypeAdapters.getDefaultSerializers( + serializeSpecialFloatingPointValues, serializeLongAsString)); + + customDeserializers.registerIfAbsent(DefaultTypeAdapters.getDefaultDeserializers()); ParameterizedTypeHandlerMap> customInstanceCreators = instanceCreators.copyOf(); - customInstanceCreators.registerIfAbsent(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS); + customInstanceCreators.registerIfAbsent(DefaultTypeAdapters.getDefaultInstanceCreators()); MappedObjectConstructor objConstructor = Gson.createObjectConstructor(customInstanceCreators); Gson gson = new Gson(exclusionStrategy, fieldNamingPolicy, objConstructor, formatter, serializeNulls, customSerializers, customDeserializers); return gson; } - + private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, ParameterizedTypeHandlerMap> serializers, ParameterizedTypeHandlerMap> deserializers) { diff --git a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java index 534a3539..18b2f3d2 100644 --- a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java +++ b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java @@ -73,13 +73,13 @@ public class ParameterizedTypesTest extends TestCase { public void testTypesWithMultipleParametersSerialization() throws Exception { MultiParameters src = - new MultiParameters(10, 1.0F, 2.1D, - "abc", new BagOfPrimitives()); + new MultiParameters(10, 1.0F, 2.1D, + "abc", new BagOfPrimitives()); Type typeOfSrc = new TypeToken>() {}.getType(); String json = gson.toJson(src, typeOfSrc); String expected = "{\"a\":10,\"b\":1.0,\"c\":2.1,\"d\":\"abc\"," - + "\"e\":{\"longValue\":0,\"intValue\":0,\"booleanValue\":false,\"stringValue\":\"\"}}"; + + "\"e\":{\"longValue\":0,\"intValue\":0,\"booleanValue\":false,\"stringValue\":\"\"}}"; assertEquals(expected, json); } @@ -87,12 +87,12 @@ public class ParameterizedTypesTest extends TestCase { Type typeOfTarget = new TypeToken>() {}.getType(); String json = "{\"a\":10,\"b\":1.0,\"c\":2.1,\"d\":\"abc\"," - + "\"e\":{\"longValue\":0,\"intValue\":0,\"booleanValue\":false,\"stringValue\":\"\"}}"; + + "\"e\":{\"longValue\":0,\"intValue\":0,\"booleanValue\":false,\"stringValue\":\"\"}}"; MultiParameters target = - gson.fromJson(json, typeOfTarget); + gson.fromJson(json, typeOfTarget); MultiParameters expected = - new MultiParameters(10, 1.0F, 2.1D, - "abc", new BagOfPrimitives()); + new MultiParameters(10, 1.0F, 2.1D, + "abc", new BagOfPrimitives()); assertEquals(expected, target); } diff --git a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java index 92c0562b..e0bb59e3 100644 --- a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java +++ b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java @@ -528,4 +528,23 @@ public class PrimitiveTest extends TestCase { } catch (JsonParseException expected) { } } + + public void testLongAsStringSerialization() throws Exception { + gson = new GsonBuilder().serializeLongFieldsAsString(true).create(); + String result = gson.toJson(15L); + assertEquals("\"15\"", result); + + // Test with an integer and ensure its still a number + result = gson.toJson(2); + assertEquals("2", result); + } + + public void testLongAsStringDeserialization() throws Exception { + long value = gson.fromJson("\"15\"", long.class); + assertEquals(15, value); + + gson = new GsonBuilder().serializeLongFieldsAsString(true).create(); + value = gson.fromJson("\"25\"", long.class); + assertEquals(25, value); + } }