diff --git a/gson/src/main/java/com/google/gson/ExposeAnnotationBasedExclusionStrategy.java b/gson/src/main/java/com/google/gson/ExposeAnnotationBasedExclusionStrategy.java index 19cff662..58e54cdc 100644 --- a/gson/src/main/java/com/google/gson/ExposeAnnotationBasedExclusionStrategy.java +++ b/gson/src/main/java/com/google/gson/ExposeAnnotationBasedExclusionStrategy.java @@ -27,11 +27,32 @@ import java.lang.reflect.Field; */ class ExposeAnnotationBasedExclusionStrategy implements ExclusionStrategy { + enum Phase { + SERIALIZATION, DESERIALIZATION + } + + private final Phase phase; + + public ExposeAnnotationBasedExclusionStrategy(Phase phase) { + this.phase = phase; + } + public boolean shouldSkipClass(Class clazz) { return false; } public boolean shouldSkipField(Field f) { - return f.getAnnotation(Expose.class) == null; + Expose annotation = f.getAnnotation(Expose.class); + if (annotation == null) { + return true; + } + switch (phase) { + case SERIALIZATION: + return !annotation.serialize(); + case DESERIALIZATION: + return !annotation.deserialize(); + default: + throw new IllegalStateException(); + } } } diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index e01fb247..a3882293 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -87,7 +87,11 @@ public final class Gson { private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; - private final ExclusionStrategy strategy; + + private final ExclusionStrategy serializationStrategy; + + private final ExclusionStrategy deserializationStrategy; + private final FieldNamingStrategy fieldNamingPolicy; private final MappedObjectConstructor objectConstructor; @@ -145,18 +149,20 @@ public final class Gson { * encountering inner class references. */ Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy) { - this(strategy, fieldNamingPolicy, + this(strategy, strategy, fieldNamingPolicy, new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()), DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(), DefaultTypeAdapters.getDefaultDeserializers(), DEFAULT_JSON_NON_EXECUTABLE); } - Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy, - MappedObjectConstructor objectConstructor, JsonFormatter formatter, boolean serializeNulls, + Gson(ExclusionStrategy serializationStrategy, ExclusionStrategy deserializationStrategy, + FieldNamingStrategy fieldNamingPolicy, MappedObjectConstructor objectConstructor, + JsonFormatter formatter, boolean serializeNulls, ParameterizedTypeHandlerMap> serializers, ParameterizedTypeHandlerMap> deserializers, boolean generateNonExecutableGson) { - this.strategy = strategy; + this.serializationStrategy = serializationStrategy; + this.deserializationStrategy = deserializationStrategy; this.fieldNamingPolicy = fieldNamingPolicy; this.objectConstructor = objectConstructor; this.formatter = formatter; @@ -166,7 +172,7 @@ public final class Gson { this.generateNonExecutableJson = generateNonExecutableGson; } - private ObjectNavigatorFactory createDefaultObjectNavigatorFactory() { + private ObjectNavigatorFactory createDefaultObjectNavigatorFactory(ExclusionStrategy strategy) { return new ObjectNavigatorFactory(strategy, fieldNamingPolicy); } @@ -223,7 +229,7 @@ public final class Gson { return JsonNull.createJsonNull(); } JsonSerializationContext context = new JsonSerializationContextDefault( - createDefaultObjectNavigatorFactory(), serializeNulls, serializers); + createDefaultObjectNavigatorFactory(serializationStrategy), serializeNulls, serializers); return context.serialize(src, typeOfSrc); } @@ -459,7 +465,8 @@ public final class Gson { return null; } JsonDeserializationContext context = new JsonDeserializationContextDefault( - createDefaultObjectNavigatorFactory(), deserializers, objectConstructor); + createDefaultObjectNavigatorFactory(deserializationStrategy), deserializers, + objectConstructor); T target = (T) context.deserialize(json, typeOfT); return target; } diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index 01971ca7..61b6f123 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -23,6 +23,7 @@ import java.util.LinkedList; import java.util.List; import com.google.gson.DefaultTypeAdapters.DefaultDateTypeAdapter; +import com.google.gson.ExposeAnnotationBasedExclusionStrategy.Phase; /** *

Use this builder to construct a {@link Gson} instance when you need to set configuration @@ -54,8 +55,12 @@ public final class GsonBuilder { new AnonymousAndLocalClassExclusionStrategy(); private static final InnerClassExclusionStrategy innerClassExclusionStrategy = new InnerClassExclusionStrategy(); - private static final ExposeAnnotationBasedExclusionStrategy exposeAnnotationExclusionStrategy = - new ExposeAnnotationBasedExclusionStrategy(); + private static final ExposeAnnotationBasedExclusionStrategy + exposeAnnotationSerializationExclusionStrategy = + new ExposeAnnotationBasedExclusionStrategy(Phase.SERIALIZATION); + private static final ExposeAnnotationBasedExclusionStrategy + exposeAnnotationDeserializationExclusionStrategy = + new ExposeAnnotationBasedExclusionStrategy(Phase.DESERIALIZATION); private double ignoreVersionsAfter; private ModifierBasedExclusionStrategy modifierBasedExclusionStrategy; @@ -408,20 +413,29 @@ public final class GsonBuilder { * @return an instance of Gson configured with the options currently set in this builder */ public Gson create() { - List strategies = new LinkedList(); - strategies.add(modifierBasedExclusionStrategy); - strategies.add(anonAndLocalClassExclusionStrategy); + List serializationStrategies = new LinkedList(); + List deserializationStrategies = new LinkedList(); + serializationStrategies.add(modifierBasedExclusionStrategy); + deserializationStrategies.add(modifierBasedExclusionStrategy); + serializationStrategies.add(anonAndLocalClassExclusionStrategy); + deserializationStrategies.add(anonAndLocalClassExclusionStrategy); if (!serializeInnerClasses) { - strategies.add(innerClassExclusionStrategy); + serializationStrategies.add(innerClassExclusionStrategy); + deserializationStrategies.add(innerClassExclusionStrategy); } if (ignoreVersionsAfter != VersionConstants.IGNORE_VERSIONS) { - strategies.add(new VersionExclusionStrategy(ignoreVersionsAfter)); + serializationStrategies.add(new VersionExclusionStrategy(ignoreVersionsAfter)); + deserializationStrategies.add(new VersionExclusionStrategy(ignoreVersionsAfter)); } if (excludeFieldsWithoutExposeAnnotation) { - strategies.add(exposeAnnotationExclusionStrategy); + serializationStrategies.add(exposeAnnotationSerializationExclusionStrategy); + deserializationStrategies.add(exposeAnnotationDeserializationExclusionStrategy); } - ExclusionStrategy exclusionStrategy = new DisjunctionExclusionStrategy(strategies); + ExclusionStrategy serializationExclusionStrategy = + new DisjunctionExclusionStrategy(serializationStrategies); + ExclusionStrategy deserializationExclusionStrategy = + new DisjunctionExclusionStrategy(deserializationStrategies); ParameterizedTypeHandlerMap> customSerializers = serializers.copyOf(); ParameterizedTypeHandlerMap> customDeserializers = deserializers.copyOf(); @@ -445,8 +459,9 @@ public final class GsonBuilder { JsonFormatter formatter = prettyPrinting ? new JsonPrintFormatter(escapeHtmlChars) : new JsonCompactFormatter(escapeHtmlChars); - Gson gson = new Gson(exclusionStrategy, fieldNamingPolicy, objConstructor, - formatter, serializeNulls, customSerializers, customDeserializers, generateNonExecutableJson); + Gson gson = new Gson(serializationExclusionStrategy, deserializationExclusionStrategy, + fieldNamingPolicy, objConstructor, formatter, serializeNulls, customSerializers, + customDeserializers, generateNonExecutableJson); return gson; } diff --git a/gson/src/main/java/com/google/gson/annotations/Expose.java b/gson/src/main/java/com/google/gson/annotations/Expose.java index 1f6f35c0..706f76e0 100644 --- a/gson/src/main/java/com/google/gson/annotations/Expose.java +++ b/gson/src/main/java/com/google/gson/annotations/Expose.java @@ -34,8 +34,8 @@ import java.lang.annotation.Target; *

  * public class User {
  *   @Expose private String firstName;
- *   @Expose private String lastName;
- *   @Expose private String emailAddress;
+ *   @Expose(serialize = false) private String lastName;
+ *   @Expose (serialize = false, deserialize = false) private String emailAddress;
  *   private String password;
  * }
  * 

@@ -45,7 +45,9 @@ import java.lang.annotation.Target; * with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()} * then the {@code toJson()} and {@code fromJson()} methods of Gson will exclude the * {@code password} field. This is because the {@code password} field is not marked with the - * {@code @Expose} annotation. + * {@code @Expose} annotation. Gson will also exclude {@code lastName} and {@code emailAddress} + * from serialization since {@code serialize} is set to {@code false}. Similarly, Gson will + * exclude {@code emailAddress} from deserialization since {@code deserialize} is set to false. * *

