mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
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:
committed by
Jesse Wilson
parent
a8102bccd2
commit
5c41565f39
@@ -24,6 +24,7 @@ import com.squareup.moshi.JsonWriter
|
|||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.squareup.moshi.internal.Util
|
import com.squareup.moshi.internal.Util
|
||||||
|
import com.squareup.moshi.internal.Util.generatedAdapter
|
||||||
import com.squareup.moshi.internal.Util.resolve
|
import com.squareup.moshi.internal.Util.resolve
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
@@ -174,8 +175,17 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||||||
if (rawType.isEnum) return null
|
if (rawType.isEnum) return null
|
||||||
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
|
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
|
||||||
if (Util.isPlatformType(rawType)) return null
|
if (Util.isPlatformType(rawType)) return null
|
||||||
val jsonClass = rawType.getAnnotation(JsonClass::class.java)
|
try {
|
||||||
if (jsonClass != null && jsonClass.generateAdapter) return null
|
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) {
|
if (rawType.isLocalClass) {
|
||||||
throw IllegalArgumentException("Cannot serialize local class or object expression ${rawType.name}")
|
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 com.squareup.moshi.internal.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.annotation.Annotation;
|
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.lang.reflect.Type;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static com.squareup.moshi.internal.Util.generatedAdapter;
|
||||||
|
|
||||||
final class StandardJsonAdapters {
|
final class StandardJsonAdapters {
|
||||||
private StandardJsonAdapters() {
|
private StandardJsonAdapters() {
|
||||||
@@ -57,9 +57,9 @@ final class StandardJsonAdapters {
|
|||||||
|
|
||||||
Class<?> rawType = Types.getRawType(type);
|
Class<?> rawType = Types.getRawType(type);
|
||||||
|
|
||||||
JsonClass jsonClass = rawType.getAnnotation(JsonClass.class);
|
@Nullable JsonAdapter<?> generatedAdapter = generatedAdapter(moshi, type, rawType);
|
||||||
if (jsonClass != null && jsonClass.generateAdapter()) {
|
if (generatedAdapter != null) {
|
||||||
return generatedAdapter(moshi, type, rawType).nullSafe();
|
return generatedAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawType.isEnum()) {
|
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> {
|
static final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
|
||||||
private final Class<T> enumType;
|
private final Class<T> enumType;
|
||||||
private final String[] nameStrings;
|
private final String[] nameStrings;
|
||||||
|
@@ -15,10 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.moshi.internal;
|
package com.squareup.moshi.internal;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.JsonClass;
|
||||||
import com.squareup.moshi.JsonQualifier;
|
import com.squareup.moshi.JsonQualifier;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
import com.squareup.moshi.Types;
|
import com.squareup.moshi.Types;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.GenericArrayType;
|
import java.lang.reflect.GenericArrayType;
|
||||||
import java.lang.reflect.GenericDeclaration;
|
import java.lang.reflect.GenericDeclaration;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -445,4 +449,48 @@ public final class Util {
|
|||||||
Set<? extends Annotation> annotations) {
|
Set<? extends Annotation> annotations) {
|
||||||
return type + (annotations.isEmpty() ? " (with no annotations)" : " annotated " + 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user