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.
This commit is contained in:
Joel Leitch 2008-12-28 03:23:36 +00:00
parent 859af0025c
commit 458f2baa2f
5 changed files with 128 additions and 57 deletions

View File

@ -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<JsonSerializer<?>> DEFAULT_SERIALIZERS =
getDefaultSerializers();
static final ParameterizedTypeHandlerMap<JsonDeserializer<?>> DEFAULT_DESERIALIZERS =
getDefaultDeserializers();
static final ParameterizedTypeHandlerMap<InstanceCreator<?>> DEFAULT_INSTANCE_CREATORS =
getDefaultInstanceCreators();
private static final ParameterizedTypeHandlerMap<JsonSerializer<?>> DEFAULT_SERIALIZERS =
createDefaultSerializers();
private static final ParameterizedTypeHandlerMap<JsonDeserializer<?>> DEFAULT_DESERIALIZERS =
createDefaultDeserializers();
private static final ParameterizedTypeHandlerMap<InstanceCreator<?>> DEFAULT_INSTANCE_CREATORS =
createDefaultInstanceCreators();
private static ParameterizedTypeHandlerMap<JsonSerializer<?>> getDefaultSerializers() {
private static ParameterizedTypeHandlerMap<JsonSerializer<?>> createDefaultSerializers() {
ParameterizedTypeHandlerMap<JsonSerializer<?>> map =
new ParameterizedTypeHandlerMap<JsonSerializer<?>>();
new ParameterizedTypeHandlerMap<JsonSerializer<?>>();
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<JsonDeserializer<?>> getDefaultDeserializers() {
private static ParameterizedTypeHandlerMap<JsonDeserializer<?>> createDefaultDeserializers() {
ParameterizedTypeHandlerMap<JsonDeserializer<?>> map =
new ParameterizedTypeHandlerMap<JsonDeserializer<?>>();
new ParameterizedTypeHandlerMap<JsonDeserializer<?>>();
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<InstanceCreator<?>> getDefaultInstanceCreators() {
private static ParameterizedTypeHandlerMap<InstanceCreator<?>> createDefaultInstanceCreators() {
ParameterizedTypeHandlerMap<InstanceCreator<?>> map =
new ParameterizedTypeHandlerMap<InstanceCreator<?>>();
map.register(Enum.class, ENUM_TYPE_ADAPTER);
@ -183,16 +182,43 @@ final class DefaultTypeAdapters {
return new JsonDeserializerExceptionWrapper(deserializer);
}
static void registerSerializersForFloatingPoints(boolean serializeSpecialFloatingPointValues,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers) {
static ParameterizedTypeHandlerMap<JsonSerializer<?>> getDefaultSerializers() {
return getDefaultSerializers(false, false);
}
static ParameterizedTypeHandlerMap<JsonSerializer<?>> getDefaultSerializers(
boolean serializeSpecialFloatingPointValues, boolean serializeLongsAsString) {
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers =
new ParameterizedTypeHandlerMap<JsonSerializer<?>>();
// 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<JsonDeserializer<?>> getDefaultDeserializers() {
return DEFAULT_DESERIALIZERS;
}
static ParameterizedTypeHandlerMap<InstanceCreator<?>> getDefaultInstanceCreators() {
return DEFAULT_INSTANCE_CREATORS;
}
static class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
@ -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<Long> {
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<Long>, JsonDeserializer<Long> {
public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
private static class LongDeserializer implements JsonDeserializer<Long> {
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<Double> {
private static class DoubleDeserializer implements JsonDeserializer<Double> {
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();
}
}

View File

@ -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<JsonSerializer<?>> getDefaultSerializers() {
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers =
DefaultTypeAdapters.DEFAULT_SERIALIZERS.copyOf();
DefaultTypeAdapters.registerSerializersForFloatingPoints(false, serializers);
return serializers;
}
static MappedObjectConstructor createObjectConstructor(
ParameterizedTypeHandlerMap<InstanceCreator<?>> instanceCreators) {

View File

@ -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<JsonSerializer<?>> customSerializers = serializers.copyOf();
ParameterizedTypeHandlerMap<JsonDeserializer<?>> 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<InstanceCreator<?>> 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<JsonSerializer<?>> serializers,
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers) {

View File

@ -73,13 +73,13 @@ public class ParameterizedTypesTest extends TestCase {
public void testTypesWithMultipleParametersSerialization() throws Exception {
MultiParameters<Integer, Float, Double, String, BagOfPrimitives> src =
new MultiParameters<Integer, Float, Double, String, BagOfPrimitives>(10, 1.0F, 2.1D,
"abc", new BagOfPrimitives());
new MultiParameters<Integer, Float, Double, String, BagOfPrimitives>(10, 1.0F, 2.1D,
"abc", new BagOfPrimitives());
Type typeOfSrc = new TypeToken<MultiParameters<Integer, Float, Double, String,
BagOfPrimitives>>() {}.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<MultiParameters<Integer, Float, Double, String,
BagOfPrimitives>>() {}.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<Integer, Float, Double, String, BagOfPrimitives> target =
gson.fromJson(json, typeOfTarget);
gson.fromJson(json, typeOfTarget);
MultiParameters<Integer, Float, Double, String, BagOfPrimitives> expected =
new MultiParameters<Integer, Float, Double, String, BagOfPrimitives>(10, 1.0F, 2.1D,
"abc", new BagOfPrimitives());
new MultiParameters<Integer, Float, Double, String, BagOfPrimitives>(10, 1.0F, 2.1D,
"abc", new BagOfPrimitives());
assertEquals(expected, target);
}

View File

@ -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);
}
}