Note that another way to achieve the same effect would have been to just mark the * {@code password} field as {@code transient}, and Gson would have excluded it even with default @@ -58,5 +60,20 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Expose { - // This is a marker annotation with no additional properties + + /** + * If true, the field marked with this annotation is written out in the JSON while serializing. + * If false, the field marked with this annotation is skipped from the serialized output. + * Defaults to true. + * @since 1.4 + */ + public boolean serialize() default true; + + /** + * If true, the field marked with this annotation is deserialized from the JSON. + * If false, the field marked with this annotation is skipped during deserialization. + * Defaults to true. + * @since 1.4 + */ + public boolean deserialize() default true; } diff --git a/gson/src/test/java/com/google/gson/ExposeAnnotationBasedExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/ExposeAnnotationBasedExclusionStrategyTest.java index 30dc0af9..556d7506 100644 --- a/gson/src/test/java/com/google/gson/ExposeAnnotationBasedExclusionStrategyTest.java +++ b/gson/src/test/java/com/google/gson/ExposeAnnotationBasedExclusionStrategyTest.java @@ -16,6 +16,7 @@ package com.google.gson; +import com.google.gson.ExposeAnnotationBasedExclusionStrategy.Phase; import com.google.gson.annotations.Expose; import junit.framework.TestCase; @@ -33,7 +34,7 @@ public class ExposeAnnotationBasedExclusionStrategyTest extends TestCase { @Override protected void setUp() throws Exception { super.setUp(); - strategy = new ExposeAnnotationBasedExclusionStrategy(); + strategy = new ExposeAnnotationBasedExclusionStrategy(Phase.SERIALIZATION); } public void testNeverSkipClasses() throws Exception { diff --git a/gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java b/gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java index a7be408e..bee107b5 100644 --- a/gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java +++ b/gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java @@ -72,11 +72,12 @@ public class ExposeFieldsTest extends TestCase { } public void testExposeAnnotationDeserialization() throws Exception { - String json = '{' + "\"a\":" + 3 + ",\"b\":" + 4 + '}'; + String json = "{a:3,b:4,d:20.0}"; ClassWithExposedFields target = gson.fromJson(json, ClassWithExposedFields.class); assertEquals(3, (int) target.a); assertNull(target.b); + assertFalse(target.d == 20); } public void testNoExposedFieldSerialization() throws Exception { @@ -87,7 +88,7 @@ public class ExposeFieldsTest extends TestCase { } public void testNoExposedFieldDeserialization() throws Exception { - String json = '{' + "\"a\":" + 4 + ",\"b\":" + 5 + '}'; + String json = "{a:4,b:5}"; ClassWithNoExposedFields obj = gson.fromJson(json, ClassWithNoExposedFields.class); assertEquals(0, obj.a); @@ -112,39 +113,37 @@ public class ExposeFieldsTest extends TestCase { private static class ClassWithExposedFields { @Expose private final Integer a; private final Integer b; + @Expose(serialize = false) final long c; + @Expose(deserialize = false) final double d; + @Expose(serialize = false, deserialize = false) final char e; ClassWithExposedFields() { this(null, null); } public ClassWithExposedFields(Integer a, Integer b) { + this(a, b, 1L, 2.0, 'a'); + } + public ClassWithExposedFields(Integer a, Integer b, long c, double d, char e) { this.a = a; this.b = b; + this.c = c; + this.d = d; + this.e = e; } public String getExpectedJson() { - if (a == null) { - return "{}"; + StringBuilder sb = new StringBuilder("{"); + if (a != null) { + sb.append("\"a\":").append(a).append(","); } - return '{' + "\"a\":" + a + '}'; + sb.append("\"d\":").append(d); + sb.append("}"); + return sb.toString(); } public String getExpectedJsonWithoutAnnotations() { - StringBuilder stringBuilder = new StringBuilder(); - boolean requiresComma = false; - stringBuilder.append('{'); - if (a != null) { - stringBuilder.append("\"a\":").append(a); - requiresComma = true; - } - if (b != null) { - if (requiresComma) { - stringBuilder.append(','); - } - stringBuilder.append("\"b\":").append(b); - } - stringBuilder.append('}'); - return stringBuilder.toString(); + return String.format("{\"a\":%d,\"b\":%d,\"c\":%d,\"d\":%f,\"e\":\"%c\"}", a, b, c, d, e); } } @@ -178,5 +177,5 @@ public class ExposeFieldsTest extends TestCase { public ClassWithInterfaceField(SomeInterface interfaceField) { this.interfaceField = interfaceField; } - } + } }