Fall back to reflection when generated adapter is not found. (#728)
* Fall back to reflection when generated adapter is not found. This is handy for development builds where kapt is disabled and models still have @JsonClass(generatedAdapter = true). * Add test for Kotlin reflection fallback behavior.
This commit is contained in:
parent
a8102bccd2
commit
5c41565f39
4 changed files with 87 additions and 46 deletions
|
@ -24,6 +24,7 @@ import com.squareup.moshi.JsonWriter
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.squareup.moshi.internal.Util
|
||||
import com.squareup.moshi.internal.Util.generatedAdapter
|
||||
import com.squareup.moshi.internal.Util.resolve
|
||||
import java.lang.reflect.Modifier
|
||||
import java.lang.reflect.Type
|
||||
|
@ -174,8 +175,17 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||
if (rawType.isEnum) return null
|
||||
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
|
||||
if (Util.isPlatformType(rawType)) return null
|
||||
val jsonClass = rawType.getAnnotation(JsonClass::class.java)
|
||||
if (jsonClass != null && jsonClass.generateAdapter) return null
|
||||
try {
|
||||
val generatedAdapter = generatedAdapter(moshi, type, rawType)
|
||||
if (generatedAdapter != null) {
|
||||
return generatedAdapter
|
||||
}
|
||||
} catch (e: RuntimeException) {
|
||||
if (e.cause !is ClassNotFoundException) {
|
||||
throw e
|
||||
}
|
||||
// Fall back to a reflective adapter when the generated adapter is not found.
|
||||
}
|
||||
|
||||
if (rawType.isLocalClass) {
|
||||
throw IllegalArgumentException("Cannot serialize local class or object expression ${rawType.name}")
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package com.squareup.moshi.kotlin.reflect
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class KotlinJsonAdapterTest {
|
||||
@JsonClass(generateAdapter = true)
|
||||
class Data
|
||||
|
||||
@Test fun fallsBackToReflectiveAdapterWithoutCodegen() {
|
||||
val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
val adapter = moshi.adapter(Data::class.java)
|
||||
assertThat(adapter.toString()).isEqualTo(
|
||||
"KotlinJsonAdapter(com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest.Data).nullSafe()"
|
||||
)
|
||||
}
|
||||
}
|
|
@ -18,15 +18,15 @@ package com.squareup.moshi;
|
|||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.squareup.moshi.internal.Util.generatedAdapter;
|
||||
|
||||
final class StandardJsonAdapters {
|
||||
private StandardJsonAdapters() {
|
||||
|
@ -57,9 +57,9 @@ final class StandardJsonAdapters {
|
|||
|
||||
Class<?> rawType = Types.getRawType(type);
|
||||
|
||||
JsonClass jsonClass = rawType.getAnnotation(JsonClass.class);
|
||||
if (jsonClass != null && jsonClass.generateAdapter()) {
|
||||
return generatedAdapter(moshi, type, rawType).nullSafe();
|
||||
@Nullable JsonAdapter<?> generatedAdapter = generatedAdapter(moshi, type, rawType);
|
||||
if (generatedAdapter != null) {
|
||||
return generatedAdapter;
|
||||
}
|
||||
|
||||
if (rawType.isEnum()) {
|
||||
|
@ -224,44 +224,6 @@ final class StandardJsonAdapters {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads the generated JsonAdapter for classes annotated {@link JsonClass}. This works because it
|
||||
* uses the same naming conventions as {@code JsonClassCodeGenProcessor}.
|
||||
*/
|
||||
static JsonAdapter<?> generatedAdapter(Moshi moshi, Type type, Class<?> rawType) {
|
||||
String adapterClassName = rawType.getName().replace("$", "_") + "JsonAdapter";
|
||||
try {
|
||||
@SuppressWarnings("unchecked") // We generate types to match.
|
||||
Class<? extends JsonAdapter<?>> adapterClass = (Class<? extends JsonAdapter<?>>)
|
||||
Class.forName(adapterClassName, true, rawType.getClassLoader());
|
||||
if (type instanceof ParameterizedType) {
|
||||
Constructor<? extends JsonAdapter<?>> constructor
|
||||
= adapterClass.getDeclaredConstructor(Moshi.class, Type[].class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(moshi, ((ParameterizedType) type).getActualTypeArguments());
|
||||
} else {
|
||||
Constructor<? extends JsonAdapter<?>> constructor
|
||||
= adapterClass.getDeclaredConstructor(Moshi.class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(moshi);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to find the generated JsonAdapter class for " + rawType, e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to find the generated JsonAdapter constructor for " + rawType, e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to access the generated JsonAdapter for " + rawType, e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to instantiate the generated JsonAdapter for " + rawType, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw Util.rethrowCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
static final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
|
||||
private final Class<T> enumType;
|
||||
private final String[] nameStrings;
|
||||
|
|
|
@ -15,10 +15,14 @@
|
|||
*/
|
||||
package com.squareup.moshi.internal;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.JsonClass;
|
||||
import com.squareup.moshi.JsonQualifier;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.squareup.moshi.Types;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.GenericDeclaration;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
@ -445,4 +449,48 @@ public final class Util {
|
|||
Set<? extends Annotation> annotations) {
|
||||
return type + (annotations.isEmpty() ? " (with no annotations)" : " annotated " + annotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the generated JsonAdapter for classes annotated {@link JsonClass}. This works because it
|
||||
* uses the same naming conventions as {@code JsonClassCodeGenProcessor}.
|
||||
*/
|
||||
public static @Nullable JsonAdapter<?> generatedAdapter(Moshi moshi, Type type,
|
||||
Class<?> rawType) {
|
||||
JsonClass jsonClass = rawType.getAnnotation(JsonClass.class);
|
||||
if (jsonClass == null || !jsonClass.generateAdapter()) {
|
||||
return null;
|
||||
}
|
||||
String adapterClassName = rawType.getName().replace("$", "_") + "JsonAdapter";
|
||||
try {
|
||||
@SuppressWarnings("unchecked") // We generate types to match.
|
||||
Class<? extends JsonAdapter<?>> adapterClass = (Class<? extends JsonAdapter<?>>)
|
||||
Class.forName(adapterClassName, true, rawType.getClassLoader());
|
||||
if (type instanceof ParameterizedType) {
|
||||
Constructor<? extends JsonAdapter<?>> constructor
|
||||
= adapterClass.getDeclaredConstructor(Moshi.class, Type[].class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(moshi, ((ParameterizedType) type).getActualTypeArguments())
|
||||
.nullSafe();
|
||||
} else {
|
||||
Constructor<? extends JsonAdapter<?>> constructor
|
||||
= adapterClass.getDeclaredConstructor(Moshi.class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(moshi).nullSafe();
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to find the generated JsonAdapter class for " + rawType, e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to find the generated JsonAdapter constructor for " + rawType, e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to access the generated JsonAdapter for " + rawType, e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to instantiate the generated JsonAdapter for " + rawType, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw rethrowCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue