Added serialize and deserialize parameters to the Expose annotation that control whether a field gets exposed during serialization or deserialization.

This commit is contained in:
Inderjeet Singh 2009-05-19 23:47:53 +00:00
parent 376385ac0e
commit 6d50bcea87
6 changed files with 106 additions and 46 deletions

View File

@ -27,11 +27,32 @@ import java.lang.reflect.Field;
*/ */
class ExposeAnnotationBasedExclusionStrategy implements ExclusionStrategy { class ExposeAnnotationBasedExclusionStrategy implements ExclusionStrategy {
enum Phase {
SERIALIZATION, DESERIALIZATION
}
private final Phase phase;
public ExposeAnnotationBasedExclusionStrategy(Phase phase) {
this.phase = phase;
}
public boolean shouldSkipClass(Class<?> clazz) { public boolean shouldSkipClass(Class<?> clazz) {
return false; return false;
} }
public boolean shouldSkipField(Field f) { 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();
}
} }
} }

View File

@ -87,7 +87,11 @@ public final class Gson {
private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; 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 FieldNamingStrategy fieldNamingPolicy;
private final MappedObjectConstructor objectConstructor; private final MappedObjectConstructor objectConstructor;
@ -145,18 +149,20 @@ public final class Gson {
* encountering inner class references. * encountering inner class references.
*/ */
Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy) { Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy) {
this(strategy, fieldNamingPolicy, this(strategy, strategy, fieldNamingPolicy,
new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()), new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()),
DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(), DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(),
DefaultTypeAdapters.getDefaultDeserializers(), DEFAULT_JSON_NON_EXECUTABLE); DefaultTypeAdapters.getDefaultDeserializers(), DEFAULT_JSON_NON_EXECUTABLE);
} }
Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy, Gson(ExclusionStrategy serializationStrategy, ExclusionStrategy deserializationStrategy,
MappedObjectConstructor objectConstructor, JsonFormatter formatter, boolean serializeNulls, FieldNamingStrategy fieldNamingPolicy, MappedObjectConstructor objectConstructor,
JsonFormatter formatter, boolean serializeNulls,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers, ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers,
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers, ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers,
boolean generateNonExecutableGson) { boolean generateNonExecutableGson) {
this.strategy = strategy; this.serializationStrategy = serializationStrategy;
this.deserializationStrategy = deserializationStrategy;
this.fieldNamingPolicy = fieldNamingPolicy; this.fieldNamingPolicy = fieldNamingPolicy;
this.objectConstructor = objectConstructor; this.objectConstructor = objectConstructor;
this.formatter = formatter; this.formatter = formatter;
@ -166,7 +172,7 @@ public final class Gson {
this.generateNonExecutableJson = generateNonExecutableGson; this.generateNonExecutableJson = generateNonExecutableGson;
} }
private ObjectNavigatorFactory createDefaultObjectNavigatorFactory() { private ObjectNavigatorFactory createDefaultObjectNavigatorFactory(ExclusionStrategy strategy) {
return new ObjectNavigatorFactory(strategy, fieldNamingPolicy); return new ObjectNavigatorFactory(strategy, fieldNamingPolicy);
} }
@ -223,7 +229,7 @@ public final class Gson {
return JsonNull.createJsonNull(); return JsonNull.createJsonNull();
} }
JsonSerializationContext context = new JsonSerializationContextDefault( JsonSerializationContext context = new JsonSerializationContextDefault(
createDefaultObjectNavigatorFactory(), serializeNulls, serializers); createDefaultObjectNavigatorFactory(serializationStrategy), serializeNulls, serializers);
return context.serialize(src, typeOfSrc); return context.serialize(src, typeOfSrc);
} }
@ -459,7 +465,8 @@ public final class Gson {
return null; return null;
} }
JsonDeserializationContext context = new JsonDeserializationContextDefault( JsonDeserializationContext context = new JsonDeserializationContextDefault(
createDefaultObjectNavigatorFactory(), deserializers, objectConstructor); createDefaultObjectNavigatorFactory(deserializationStrategy), deserializers,
objectConstructor);
T target = (T) context.deserialize(json, typeOfT); T target = (T) context.deserialize(json, typeOfT);
return target; return target;
} }

