Skip to content

Commit

Permalink
Added support for JsonSerializer/JsonDeserializer for JsonAdapter ann…
Browse files Browse the repository at this point in the history
…otation.

JsonAdapter is cached per the type of the JsonAdapter class.
Added a test to ensure JsonAdapter works on fields of parameterized types
Keep track of registered JsonAdapters and JsonAdapterFactorys in ThreadLocal.
  • Loading branch information
Inderjeet Singh authored and squarejesse committed Jun 2, 2016
1 parent 854760e commit 45511fd
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 29 deletions.
24 changes: 13 additions & 11 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public final class Gson {
private final boolean generateNonExecutableJson;
private final boolean prettyPrinting;
private final boolean lenient;
private JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;

/**
* Constructs a Gson object with default configuration. The default configuration has the
Expand Down Expand Up @@ -245,10 +246,11 @@ public Gson() {
// type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
factories.add(new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingStrategy, excluder));
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));

this.factories = Collections.unmodifiableList(factories);
}
Expand Down Expand Up @@ -486,26 +488,26 @@ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
* @since 2.2
*/
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
boolean skipPastFound = false;
// Skip past if and only if the specified factory is present in the factories.
// This is useful because the factories created through JsonAdapter annotations are not
// registered in this list.
if (!factories.contains(skipPast)) skipPastFound = true;
// If the specified skipPast factory is not registered, ignore it.
boolean skipPastFound = skipPast == null
|| (!factories.contains(skipPast) && jsonAdapterFactory.getDelegateAdapterFactory(type) == null);

for (TypeAdapterFactory factory : factories) {
if (!skipPastFound) {
if (factory == skipPast) {
skipPastFound = true;
skipPastFound = factory == skipPast;
if (!skipPastFound && factory instanceof JsonAdapterAnnotationTypeAdapterFactory) {
// Also check if there is a registered JsonAdapter for it
factory = ((JsonAdapterAnnotationTypeAdapterFactory)factory).getDelegateAdapterFactory(type);
skipPastFound = factory == skipPast;
}
continue;
}

TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
return candidate;
}
}
throw new IllegalArgumentException("GSON cannot serialize " + type);
throw new IllegalArgumentException("GSON cannot serialize or deserialize " + type);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

package com.google.gson.internal.bind;

import java.util.HashMap;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
Expand All @@ -31,6 +36,21 @@
*/
public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {

@SuppressWarnings("rawtypes")
private final ThreadLocal<Map<Class, TypeAdapter>> activeJsonAdapterClasses = new ThreadLocal<Map<Class, TypeAdapter>>() {
@Override protected Map<Class, TypeAdapter> initialValue() {
// No need for a thread-safe map since we are using it in a single thread
return new HashMap<Class, TypeAdapter>();
}
};
@SuppressWarnings("rawtypes")
private final ThreadLocal<Map<Class, TypeAdapterFactory>> activeJsonAdapterFactories = new ThreadLocal<Map<Class, TypeAdapterFactory>>() {
@Override protected Map<Class, TypeAdapterFactory> initialValue() {
// No need for a thread-safe map since we are using it in a single thread
return new HashMap<Class, TypeAdapterFactory>();
}
};

private final ConstructorConstructor constructorConstructor;

public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
Expand All @@ -40,33 +60,86 @@ public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructo
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
JsonAdapter annotation = targetType.getRawType().getAnnotation(JsonAdapter.class);
Class<? super T> rawType = targetType.getRawType();
JsonAdapter annotation = rawType.getAnnotation(JsonAdapter.class);
if (annotation == null) {
return null;
}
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
}

@SuppressWarnings("unchecked") // Casts guarded by conditionals.
static TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
TypeToken<?> fieldType, JsonAdapter annotation) {
public <T> TypeAdapter<T> getDelegateAdapter(Gson gson, TypeAdapterFactory skipPast, TypeToken<T> targetType) {
TypeAdapterFactory factory = getDelegateAdapterFactory(targetType);
if (factory == skipPast) factory = null;
return factory == null ? null: factory.create(gson, targetType);
}

public <T> TypeAdapterFactory getDelegateAdapterFactory(TypeToken<T> targetType) {
Class<?> annotatedClass = targetType.getRawType();
JsonAdapter annotation = annotatedClass.getAnnotation(JsonAdapter.class);
if (annotation == null) {
return null;
}
return getTypeAdapterFactory(annotation, constructorConstructor);
}

@SuppressWarnings({ "unchecked", "rawtypes" }) // Casts guarded by conditionals.
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
TypeToken<?> type, JsonAdapter annotation) {
Class<?> value = annotation.value();
boolean isTypeAdapter = TypeAdapter.class.isAssignableFrom(value);
boolean isJsonSerializer = JsonSerializer.class.isAssignableFrom(value);
boolean isJsonDeserializer = JsonDeserializer.class.isAssignableFrom(value);

TypeAdapter<?> typeAdapter;
if (TypeAdapter.class.isAssignableFrom(value)) {
Class<TypeAdapter<?>> typeAdapterClass = (Class<TypeAdapter<?>>) value;
typeAdapter = constructorConstructor.get(TypeToken.get(typeAdapterClass)).construct();
if (isTypeAdapter || isJsonSerializer || isJsonDeserializer) {
Map<Class, TypeAdapter> adapters = activeJsonAdapterClasses.get();
typeAdapter = adapters.get(value);
if (typeAdapter == null) {
if (isTypeAdapter) {
Class<TypeAdapter<?>> typeAdapterClass = (Class<TypeAdapter<?>>) value;
typeAdapter = constructorConstructor.get(TypeToken.get(typeAdapterClass)).construct();
} else if (isJsonSerializer || isJsonDeserializer) {
JsonSerializer serializer = null;
if (isJsonSerializer) {
Class<JsonSerializer<?>> serializerClass = (Class<JsonSerializer<?>>) value;
serializer = constructorConstructor.get(TypeToken.get(serializerClass)).construct();
}
JsonDeserializer deserializer = null;
if (isJsonDeserializer) {
Class<JsonDeserializer<?>> deserializerClass = (Class<JsonDeserializer<?>>) value;
deserializer = constructorConstructor.get(TypeToken.get(deserializerClass)).construct();
}
typeAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null);
}
adapters.put(value, typeAdapter);
}
} else if (TypeAdapterFactory.class.isAssignableFrom(value)) {
Class<TypeAdapterFactory> typeAdapterFactory = (Class<TypeAdapterFactory>) value;
typeAdapter = constructorConstructor.get(TypeToken.get(typeAdapterFactory))
.construct()
.create(gson, fieldType);
TypeAdapterFactory factory = getTypeAdapterFactory(annotation, constructorConstructor);
typeAdapter = factory == null ? null : factory.create(gson, type);
} else {
throw new IllegalArgumentException(
"@JsonAdapter value must be TypeAdapter or TypeAdapterFactory reference.");
"@JsonAdapter value must be TypeAdapter, TypeAdapterFactory, JsonSerializer or JsonDeserializer reference.");
}
if (typeAdapter != null) {
typeAdapter = typeAdapter.nullSafe();
}
return typeAdapter;
}


