Permit users to define type adapters for primitive types and strings.

Also expose an API to get the field naming strategy.
This commit is contained in:
Jesse Wilson 2012-06-30 02:37:49 +00:00
parent c3ada66749
commit dc4e43bb23
6 changed files with 132 additions and 49 deletions

View File

@ -124,6 +124,7 @@ public final class Gson {
private final boolean htmlSafe;
private final boolean generateNonExecutableJson;
private final boolean prettyPrinting;
private final FieldNamingStrategy fieldNamingPolicy;
final JsonDeserializationContext deserializationContext = new JsonDeserializationContext() {
@SuppressWarnings("unchecked")
@ -193,10 +194,18 @@ public final class Gson {
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.prettyPrinting = prettyPrinting;
this.fieldNamingPolicy = fieldNamingPolicy;
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// user's type adapters
factories.addAll(typeAdapterFactories);
// type adapters for basic platform types
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
@ -208,21 +217,12 @@ public final class Gson {
doubleAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.newFactory(float.class, Float.class,
floatAdapter(serializeSpecialFloatingPointValues)));
factories.add(excluder);
factories.add(TypeAdapters.NUMBER_FACTORY);
factories.add(TypeAdapters.CHARACTER_FACTORY);
factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// user's type adapters
factories.addAll(typeAdapterFactories);
// built-in type adapters that can be overridden
factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY);
factories.add(TypeAdapters.UUID_FACTORY);
@ -234,16 +234,30 @@ public final class Gson {
factories.add(TimeTypeAdapter.FACTORY);
factories.add(SqlDateTypeAdapter.FACTORY);
factories.add(TypeAdapters.TIMESTAMP_FACTORY);
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY);
// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);
// type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingPolicy, excluder));
this.factories = Collections.unmodifiableList(factories);
}
/**
* Returns the field naming policy used to translate Java field names to JSON
* property names.
*/
public FieldNamingStrategy getFieldNamingPolicy() {
return fieldNamingPolicy;
}
private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
if (serializeSpecialFloatingPointValues) {
return TypeAdapters.DOUBLE;

View File

@ -28,7 +28,6 @@ import java.util.Map;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.Primitives;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken;
@ -435,6 +434,10 @@ public final class GsonBuilder {
* all the required interfaces for custom serialization with Gson. If a type adapter was
* previously registered for the specified {@code type}, it is overwritten.
*
* <p>This registers the type specified and no other types: you must manually register related
* types! For example, applications registering {@code boolean.class} should also register {@code
* Boolean.class}.
*
* @param type the type definition for the type adapter being registered
* @param typeAdapter This object must implement at least one of the {@link TypeAdapter},
* {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
@ -446,10 +449,6 @@ public final class GsonBuilder {
|| typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof InstanceCreator<?>
|| typeAdapter instanceof TypeAdapter<?>);
if (Primitives.isPrimitive(type) || Primitives.isWrapperType(type) || type == String.class) {
throw new IllegalArgumentException(
"Cannot register type adapters for " + type);
}
if (typeAdapter instanceof InstanceCreator<?>) {
instanceCreators.put(type, (InstanceCreator) typeAdapter);
}

View File

@ -52,7 +52,7 @@ public class GsonBuilderTest extends TestCase {
assertEquals("{\"d\":\"d\"}", gson.toJson(new HasModifiers()));
}
public void testRegisterTypeAdapterForUnsupportedType() {
public void testRegisterTypeAdapterForCoreType() {
Type[] types = {
byte.class,
int.class,
@ -62,11 +62,7 @@ public class GsonBuilderTest extends TestCase {
String.class,
};
for (Type type : types) {
try {
new GsonBuilder().registerTypeAdapter(type, NULL_TYPE_ADAPTER);
fail(type.toString());
} catch (IllegalArgumentException e) {
}
new GsonBuilder().registerTypeAdapter(type, NULL_TYPE_ADAPTER);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2012 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 com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.Locale;
import junit.framework.TestCase;
/**
* @author Jesse Wilson
*/
public class OverrideCoreTypeAdaptersTest extends TestCase {
private static final TypeAdapter<Boolean> booleanAsIntAdapter = new TypeAdapter<Boolean>() {
@Override public void write(JsonWriter out, Boolean value) throws IOException {
out.value(value ? 1 : 0);
}
@Override public Boolean read(JsonReader in) throws IOException {
int value = in.nextInt();
return value != 0;
}
};
private static final TypeAdapter<String> swapCaseStringAdapter = new TypeAdapter<String>() {
@Override public void write(JsonWriter out, String value) throws IOException {
out.value(value.toUpperCase(Locale.US));
}
@Override public String read(JsonReader in) throws IOException {
return in.nextString().toLowerCase(Locale.US);
}
};
public void testOverrideWrapperBooleanAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Boolean.class, booleanAsIntAdapter)
.create();
assertEquals("true", gson.toJson(true, boolean.class));
assertEquals("1", gson.toJson(true, Boolean.class));
assertEquals(Boolean.TRUE, gson.fromJson("true", boolean.class));
assertEquals(Boolean.TRUE, gson.fromJson("1", Boolean.class));
}
public void testOverridePrimitiveBooleanAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(boolean.class, booleanAsIntAdapter)
.create();
assertEquals("1", gson.toJson(true, boolean.class));
assertEquals("true", gson.toJson(true, Boolean.class));
assertEquals(Boolean.TRUE, gson.fromJson("1", boolean.class));
assertEquals(Boolean.TRUE, gson.fromJson("true", Boolean.class));
}
public void testOverrideStringAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, swapCaseStringAdapter)
.create();
assertEquals("\"HELLO\"", gson.toJson("Hello", String.class));
assertEquals("hello", gson.fromJson("\"Hello\"", String.class));
}
}

View File

@ -204,28 +204,28 @@ public class CustomTypeAdaptersTest extends TestCase {
}
}
public void testCustomSerializerForbiddenForPrimitives() {
try {
new GsonBuilder().registerTypeAdapter(long.class, new JsonSerializer<Long>() {
public JsonElement serialize(Long s, Type t, JsonSerializationContext c) {
throw new AssertionError();
}
});
fail();
} catch (IllegalArgumentException expected) {
}
public void testCustomSerializerInvokedForPrimitives() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(boolean.class, new JsonSerializer<Boolean>() {
public JsonElement serialize(Boolean s, Type t, JsonSerializationContext c) {
return new JsonPrimitive(s ? 1 : 0);
}
})
.create();
assertEquals("1", gson.toJson(true, boolean.class));
assertEquals("true", gson.toJson(true, Boolean.class));
}
public void testCustomDeserializerForbiddenForPrimitives() {
try {
new GsonBuilder().registerTypeAdapter(long.class, new JsonDeserializer<Long>() {
public Long deserialize(JsonElement json, Type t, JsonDeserializationContext c) {
throw new AssertionError();
}
});
fail();
} catch (Exception expected) {
}
public void testCustomDeserializerInvokedForPrimitives() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(boolean.class, new JsonDeserializer() {
public Object deserialize(JsonElement json, Type t, JsonDeserializationContext context) {
return json.getAsInt() != 0;
}
})
.create();
assertEquals(Boolean.TRUE, gson.fromJson("1", boolean.class));
assertEquals(Boolean.TRUE, gson.fromJson("true", Boolean.class));
}
public void testCustomByteArraySerializer() {

View File

@ -54,19 +54,18 @@ public class DelegateTypeAdapterTest extends TestCase {
}
String json = gson.toJson(bags);
bags = gson.fromJson(json, new TypeToken<List<BagOfPrimitives>>(){}.getType());
// 11: 1 list object, and 10 entries. stats not invoked on individual fields of
// BagOfPrimitives since those are primitives.
assertEquals(11, stats.numReads);
assertEquals(11, stats.numWrites);
// 11: 1 list object, and 10 entries. stats invoked on all 5 fields
assertEquals(51, stats.numReads);
assertEquals(51, stats.numWrites);
}
public void testDelegateNotInvokedOnStrings() {
public void testDelegateInvokedOnStrings() {
String[] bags = {"1", "2", "3", "4"};
String json = gson.toJson(bags);
bags = gson.fromJson(json, String[].class);
// Only 1 array object. stats not invoked on individual strings.
assertEquals(1, stats.numReads);
assertEquals(1, stats.numWrites);
// 1 array object with 4 elements.
assertEquals(5, stats.numReads);
assertEquals(5, stats.numWrites);
}
private static class StatsTypeAdapterFactory implements TypeAdapterFactory {