View File

@ -23,6 +23,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import com.google.gson.DefaultTypeAdapters.DefaultDateTypeAdapter; import com.google.gson.DefaultTypeAdapters.DefaultDateTypeAdapter;
import com.google.gson.ExposeAnnotationBasedExclusionStrategy.Phase;
/** /**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration * <p>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(); new AnonymousAndLocalClassExclusionStrategy();
private static final InnerClassExclusionStrategy innerClassExclusionStrategy = private static final InnerClassExclusionStrategy innerClassExclusionStrategy =
new InnerClassExclusionStrategy(); new InnerClassExclusionStrategy();
private static final ExposeAnnotationBasedExclusionStrategy exposeAnnotationExclusionStrategy = private static final ExposeAnnotationBasedExclusionStrategy
new ExposeAnnotationBasedExclusionStrategy(); exposeAnnotationSerializationExclusionStrategy =
new ExposeAnnotationBasedExclusionStrategy(Phase.SERIALIZATION);
private static final ExposeAnnotationBasedExclusionStrategy
exposeAnnotationDeserializationExclusionStrategy =
new ExposeAnnotationBasedExclusionStrategy(Phase.DESERIALIZATION);
private double ignoreVersionsAfter; private double ignoreVersionsAfter;
private ModifierBasedExclusionStrategy modifierBasedExclusionStrategy; 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 * @return an instance of Gson configured with the options currently set in this builder
*/ */
public Gson create() { public Gson create() {
List<ExclusionStrategy> strategies = new LinkedList<ExclusionStrategy>(); List<ExclusionStrategy> serializationStrategies = new LinkedList<ExclusionStrategy>();
strategies.add(modifierBasedExclusionStrategy); List<ExclusionStrategy> deserializationStrategies = new LinkedList<ExclusionStrategy>();
strategies.add(anonAndLocalClassExclusionStrategy); serializationStrategies.add(modifierBasedExclusionStrategy);
deserializationStrategies.add(modifierBasedExclusionStrategy);
serializationStrategies.add(anonAndLocalClassExclusionStrategy);
deserializationStrategies.add(anonAndLocalClassExclusionStrategy);
if (!serializeInnerClasses) { if (!serializeInnerClasses) {
strategies.add(innerClassExclusionStrategy); serializationStrategies.add(innerClassExclusionStrategy);
deserializationStrategies.add(innerClassExclusionStrategy);
} }
if (ignoreVersionsAfter != VersionConstants.IGNORE_VERSIONS) { if (ignoreVersionsAfter != VersionConstants.IGNORE_VERSIONS) {
strategies.add(new VersionExclusionStrategy(ignoreVersionsAfter)); serializationStrategies.add(new VersionExclusionStrategy(ignoreVersionsAfter));
deserializationStrategies.add(new VersionExclusionStrategy(ignoreVersionsAfter));
} }
if (excludeFieldsWithoutExposeAnnotation) { 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<JsonSerializer<?>> customSerializers = serializers.copyOf(); ParameterizedTypeHandlerMap<JsonSerializer<?>> customSerializers = serializers.copyOf();
ParameterizedTypeHandlerMap<JsonDeserializer<?>> customDeserializers = deserializers.copyOf(); ParameterizedTypeHandlerMap<JsonDeserializer<?>> customDeserializers = deserializers.copyOf();
@ -445,8 +459,9 @@ public final class GsonBuilder {
JsonFormatter formatter = prettyPrinting ? JsonFormatter formatter = prettyPrinting ?
new JsonPrintFormatter(escapeHtmlChars) : new JsonCompactFormatter(escapeHtmlChars); new JsonPrintFormatter(escapeHtmlChars) : new JsonCompactFormatter(escapeHtmlChars);
Gson gson = new Gson(exclusionStrategy, fieldNamingPolicy, objConstructor, Gson gson = new Gson(serializationExclusionStrategy, deserializationExclusionStrategy,
formatter, serializeNulls, customSerializers, customDeserializers, generateNonExecutableJson); fieldNamingPolicy, objConstructor, formatter, serializeNulls, customSerializers,
customDeserializers, generateNonExecutableJson);
return gson; return gson;
} }

View File

@ -34,8 +34,8 @@ import java.lang.annotation.Target;
* <p><pre> * <p><pre>
* public class User { * public class User {
* &#64Expose private String firstName; * &#64Expose private String firstName;
* &#64Expose private String lastName; * &#64Expose(serialize = false) private String lastName;
* &#64Expose private String emailAddress; * &#64Expose (serialize = false, deserialize = false) private String emailAddress;
* private String password; * private String password;
* } * }
* </pre></p> * </pre></p>
@ -45,7 +45,9 @@ import java.lang.annotation.Target;
* with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()} * with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()}
* then the {@code toJson()} and {@code fromJson()} methods of Gson will exclude the * 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 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.
* *
* <p>Note that another way to achieve the same effect would have been to just mark the * <p>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 * {@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) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
public @interface Expose { 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;
} }