@SuppressWarnings({ "unchecked", "rawtypes" }) // Casts guarded by conditionals.
TypeAdapterFactory getTypeAdapterFactory(JsonAdapter annotation, ConstructorConstructor constructorConstructor) {
Class<?> value = annotation.value();
if (!TypeAdapterFactory.class.isAssignableFrom(value)) return null;
Map<Class, TypeAdapterFactory> adapterFactories = activeJsonAdapterFactories.get();
TypeAdapterFactory factory = adapterFactories.get(value);
if (factory == null) {
Class<TypeAdapterFactory> typeAdapterFactoryClass = (Class<TypeAdapterFactory>) value;
factory = constructorConstructor.get(TypeToken.get(typeAdapterFactoryClass))
.construct();
adapterFactories.put(value, factory);
}
return factory;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,22 @@
import java.util.List;
import java.util.Map;

import static com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.getTypeAdapter;

/**
* Type adapter that reflects over the fields and methods of a class.
*/
public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
private final FieldNamingStrategy fieldNamingPolicy;
private final Excluder excluder;
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;

public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory) {
this.constructorConstructor = constructorConstructor;
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
this.jsonAdapterFactory = jsonAdapterFactory;
}

public boolean excludeField(Field f, boolean serialize) {
Expand Down Expand Up @@ -108,7 +109,8 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
TypeAdapter<?> mapped = null;
if (annotation != null) {
mapped = getTypeAdapter(constructorConstructor, context, fieldType, annotation);
mapped = jsonAdapterFactory.getTypeAdapter(
constructorConstructor, context, fieldType, annotation);
}
final boolean jsonAdapterPresent = mapped != null;
if (mapped == null) mapped = context.getAdapter(fieldType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package com.google.gson.functional;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
Expand All @@ -24,7 +28,7 @@
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;

import junit.framework.TestCase;

/**
Expand Down Expand Up @@ -268,4 +272,35 @@ private static final class LongToStringTypeAdapterFactory implements TypeAdapter
+ " annotated with @JsonAdapter(LongToStringTypeAdapterFactory.class)");
}
}

public void testFieldAnnotationWorksForParameterizedType() {
Gson gson = new Gson();
String json = gson.toJson(new Gizmo2(Arrays.asList(new Part("Part"))));
assertEquals("{\"part\":\"GizmoPartTypeAdapterFactory\"}", json);
Gizmo2 computer = gson.fromJson("{'part':'Part'}", Gizmo2.class);
assertEquals("GizmoPartTypeAdapterFactory", computer.part.get(0).name);
}

private static final class Gizmo2 {
@JsonAdapter(Gizmo2PartTypeAdapterFactory.class)
List<Part> part;
Gizmo2(List<Part> part) {
this.part = part;
}
}

private static class Gizmo2PartTypeAdapterFactory implements TypeAdapterFactory {
@Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException {
out.value("GizmoPartTypeAdapterFactory");
}
@SuppressWarnings("unchecked")
@Override public T read(JsonReader in) throws IOException {
in.nextString();
return (T) Arrays.asList(new Part("GizmoPartTypeAdapterFactory"));
}
};
}
}
}
Loading

0 comments on commit 45511fd

Please sign in to comment.