diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 3f4a62b4..cb81c3b2 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -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 factories = new ArrayList(); // 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 doubleAdapter(boolean serializeSpecialFloatingPointValues) { if (serializeSpecialFloatingPointValues) { return TypeAdapters.DOUBLE; diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index f8bc01c7..e6c0b8c0 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -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. * + *

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); } diff --git a/gson/src/test/java/com/google/gson/GsonBuilderTest.java b/gson/src/test/java/com/google/gson/GsonBuilderTest.java index 737ab055..73601c0e 100755 --- a/gson/src/test/java/com/google/gson/GsonBuilderTest.java +++ b/gson/src/test/java/com/google/gson/GsonBuilderTest.java @@ -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); } } diff --git a/gson/src/test/java/com/google/gson/OverrideCoreTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/OverrideCoreTypeAdaptersTest.java new file mode 100644 index 00000000..f373a175 --- /dev/null +++ b/gson/src/test/java/com/google/gson/OverrideCoreTypeAdaptersTest.java @@ -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 booleanAsIntAdapter = new TypeAdapter() { + @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 swapCaseStringAdapter = new TypeAdapter() { + @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)); + } +} diff --git a/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java index 4c2ae706..ea7b0cd4 100644 --- a/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java @@ -204,28 +204,28 @@ public class CustomTypeAdaptersTest extends TestCase { } } - public void testCustomSerializerForbiddenForPrimitives() { - try { - new GsonBuilder().registerTypeAdapter(long.class, new JsonSerializer() { - 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() { + 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() { - 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() { diff --git a/gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java index 8947b9c2..a7a9b5b9 100644 --- a/gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java @@ -54,19 +54,18 @@ public class DelegateTypeAdapterTest extends TestCase { } String json = gson.toJson(bags); bags = gson.fromJson(json, new TypeToken>(){}.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 {