View File

@ -16,6 +16,7 @@
package com.google.gson; package com.google.gson;
import com.google.gson.ExposeAnnotationBasedExclusionStrategy.Phase;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import junit.framework.TestCase; import junit.framework.TestCase;
@ -33,7 +34,7 @@ public class ExposeAnnotationBasedExclusionStrategyTest extends TestCase {
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
strategy = new ExposeAnnotationBasedExclusionStrategy(); strategy = new ExposeAnnotationBasedExclusionStrategy(Phase.SERIALIZATION);
} }
public void testNeverSkipClasses() throws Exception { public void testNeverSkipClasses() throws Exception {

View File

@ -72,11 +72,12 @@ public class ExposeFieldsTest extends TestCase {
} }
public void testExposeAnnotationDeserialization() throws Exception { 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); ClassWithExposedFields target = gson.fromJson(json, ClassWithExposedFields.class);
assertEquals(3, (int) target.a); assertEquals(3, (int) target.a);
assertNull(target.b); assertNull(target.b);
assertFalse(target.d == 20);
} }
public void testNoExposedFieldSerialization() throws Exception { public void testNoExposedFieldSerialization() throws Exception {
@ -87,7 +88,7 @@ public class ExposeFieldsTest extends TestCase {
} }
public void testNoExposedFieldDeserialization() throws Exception { public void testNoExposedFieldDeserialization() throws Exception {
String json = '{' + "\"a\":" + 4 + ",\"b\":" + 5 + '}'; String json = "{a:4,b:5}";
ClassWithNoExposedFields obj = gson.fromJson(json, ClassWithNoExposedFields.class); ClassWithNoExposedFields obj = gson.fromJson(json, ClassWithNoExposedFields.class);
assertEquals(0, obj.a); assertEquals(0, obj.a);
@ -112,39 +113,37 @@ public class ExposeFieldsTest extends TestCase {
private static class ClassWithExposedFields { private static class ClassWithExposedFields {
@Expose private final Integer a; @Expose private final Integer a;
private final Integer b; 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() { ClassWithExposedFields() {
this(null, null); this(null, null);
} }
public ClassWithExposedFields(Integer a, Integer b) { 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.a = a;
this.b = b; this.b = b;
this.c = c;
this.d = d;
this.e = e;
} }
public String getExpectedJson() { public String getExpectedJson() {
if (a == null) { StringBuilder sb = new StringBuilder("{");
return "{}"; 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() { public String getExpectedJsonWithoutAnnotations() {
StringBuilder stringBuilder = new StringBuilder(); return String.format("{\"a\":%d,\"b\":%d,\"c\":%d,\"d\":%f,\"e\":\"%c\"}", a, b, c, d, e);
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();
} }
} }
@ -178,5 +177,5 @@ public class ExposeFieldsTest extends TestCase {
public ClassWithInterfaceField(SomeInterface interfaceField) { public ClassWithInterfaceField(SomeInterface interfaceField) {
this.interfaceField = interfaceField; this.interfaceField = interfaceField;
} }
} }
} }