mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 07:59:21 +08:00
Convert ClassJsonAdapter to kotlin (#1470)
This commit is contained in:
@@ -32,6 +32,7 @@ val japicmp = tasks.register<JapicmpTask>("japicmp") {
|
|||||||
"com.squareup.moshi.StandardJsonAdapters", // Package-private
|
"com.squareup.moshi.StandardJsonAdapters", // Package-private
|
||||||
"com.squareup.moshi.RecordJsonAdapter\$ComponentBinding", // Package-private
|
"com.squareup.moshi.RecordJsonAdapter\$ComponentBinding", // Package-private
|
||||||
"com.squareup.moshi.AdapterMethodsFactory", // Internal.
|
"com.squareup.moshi.AdapterMethodsFactory", // Internal.
|
||||||
|
"com.squareup.moshi.ClassJsonAdapter", // Internal.
|
||||||
)
|
)
|
||||||
methodExcludes = listOf(
|
methodExcludes = listOf(
|
||||||
"com.squareup.moshi.JsonAdapter#indent(java.lang.String)", // Was unintentionally open before
|
"com.squareup.moshi.JsonAdapter#indent(java.lang.String)", // Was unintentionally open before
|
||||||
@@ -39,7 +40,7 @@ val japicmp = tasks.register<JapicmpTask>("japicmp") {
|
|||||||
fieldExcludes = listOf(
|
fieldExcludes = listOf(
|
||||||
"com.squareup.moshi.CollectionJsonAdapter#FACTORY", // False-positive, class is not public anyway
|
"com.squareup.moshi.CollectionJsonAdapter#FACTORY", // False-positive, class is not public anyway
|
||||||
"com.squareup.moshi.MapJsonAdapter#FACTORY", // Class is not public
|
"com.squareup.moshi.MapJsonAdapter#FACTORY", // Class is not public
|
||||||
"com.squareup.moshi.ArrayJsonAdapter#FACTORY" // Class is not public
|
"com.squareup.moshi.ArrayJsonAdapter#FACTORY", // Class is not public
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,10 +16,10 @@
|
|||||||
package com.squareup.moshi
|
package com.squareup.moshi
|
||||||
|
|
||||||
import com.squareup.moshi.internal.canonicalize
|
import com.squareup.moshi.internal.canonicalize
|
||||||
|
import com.squareup.moshi.internal.checkNull
|
||||||
import com.squareup.moshi.internal.hasNullable
|
import com.squareup.moshi.internal.hasNullable
|
||||||
import com.squareup.moshi.internal.jsonAnnotations
|
import com.squareup.moshi.internal.jsonAnnotations
|
||||||
import com.squareup.moshi.internal.knownNotNull
|
import com.squareup.moshi.internal.knownNotNull
|
||||||
import com.squareup.moshi.internal.checkNull
|
|
||||||
import com.squareup.moshi.internal.toStringWithAnnotations
|
import com.squareup.moshi.internal.toStringWithAnnotations
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
@@ -191,12 +191,14 @@ internal class AdapterMethodsFactory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw IllegalArgumentException("""Unexpected signature for $method.
|
throw IllegalArgumentException(
|
||||||
|
"""Unexpected signature for $method.
|
||||||
@ToJson method signatures may have one of the following structures:
|
@ToJson method signatures may have one of the following structures:
|
||||||
<any access modifier> void toJson(JsonWriter writer, T value) throws <any>;
|
<any access modifier> void toJson(JsonWriter writer, T value) throws <any>;
|
||||||
<any access modifier> void toJson(JsonWriter writer, T value, JsonAdapter<any> delegate, <any more delegates>) throws <any>;
|
<any access modifier> void toJson(JsonWriter writer, T value, JsonAdapter<any> delegate, <any more delegates>) throws <any>;
|
||||||
<any access modifier> R toJson(T value) throws <any>;
|
<any access modifier> R toJson(T value) throws <any>;
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,12 +274,14 @@ internal class AdapterMethodsFactory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw IllegalArgumentException("""Unexpected signature for $method.
|
throw IllegalArgumentException(
|
||||||
|
"""Unexpected signature for $method.
|
||||||
@FromJson method signatures may have one of the following structures:
|
@FromJson method signatures may have one of the following structures:
|
||||||
<any access modifier> R fromJson(JsonReader jsonReader) throws <any>;
|
<any access modifier> R fromJson(JsonReader jsonReader) throws <any>;
|
||||||
<any access modifier> R fromJson(JsonReader jsonReader, JsonAdapter<any> delegate, <any more delegates>) throws <any>;
|
<any access modifier> R fromJson(JsonReader jsonReader, JsonAdapter<any> delegate, <any more delegates>) throws <any>;
|
||||||
<any access modifier> R fromJson(T value) throws <any>;
|
<any access modifier> R fromJson(T value) throws <any>;
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,6 @@ import com.squareup.moshi.internal.rethrowCause
|
|||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
import java.io.ObjectStreamClass
|
import java.io.ObjectStreamClass
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import kotlin.Throws
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Magic that creates instances of arbitrary concrete classes. Derived from Gson's UnsafeAllocator
|
* Magic that creates instances of arbitrary concrete classes. Derived from Gson's UnsafeAllocator
|
||||||
@@ -29,7 +28,6 @@ import kotlin.Throws
|
|||||||
* @author Jesse Wilson
|
* @author Jesse Wilson
|
||||||
*/
|
*/
|
||||||
internal abstract class ClassFactory<T> {
|
internal abstract class ClassFactory<T> {
|
||||||
@Throws(InvocationTargetException::class, IllegalAccessException::class, InstantiationException::class)
|
|
||||||
abstract fun newInstance(): T
|
abstract fun newInstance(): T
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@@ -1,255 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Square, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.squareup.moshi;
|
|
||||||
|
|
||||||
import static com.squareup.moshi.internal.Util.jsonName;
|
|
||||||
import static com.squareup.moshi.internal.Util.resolve;
|
|
||||||
|
|
||||||
import com.squareup.moshi.internal.Util;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits a regular class as a JSON object by mapping Java fields to JSON object properties.
|
|
||||||
*
|
|
||||||
* <h1>Platform Types</h1>
|
|
||||||
*
|
|
||||||
* Fields from platform classes are omitted from both serialization and deserialization unless they
|
|
||||||
* are either public or protected. This includes the following packages and their subpackages:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>android.*
|
|
||||||
* <li>androidx.*
|
|
||||||
* <li>java.*
|
|
||||||
* <li>javax.*
|
|
||||||
* <li>kotlin.*
|
|
||||||
* <li>kotlinx.*
|
|
||||||
* <li>scala.*
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
|
||||||
public static final JsonAdapter.Factory FACTORY =
|
|
||||||
new JsonAdapter.Factory() {
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonAdapter<?> create(
|
|
||||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
|
||||||
if (!(type instanceof Class) && !(type instanceof ParameterizedType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Class<?> rawType = Types.getRawType(type);
|
|
||||||
if (rawType.isInterface() || rawType.isEnum()) return null;
|
|
||||||
if (!annotations.isEmpty()) return null;
|
|
||||||
if (Util.isPlatformType(rawType)) {
|
|
||||||
throwIfIsCollectionClass(type, List.class);
|
|
||||||
throwIfIsCollectionClass(type, Set.class);
|
|
||||||
throwIfIsCollectionClass(type, Map.class);
|
|
||||||
throwIfIsCollectionClass(type, Collection.class);
|
|
||||||
|
|
||||||
String messagePrefix = "Platform " + rawType;
|
|
||||||
if (type instanceof ParameterizedType) {
|
|
||||||
messagePrefix += " in " + type;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
messagePrefix + " requires explicit JsonAdapter to be registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawType.isAnonymousClass()) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Cannot serialize anonymous class " + rawType.getName());
|
|
||||||
}
|
|
||||||
if (rawType.isLocalClass()) {
|
|
||||||
throw new IllegalArgumentException("Cannot serialize local class " + rawType.getName());
|
|
||||||
}
|
|
||||||
if (rawType.getEnclosingClass() != null && !Modifier.isStatic(rawType.getModifiers())) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Cannot serialize non-static nested class " + rawType.getName());
|
|
||||||
}
|
|
||||||
if (Modifier.isAbstract(rawType.getModifiers())) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Cannot serialize abstract class " + rawType.getName());
|
|
||||||
}
|
|
||||||
if (Util.isKotlin(rawType)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Cannot serialize Kotlin type "
|
|
||||||
+ rawType.getName()
|
|
||||||
+ ". Reflective serialization of Kotlin classes without using kotlin-reflect has "
|
|
||||||
+ "undefined and unexpected behavior. Please use KotlinJsonAdapterFactory from the "
|
|
||||||
+ "moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassFactory<Object> classFactory = ClassFactory.get(rawType);
|
|
||||||
Map<String, FieldBinding<?>> fields = new TreeMap<>();
|
|
||||||
for (Type t = type; t != Object.class; t = Types.getGenericSuperclass(t)) {
|
|
||||||
createFieldBindings(moshi, t, fields);
|
|
||||||
}
|
|
||||||
return new ClassJsonAdapter<>(classFactory, fields).nullSafe();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throw clear error messages for the common beginner mistake of using the concrete
|
|
||||||
* collection classes instead of the collection interfaces, eg: ArrayList instead of List.
|
|
||||||
*/
|
|
||||||
private void throwIfIsCollectionClass(Type type, Class<?> collectionInterface) {
|
|
||||||
Class<?> rawClass = Types.getRawType(type);
|
|
||||||
if (collectionInterface.isAssignableFrom(rawClass)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"No JsonAdapter for "
|
|
||||||
+ type
|
|
||||||
+ ", you should probably use "
|
|
||||||
+ collectionInterface.getSimpleName()
|
|
||||||
+ " instead of "
|
|
||||||
+ rawClass.getSimpleName()
|
|
||||||
+ " (Moshi only supports the collection interfaces by default)"
|
|
||||||
+ " or else register a custom JsonAdapter.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates a field binding for each of declared field of {@code type}. */
|
|
||||||
private void createFieldBindings(
|
|
||||||
Moshi moshi, Type type, Map<String, FieldBinding<?>> fieldBindings) {
|
|
||||||
Class<?> rawType = Types.getRawType(type);
|
|
||||||
boolean platformType = Util.isPlatformType(rawType);
|
|
||||||
for (Field field : rawType.getDeclaredFields()) {
|
|
||||||
if (!includeField(platformType, field.getModifiers())) continue;
|
|
||||||
Json jsonAnnotation = field.getAnnotation(Json.class);
|
|
||||||
if (jsonAnnotation != null && jsonAnnotation.ignore()) continue;
|
|
||||||
|
|
||||||
// Look up a type adapter for this type.
|
|
||||||
Type fieldType = resolve(field.getGenericType(), type, rawType);
|
|
||||||
Set<? extends Annotation> annotations = Util.getJsonAnnotations(field);
|
|
||||||
String fieldName = field.getName();
|
|
||||||
JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations, fieldName);
|
|
||||||
|
|
||||||
// Create the binding between field and JSON.
|
|
||||||
field.setAccessible(true);
|
|
||||||
|
|
||||||
// Store it using the field's name. If there was already a field with this name, fail!
|
|
||||||
String jsonName = jsonName(jsonAnnotation, fieldName);
|
|
||||||
FieldBinding<Object> fieldBinding = new FieldBinding<>(jsonName, field, adapter);
|
|
||||||
FieldBinding<?> replaced = fieldBindings.put(jsonName, fieldBinding);
|
|
||||||
if (replaced != null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Conflicting fields:\n"
|
|
||||||
+ " "
|
|
||||||
+ replaced.field
|
|
||||||
+ "\n"
|
|
||||||
+ " "
|
|
||||||
+ fieldBinding.field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns true if fields with {@code modifiers} are included in the emitted JSON. */
|
|
||||||
private boolean includeField(boolean platformType, int modifiers) {
|
|
||||||
if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) return false;
|
|
||||||
return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || !platformType;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ClassFactory<T> classFactory;
|
|
||||||
private final FieldBinding<?>[] fieldsArray;
|
|
||||||
private final JsonReader.Options options;
|
|
||||||
|
|
||||||
ClassJsonAdapter(ClassFactory<T> classFactory, Map<String, FieldBinding<?>> fieldsMap) {
|
|
||||||
this.classFactory = classFactory;
|
|
||||||
this.fieldsArray = fieldsMap.values().toArray(new FieldBinding[fieldsMap.size()]);
|
|
||||||
this.options = JsonReader.Options.of(fieldsMap.keySet().toArray(new String[fieldsMap.size()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T fromJson(JsonReader reader) throws IOException {
|
|
||||||
T result;
|
|
||||||
try {
|
|
||||||
result = classFactory.newInstance();
|
|
||||||
} catch (InstantiationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
throw Util.rethrowCause(e);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
reader.beginObject();
|
|
||||||
while (reader.hasNext()) {
|
|
||||||
int index = reader.selectName(options);
|
|
||||||
if (index == -1) {
|
|
||||||
reader.skipName();
|
|
||||||
reader.skipValue();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
fieldsArray[index].read(reader, result);
|
|
||||||
}
|
|
||||||
reader.endObject();
|
|
||||||
return result;
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, T value) throws IOException {
|
|
||||||
try {
|
|
||||||
writer.beginObject();
|
|
||||||
for (FieldBinding<?> fieldBinding : fieldsArray) {
|
|
||||||
writer.name(fieldBinding.name);
|
|
||||||
fieldBinding.write(writer, value);
|
|
||||||
}
|
|
||||||
writer.endObject();
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "JsonAdapter(" + classFactory + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
static class FieldBinding<T> {
|
|
||||||
final String name;
|
|
||||||
final Field field;
|
|
||||||
final JsonAdapter<T> adapter;
|
|
||||||
|
|
||||||
FieldBinding(String name, Field field, JsonAdapter<T> adapter) {
|
|
||||||
this.name = name;
|
|
||||||
this.field = field;
|
|
||||||
this.adapter = adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void read(JsonReader reader, Object value) throws IOException, IllegalAccessException {
|
|
||||||
T fieldValue = adapter.fromJson(reader);
|
|
||||||
field.set(value, fieldValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") // We require that field's values are of type T.
|
|
||||||
void write(JsonWriter writer, Object value) throws IllegalAccessException, IOException {
|
|
||||||
T fieldValue = (T) field.get(value);
|
|
||||||
adapter.toJson(writer, fieldValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
223
moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.kt
Normal file
223
moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.kt
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.squareup.moshi
|
||||||
|
|
||||||
|
import com.squareup.moshi.internal.checkNull
|
||||||
|
import com.squareup.moshi.internal.isKotlin
|
||||||
|
import com.squareup.moshi.internal.isPlatformType
|
||||||
|
import com.squareup.moshi.internal.jsonAnnotations
|
||||||
|
import com.squareup.moshi.internal.jsonName
|
||||||
|
import com.squareup.moshi.internal.resolve
|
||||||
|
import com.squareup.moshi.internal.rethrowCause
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.lang.reflect.Modifier.isAbstract
|
||||||
|
import java.lang.reflect.Modifier.isProtected
|
||||||
|
import java.lang.reflect.Modifier.isPublic
|
||||||
|
import java.lang.reflect.Modifier.isStatic
|
||||||
|
import java.lang.reflect.Modifier.isTransient
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits a regular class as a JSON object by mapping Java fields to JSON object properties.
|
||||||
|
*
|
||||||
|
* # Platform Types
|
||||||
|
*
|
||||||
|
* Fields from platform classes are omitted from both serialization and deserialization unless they
|
||||||
|
* are either public or protected. This includes the following packages and their subpackages:
|
||||||
|
*
|
||||||
|
* * `android.*`
|
||||||
|
* * `androidx.*`
|
||||||
|
* * `java.*`
|
||||||
|
* * `javax.*`
|
||||||
|
* * `kotlin.*`
|
||||||
|
* * `kotlinx.*`
|
||||||
|
* * `scala.*`
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
internal class ClassJsonAdapter<T>(
|
||||||
|
private val classFactory: ClassFactory<T>,
|
||||||
|
fieldsMap: Map<String, FieldBinding<*>>
|
||||||
|
) : JsonAdapter<T>() {
|
||||||
|
private val fieldsArray = fieldsMap.values.toTypedArray()
|
||||||
|
private val options = JsonReader.Options.of(*fieldsMap.keys.toTypedArray())
|
||||||
|
|
||||||
|
override fun fromJson(reader: JsonReader): T {
|
||||||
|
val result: T = try {
|
||||||
|
classFactory.newInstance()
|
||||||
|
} catch (e: InstantiationException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
throw e.rethrowCause()
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
throw AssertionError()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.beginObject()
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
val index = reader.selectName(options)
|
||||||
|
if (index == -1) {
|
||||||
|
reader.skipName()
|
||||||
|
reader.skipValue()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fieldsArray[index].read(reader, result)
|
||||||
|
}
|
||||||
|
reader.endObject()
|
||||||
|
return result
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
throw AssertionError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toJson(writer: JsonWriter, value: T?) {
|
||||||
|
try {
|
||||||
|
writer.beginObject()
|
||||||
|
for (fieldBinding in fieldsArray) {
|
||||||
|
writer.name(fieldBinding.name)
|
||||||
|
fieldBinding.write(writer, value)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
throw AssertionError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = "JsonAdapter($classFactory)"
|
||||||
|
|
||||||
|
internal class FieldBinding<T>(val name: String, val field: Field, val adapter: JsonAdapter<T>) {
|
||||||
|
fun read(reader: JsonReader, value: Any?) {
|
||||||
|
val fieldValue = adapter.fromJson(reader)
|
||||||
|
field[value] = fieldValue
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") // We require that field's values are of type T.
|
||||||
|
fun write(writer: JsonWriter, value: Any?) {
|
||||||
|
val fieldValue = field[value] as T
|
||||||
|
adapter.toJson(writer, fieldValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Factory : JsonAdapter.Factory {
|
||||||
|
override fun create(
|
||||||
|
type: Type,
|
||||||
|
annotations: Set<Annotation>,
|
||||||
|
moshi: Moshi
|
||||||
|
): JsonAdapter<*>? {
|
||||||
|
if (type !is Class<*> && type !is ParameterizedType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val rawType = type.rawType
|
||||||
|
if (rawType.isInterface || rawType.isEnum) return null
|
||||||
|
if (annotations.isNotEmpty()) return null
|
||||||
|
if (rawType.isPlatformType) {
|
||||||
|
type.throwIfIsCollectionClass(List::class.java)
|
||||||
|
type.throwIfIsCollectionClass(Set::class.java)
|
||||||
|
type.throwIfIsCollectionClass(Map::class.java)
|
||||||
|
type.throwIfIsCollectionClass(Collection::class.java)
|
||||||
|
|
||||||
|
val messagePrefix = buildString {
|
||||||
|
append("Platform $rawType")
|
||||||
|
if (type is ParameterizedType) {
|
||||||
|
append(" in $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"$messagePrefix requires explicit JsonAdapter to be registered"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
require(!rawType.isAnonymousClass) {
|
||||||
|
"Cannot serialize anonymous class ${rawType.name}"
|
||||||
|
}
|
||||||
|
require(!rawType.isLocalClass) {
|
||||||
|
"Cannot serialize local class ${rawType.name}"
|
||||||
|
}
|
||||||
|
val isNonStaticNestedClass = rawType.enclosingClass != null && !isStatic(rawType.modifiers)
|
||||||
|
require(!isNonStaticNestedClass) {
|
||||||
|
"Cannot serialize non-static nested class ${rawType.name}"
|
||||||
|
}
|
||||||
|
require(!isAbstract(rawType.modifiers)) {
|
||||||
|
"Cannot serialize abstract class ${rawType.name}"
|
||||||
|
}
|
||||||
|
require(!rawType.isKotlin) {
|
||||||
|
"Cannot serialize Kotlin type ${rawType.name}. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapterFactory from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact."
|
||||||
|
}
|
||||||
|
val classFactory = ClassFactory.get<Any>(rawType)
|
||||||
|
val fields = sortedMapOf<String, FieldBinding<*>>()
|
||||||
|
var parentType = type
|
||||||
|
while (parentType != Any::class.java) {
|
||||||
|
createFieldBindings(moshi, parentType, fields)
|
||||||
|
parentType = Types.getGenericSuperclass(parentType)
|
||||||
|
}
|
||||||
|
return ClassJsonAdapter(classFactory, fields).nullSafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw clear error messages for the common beginner mistake of using the concrete
|
||||||
|
* collection classes instead of the collection interfaces, eg: ArrayList instead of List.
|
||||||
|
*/
|
||||||
|
private fun Type.throwIfIsCollectionClass(collectionInterface: Class<*>) {
|
||||||
|
require(!collectionInterface.isAssignableFrom(rawType)) {
|
||||||
|
"No JsonAdapter for $this, you should probably use ${collectionInterface.simpleName} instead of ${rawType.simpleName} (Moshi only supports the collection interfaces by default) or else register a custom JsonAdapter."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a field binding for each of declared field of `type`. */
|
||||||
|
private fun createFieldBindings(
|
||||||
|
moshi: Moshi,
|
||||||
|
type: Type,
|
||||||
|
fieldBindings: MutableMap<String, FieldBinding<*>>
|
||||||
|
) {
|
||||||
|
val rawType = type.rawType
|
||||||
|
val platformType = rawType.isPlatformType
|
||||||
|
for (field in rawType.declaredFields) {
|
||||||
|
if (!includeField(platformType, field.modifiers)) continue
|
||||||
|
val jsonAnnotation = field.getAnnotation(Json::class.java)
|
||||||
|
if (jsonAnnotation != null && jsonAnnotation.ignore) continue
|
||||||
|
|
||||||
|
// Look up a type adapter for this type.
|
||||||
|
val fieldType = field.genericType.resolve(type, rawType)
|
||||||
|
val annotations = field.jsonAnnotations
|
||||||
|
val fieldName = field.name
|
||||||
|
val adapter = moshi.adapter<Any>(
|
||||||
|
type = fieldType,
|
||||||
|
annotations = annotations,
|
||||||
|
fieldName = fieldName
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create the binding between field and JSON.
|
||||||
|
field.isAccessible = true
|
||||||
|
|
||||||
|
// Store it using the field's name. If there was already a field with this name, fail!
|
||||||
|
val jsonName = jsonAnnotation.jsonName(fieldName)
|
||||||
|
val fieldBinding = FieldBinding(jsonName, field, adapter)
|
||||||
|
val replaced = fieldBindings.put(jsonName, fieldBinding)
|
||||||
|
checkNull(replaced) {
|
||||||
|
"Conflicting fields:\n ${it.field}\n ${fieldBinding.field}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if fields with `modifiers` are included in the emitted JSON. */
|
||||||
|
private fun includeField(platformType: Boolean, modifiers: Int): Boolean {
|
||||||
|
if (isStatic(modifiers) || isTransient(modifiers)) return false
|
||||||
|
return isPublic(modifiers) || isProtected(modifiers) || !platformType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -369,7 +369,7 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
add(MapJsonAdapter.Factory)
|
add(MapJsonAdapter.Factory)
|
||||||
add(ArrayJsonAdapter.Factory)
|
add(ArrayJsonAdapter.Factory)
|
||||||
add(RecordJsonAdapter.Factory)
|
add(RecordJsonAdapter.Factory)
|
||||||
add(ClassJsonAdapter.FACTORY)
|
add(ClassJsonAdapter.Factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> newAdapterFactory(
|
fun <T> newAdapterFactory(
|
||||||
|
@@ -184,9 +184,9 @@ public final class ClassJsonAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void fieldNameCollision() throws Exception {
|
public void fieldNameCollision() throws Exception {
|
||||||
try {
|
try {
|
||||||
ClassJsonAdapter.FACTORY.create(ExtendsBaseA.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.Factory.create(ExtendsBaseA.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalStateException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
.hasMessageThat()
|
.hasMessageThat()
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
@@ -206,9 +206,9 @@ public final class ClassJsonAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void jsonAnnotationNameCollision() throws Exception {
|
public void jsonAnnotationNameCollision() throws Exception {
|
||||||
try {
|
try {
|
||||||
ClassJsonAdapter.FACTORY.create(NameCollision.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.Factory.create(NameCollision.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalStateException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
.hasMessageThat()
|
.hasMessageThat()
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
@@ -364,7 +364,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void nonStaticNestedClassNotSupported() throws Exception {
|
public void nonStaticNestedClassNotSupported() throws Exception {
|
||||||
try {
|
try {
|
||||||
ClassJsonAdapter.FACTORY.create(NonStatic.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.Factory.create(NonStatic.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
@@ -385,7 +385,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
ClassJsonAdapter.FACTORY.create(c.getClass(), NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.Factory.create(c.getClass(), NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
@@ -398,7 +398,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
public void localClassNotSupported() throws Exception {
|
public void localClassNotSupported() throws Exception {
|
||||||
class Local {}
|
class Local {}
|
||||||
try {
|
try {
|
||||||
ClassJsonAdapter.FACTORY.create(Local.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.Factory.create(Local.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
@@ -412,7 +412,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void interfaceNotSupported() throws Exception {
|
public void interfaceNotSupported() throws Exception {
|
||||||
assertThat(ClassJsonAdapter.FACTORY.create(Interface.class, NO_ANNOTATIONS, moshi)).isNull();
|
assertThat(ClassJsonAdapter.Factory.create(Interface.class, NO_ANNOTATIONS, moshi)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract static class Abstract {}
|
abstract static class Abstract {}
|
||||||
@@ -420,7 +420,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void abstractClassNotSupported() throws Exception {
|
public void abstractClassNotSupported() throws Exception {
|
||||||
try {
|
try {
|
||||||
ClassJsonAdapter.FACTORY.create(Abstract.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.Factory.create(Abstract.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected)
|
assertThat(expected)
|
||||||
@@ -529,7 +529,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
JsonAdapter<Box<Integer>> adapter =
|
JsonAdapter<Box<Integer>> adapter =
|
||||||
(JsonAdapter<Box<Integer>>)
|
(JsonAdapter<Box<Integer>>)
|
||||||
ClassJsonAdapter.FACTORY.create(
|
ClassJsonAdapter.Factory.create(
|
||||||
Types.newParameterizedTypeWithOwner(
|
Types.newParameterizedTypeWithOwner(
|
||||||
ClassJsonAdapterTest.class, Box.class, Integer.class),
|
ClassJsonAdapterTest.class, Box.class, Integer.class),
|
||||||
NO_ANNOTATIONS,
|
NO_ANNOTATIONS,
|
||||||
@@ -541,7 +541,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
private <T> String toJson(Class<T> type, T value) throws IOException {
|
private <T> String toJson(Class<T> type, T value) throws IOException {
|
||||||
@SuppressWarnings("unchecked") // Factory.create returns an adapter that matches its argument.
|
@SuppressWarnings("unchecked") // Factory.create returns an adapter that matches its argument.
|
||||||
JsonAdapter<T> jsonAdapter =
|
JsonAdapter<T> jsonAdapter =
|
||||||
(JsonAdapter<T>) ClassJsonAdapter.FACTORY.create(type, NO_ANNOTATIONS, moshi);
|
(JsonAdapter<T>) ClassJsonAdapter.Factory.create(type, NO_ANNOTATIONS, moshi);
|
||||||
|
|
||||||
// Wrap in an array to avoid top-level object warnings without going completely lenient.
|
// Wrap in an array to avoid top-level object warnings without going completely lenient.
|
||||||
Buffer buffer = new Buffer();
|
Buffer buffer = new Buffer();
|
||||||
@@ -559,7 +559,7 @@ public final class ClassJsonAdapterTest {
|
|||||||
private <T> T fromJson(Class<T> type, String json) throws IOException {
|
private <T> T fromJson(Class<T> type, String json) throws IOException {
|
||||||
@SuppressWarnings("unchecked") // Factory.create returns an adapter that matches its argument.
|
@SuppressWarnings("unchecked") // Factory.create returns an adapter that matches its argument.
|
||||||
JsonAdapter<T> jsonAdapter =
|
JsonAdapter<T> jsonAdapter =
|
||||||
(JsonAdapter<T>) ClassJsonAdapter.FACTORY.create(type, NO_ANNOTATIONS, moshi);
|
(JsonAdapter<T>) ClassJsonAdapter.Factory.create(type, NO_ANNOTATIONS, moshi);
|
||||||
// Wrap in an array to avoid top-level object warnings without going completely lenient.
|
// Wrap in an array to avoid top-level object warnings without going completely lenient.
|
||||||
JsonReader jsonReader = newReader("[" + json + "]");
|
JsonReader jsonReader = newReader("[" + json + "]");
|
||||||
jsonReader.beginArray();
|
jsonReader.beginArray();
|
||||||
|
Reference in New Issue
Block a user