mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Convert StandardJsonAdapters to Kotlin (#1494)
* Rename .java to .kt * Convert StandardJsonAdapters to Kotlin * Inline factory to object * Idiomatic cleanups + japicmp * Fixes * Another idiomatatic bit * Use knownNotNull() where applicable
This commit is contained in:
@@ -28,7 +28,8 @@ val japicmp = tasks.register<JapicmpTask>("japicmp") {
|
||||
classExcludes = listOf(
|
||||
"com.squareup.moshi.internal.NonNullJsonAdapter", // Internal.
|
||||
"com.squareup.moshi.internal.NullSafeJsonAdapter", // Internal.
|
||||
"com.squareup.moshi.internal.Util" // Internal.
|
||||
"com.squareup.moshi.internal.Util", // Internal.
|
||||
"com.squareup.moshi.StandardJsonAdapters" // Package-private
|
||||
)
|
||||
methodExcludes = listOf(
|
||||
"com.squareup.moshi.JsonAdapter#indent(java.lang.String)" // Was unintentionally open before
|
||||
|
@@ -329,7 +329,7 @@ public class Moshi internal constructor(builder: Builder) {
|
||||
internal companion object {
|
||||
@JvmField
|
||||
val BUILT_IN_FACTORIES: List<JsonAdapter.Factory> = buildList(6) {
|
||||
add(StandardJsonAdapters.FACTORY)
|
||||
add(StandardJsonAdapters)
|
||||
add(CollectionJsonAdapter.Factory)
|
||||
add(MapJsonAdapter.FACTORY)
|
||||
add(ArrayJsonAdapter.FACTORY)
|
||||
|
@@ -1,396 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.generatedAdapter;
|
||||
|
||||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
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;
|
||||
|
||||
final class StandardJsonAdapters {
|
||||
private StandardJsonAdapters() {}
|
||||
|
||||
public static final JsonAdapter.Factory FACTORY =
|
||||
new JsonAdapter.Factory() {
|
||||
@Override
|
||||
public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
if (!annotations.isEmpty()) return null;
|
||||
if (type == boolean.class) return BOOLEAN_JSON_ADAPTER;
|
||||
if (type == byte.class) return BYTE_JSON_ADAPTER;
|
||||
if (type == char.class) return CHARACTER_JSON_ADAPTER;
|
||||
if (type == double.class) return DOUBLE_JSON_ADAPTER;
|
||||
if (type == float.class) return FLOAT_JSON_ADAPTER;
|
||||
if (type == int.class) return INTEGER_JSON_ADAPTER;
|
||||
if (type == long.class) return LONG_JSON_ADAPTER;
|
||||
if (type == short.class) return SHORT_JSON_ADAPTER;
|
||||
if (type == Boolean.class) return BOOLEAN_JSON_ADAPTER.nullSafe();
|
||||
if (type == Byte.class) return BYTE_JSON_ADAPTER.nullSafe();
|
||||
if (type == Character.class) return CHARACTER_JSON_ADAPTER.nullSafe();
|
||||
if (type == Double.class) return DOUBLE_JSON_ADAPTER.nullSafe();
|
||||
if (type == Float.class) return FLOAT_JSON_ADAPTER.nullSafe();
|
||||
if (type == Integer.class) return INTEGER_JSON_ADAPTER.nullSafe();
|
||||
if (type == Long.class) return LONG_JSON_ADAPTER.nullSafe();
|
||||
if (type == Short.class) return SHORT_JSON_ADAPTER.nullSafe();
|
||||
if (type == String.class) return STRING_JSON_ADAPTER.nullSafe();
|
||||
if (type == Object.class) return new ObjectJsonAdapter(moshi).nullSafe();
|
||||
|
||||
Class<?> rawType = Types.getRawType(type);
|
||||
|
||||
@Nullable JsonAdapter<?> generatedAdapter = generatedAdapter(moshi, type, rawType);
|
||||
if (generatedAdapter != null) {
|
||||
return generatedAdapter;
|
||||
}
|
||||
|
||||
if (rawType.isEnum()) {
|
||||
//noinspection unchecked
|
||||
return new EnumJsonAdapter<>((Class<? extends Enum>) rawType).nullSafe();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private static final String ERROR_FORMAT = "Expected %s but was %s at path %s";
|
||||
|
||||
static int rangeCheckNextInt(JsonReader reader, String typeMessage, int min, int max)
|
||||
throws IOException {
|
||||
int value = reader.nextInt();
|
||||
if (value < min || value > max) {
|
||||
throw new JsonDataException(
|
||||
String.format(ERROR_FORMAT, typeMessage, value, reader.getPath()));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static final JsonAdapter<Boolean> BOOLEAN_JSON_ADAPTER =
|
||||
new JsonAdapter<Boolean>() {
|
||||
@Override
|
||||
public Boolean fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Boolean value) throws IOException {
|
||||
writer.value(value.booleanValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Boolean)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<Byte> BYTE_JSON_ADAPTER =
|
||||
new JsonAdapter<Byte>() {
|
||||
@Override
|
||||
public Byte fromJson(JsonReader reader) throws IOException {
|
||||
return (byte) rangeCheckNextInt(reader, "a byte", Byte.MIN_VALUE, 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Byte value) throws IOException {
|
||||
writer.value(value.intValue() & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Byte)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<Character> CHARACTER_JSON_ADAPTER =
|
||||
new JsonAdapter<Character>() {
|
||||
@Override
|
||||
public Character fromJson(JsonReader reader) throws IOException {
|
||||
String value = reader.nextString();
|
||||
if (value.length() > 1) {
|
||||
throw new JsonDataException(
|
||||
String.format(ERROR_FORMAT, "a char", '"' + value + '"', reader.getPath()));
|
||||
}
|
||||
return value.charAt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Character value) throws IOException {
|
||||
writer.value(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Character)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<Double> DOUBLE_JSON_ADAPTER =
|
||||
new JsonAdapter<Double>() {
|
||||
@Override
|
||||
public Double fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Double value) throws IOException {
|
||||
writer.value(value.doubleValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Double)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<Float> FLOAT_JSON_ADAPTER =
|
||||
new JsonAdapter<Float>() {
|
||||
@Override
|
||||
public Float fromJson(JsonReader reader) throws IOException {
|
||||
float value = (float) reader.nextDouble();
|
||||
// Double check for infinity after float conversion; many doubles > Float.MAX
|
||||
if (!reader.isLenient() && Float.isInfinite(value)) {
|
||||
throw new JsonDataException(
|
||||
"JSON forbids NaN and infinities: " + value + " at path " + reader.getPath());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Float value) throws IOException {
|
||||
// Manual null pointer check for the float.class adapter.
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
// Use the Number overload so we write out float precision instead of double precision.
|
||||
writer.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Float)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<Integer> INTEGER_JSON_ADAPTER =
|
||||
new JsonAdapter<Integer>() {
|
||||
@Override
|
||||
public Integer fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Integer value) throws IOException {
|
||||
writer.value(value.intValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Integer)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<Long> LONG_JSON_ADAPTER =
|
||||
new JsonAdapter<Long>() {
|
||||
@Override
|
||||
public Long fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Long value) throws IOException {
|
||||
writer.value(value.longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Long)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<Short> SHORT_JSON_ADAPTER =
|
||||
new JsonAdapter<Short>() {
|
||||
@Override
|
||||
public Short fromJson(JsonReader reader) throws IOException {
|
||||
return (short) rangeCheckNextInt(reader, "a short", Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Short value) throws IOException {
|
||||
writer.value(value.intValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Short)";
|
||||
}
|
||||
};
|
||||
|
||||
static final JsonAdapter<String> STRING_JSON_ADAPTER =
|
||||
new JsonAdapter<String>() {
|
||||
@Override
|
||||
public String fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, String value) throws IOException {
|
||||
writer.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(String)";
|
||||
}
|
||||
};
|
||||
|
||||
static final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
|
||||
private final Class<T> enumType;
|
||||
private final String[] nameStrings;
|
||||
private final T[] constants;
|
||||
private final JsonReader.Options options;
|
||||
|
||||
EnumJsonAdapter(Class<T> enumType) {
|
||||
this.enumType = enumType;
|
||||
try {
|
||||
constants = enumType.getEnumConstants();
|
||||
nameStrings = new String[constants.length];
|
||||
for (int i = 0; i < constants.length; i++) {
|
||||
T constant = constants[i];
|
||||
String constantName = constant.name();
|
||||
nameStrings[i] = Util.jsonName(enumType.getField(constantName), constantName);
|
||||
}
|
||||
options = JsonReader.Options.of(nameStrings);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new AssertionError("Missing field in " + enumType.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromJson(JsonReader reader) throws IOException {
|
||||
int index = reader.selectString(options);
|
||||
if (index != -1) return constants[index];
|
||||
|
||||
// We can consume the string safely, we are terminating anyway.
|
||||
String path = reader.getPath();
|
||||
String name = reader.nextString();
|
||||
throw new JsonDataException(
|
||||
"Expected one of "
|
||||
+ Arrays.asList(nameStrings)
|
||||
+ " but was "
|
||||
+ name
|
||||
+ " at path "
|
||||
+ path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, T value) throws IOException {
|
||||
writer.value(nameStrings[value.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(" + enumType.getName() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This adapter is used when the declared type is {@code java.lang.Object}. Typically the runtime
|
||||
* type is something else, and when encoding JSON this delegates to the runtime type's adapter.
|
||||
* For decoding (where there is no runtime type to inspect), this uses maps and lists.
|
||||
*
|
||||
* <p>This adapter needs a Moshi instance to look up the appropriate adapter for runtime types as
|
||||
* they are encountered.
|
||||
*/
|
||||
static final class ObjectJsonAdapter extends JsonAdapter<Object> {
|
||||
private final Moshi moshi;
|
||||
private final JsonAdapter<List> listJsonAdapter;
|
||||
private final JsonAdapter<Map> mapAdapter;
|
||||
private final JsonAdapter<String> stringAdapter;
|
||||
private final JsonAdapter<Double> doubleAdapter;
|
||||
private final JsonAdapter<Boolean> booleanAdapter;
|
||||
|
||||
ObjectJsonAdapter(Moshi moshi) {
|
||||
this.moshi = moshi;
|
||||
this.listJsonAdapter = moshi.adapter(List.class);
|
||||
this.mapAdapter = moshi.adapter(Map.class);
|
||||
this.stringAdapter = moshi.adapter(String.class);
|
||||
this.doubleAdapter = moshi.adapter(Double.class);
|
||||
this.booleanAdapter = moshi.adapter(Boolean.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromJson(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case BEGIN_ARRAY:
|
||||
return listJsonAdapter.fromJson(reader);
|
||||
|
||||
case BEGIN_OBJECT:
|
||||
return mapAdapter.fromJson(reader);
|
||||
|
||||
case STRING:
|
||||
return stringAdapter.fromJson(reader);
|
||||
|
||||
case NUMBER:
|
||||
return doubleAdapter.fromJson(reader);
|
||||
|
||||
case BOOLEAN:
|
||||
return booleanAdapter.fromJson(reader);
|
||||
|
||||
case NULL:
|
||||
return reader.nextNull();
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Expected a value but was " + reader.peek() + " at path " + reader.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Object value) throws IOException {
|
||||
Class<?> valueClass = value.getClass();
|
||||
if (valueClass == Object.class) {
|
||||
// Don't recurse infinitely when the runtime type is also Object.class.
|
||||
writer.beginObject();
|
||||
writer.endObject();
|
||||
} else {
|
||||
moshi.adapter(toJsonType(valueClass), Util.NO_ANNOTATIONS).toJson(writer, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type to look up a type adapter for when writing {@code value} to JSON. Without
|
||||
* this, attempts to emit standard types like `LinkedHashMap` would fail because Moshi doesn't
|
||||
* provide built-in adapters for implementation types. It knows how to <strong>write</strong>
|
||||
* those types, but lacks a mechanism to read them because it doesn't know how to find the
|
||||
* appropriate constructor.
|
||||
*/
|
||||
private Class<?> toJsonType(Class<?> valueClass) {
|
||||
if (Map.class.isAssignableFrom(valueClass)) return Map.class;
|
||||
if (Collection.class.isAssignableFrom(valueClass)) return Collection.class;
|
||||
return valueClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonAdapter(Object)";
|
||||
}
|
||||
}
|
||||
}
|
275
moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.kt
Normal file
275
moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.kt
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.NO_ANNOTATIONS
|
||||
import com.squareup.moshi.internal.generatedAdapter
|
||||
import com.squareup.moshi.internal.jsonName
|
||||
import com.squareup.moshi.internal.knownNotNull
|
||||
import java.lang.reflect.Type
|
||||
|
||||
internal object StandardJsonAdapters : JsonAdapter.Factory {
|
||||
|
||||
private const val ERROR_FORMAT = "Expected %s but was %s at path %s"
|
||||
|
||||
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||
if (annotations.isNotEmpty()) return null
|
||||
if (type === Boolean::class.javaPrimitiveType) return BOOLEAN_JSON_ADAPTER
|
||||
if (type === Byte::class.javaPrimitiveType) return BYTE_JSON_ADAPTER
|
||||
if (type === Char::class.javaPrimitiveType) return CHARACTER_JSON_ADAPTER
|
||||
if (type === Double::class.javaPrimitiveType) return DOUBLE_JSON_ADAPTER
|
||||
if (type === Float::class.javaPrimitiveType) return FLOAT_JSON_ADAPTER
|
||||
if (type === Int::class.javaPrimitiveType) return INTEGER_JSON_ADAPTER
|
||||
if (type === Long::class.javaPrimitiveType) return LONG_JSON_ADAPTER
|
||||
if (type === Short::class.javaPrimitiveType) return SHORT_JSON_ADAPTER
|
||||
if (type === Boolean::class.javaObjectType) return BOOLEAN_JSON_ADAPTER.nullSafe()
|
||||
if (type === Byte::class.javaObjectType) return BYTE_JSON_ADAPTER.nullSafe()
|
||||
if (type === Char::class.javaObjectType) return CHARACTER_JSON_ADAPTER.nullSafe()
|
||||
if (type === Double::class.javaObjectType) return DOUBLE_JSON_ADAPTER.nullSafe()
|
||||
if (type === Float::class.javaObjectType) return FLOAT_JSON_ADAPTER.nullSafe()
|
||||
if (type === Int::class.javaObjectType) return INTEGER_JSON_ADAPTER.nullSafe()
|
||||
if (type === Long::class.javaObjectType) return LONG_JSON_ADAPTER.nullSafe()
|
||||
if (type === Short::class.javaObjectType) return SHORT_JSON_ADAPTER.nullSafe()
|
||||
if (type === String::class.java) return STRING_JSON_ADAPTER.nullSafe()
|
||||
if (type === Any::class.java) return ObjectJsonAdapter(moshi).nullSafe()
|
||||
val rawType = type.rawType
|
||||
val generatedAdapter = moshi.generatedAdapter(type, rawType)
|
||||
if (generatedAdapter != null) {
|
||||
return generatedAdapter
|
||||
}
|
||||
return if (rawType.isEnum) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
EnumJsonAdapter(rawType as Class<out Enum<*>>).nullSafe()
|
||||
} else null
|
||||
}
|
||||
|
||||
fun rangeCheckNextInt(reader: JsonReader, typeMessage: String?, min: Int, max: Int): Int {
|
||||
val value = reader.nextInt()
|
||||
if (value !in min..max) {
|
||||
throw JsonDataException(ERROR_FORMAT.format(typeMessage, value, reader.path))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
val BOOLEAN_JSON_ADAPTER: JsonAdapter<Boolean> = object : JsonAdapter<Boolean>() {
|
||||
override fun fromJson(reader: JsonReader) = reader.nextBoolean()
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Boolean?) {
|
||||
writer.value(knownNotNull(value))
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Boolean)"
|
||||
}
|
||||
|
||||
private val BYTE_JSON_ADAPTER: JsonAdapter<Byte> = object : JsonAdapter<Byte>() {
|
||||
override fun fromJson(reader: JsonReader): Byte {
|
||||
return rangeCheckNextInt(reader, "a byte", Byte.MIN_VALUE.toInt(), 0xff).toByte()
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Byte?) {
|
||||
writer.value((knownNotNull(value).toInt() and 0xff).toLong())
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Byte)"
|
||||
}
|
||||
|
||||
private val CHARACTER_JSON_ADAPTER: JsonAdapter<Char> = object : JsonAdapter<Char>() {
|
||||
override fun fromJson(reader: JsonReader): Char {
|
||||
val value = reader.nextString()
|
||||
if (value.length > 1) {
|
||||
throw JsonDataException(ERROR_FORMAT.format("a char", "\"$value\"", reader.path))
|
||||
}
|
||||
return value[0]
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Char?) {
|
||||
writer.value(knownNotNull(value).toString())
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Character)"
|
||||
}
|
||||
|
||||
private val DOUBLE_JSON_ADAPTER: JsonAdapter<Double> = object : JsonAdapter<Double>() {
|
||||
override fun fromJson(reader: JsonReader): Double {
|
||||
return reader.nextDouble()
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Double?) {
|
||||
writer.value(knownNotNull(value).toDouble())
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Double)"
|
||||
}
|
||||
|
||||
private val FLOAT_JSON_ADAPTER: JsonAdapter<Float> = object : JsonAdapter<Float>() {
|
||||
override fun fromJson(reader: JsonReader): Float {
|
||||
val value = reader.nextDouble().toFloat()
|
||||
// Double check for infinity after float conversion; many doubles > Float.MAX
|
||||
if (!reader.isLenient && value.isInfinite()) {
|
||||
throw JsonDataException(
|
||||
"JSON forbids NaN and infinities: $value at path ${reader.path}"
|
||||
)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Float?) {
|
||||
// Manual null pointer check for the float.class adapter.
|
||||
if (value == null) {
|
||||
throw NullPointerException()
|
||||
}
|
||||
// Use the Number overload so we write out float precision instead of double precision.
|
||||
writer.value(value)
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Float)"
|
||||
}
|
||||
|
||||
private val INTEGER_JSON_ADAPTER: JsonAdapter<Int> = object : JsonAdapter<Int>() {
|
||||
override fun fromJson(reader: JsonReader): Int {
|
||||
return reader.nextInt()
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Int?) {
|
||||
writer.value(knownNotNull(value).toInt().toLong())
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Integer)"
|
||||
}
|
||||
|
||||
private val LONG_JSON_ADAPTER: JsonAdapter<Long> = object : JsonAdapter<Long>() {
|
||||
override fun fromJson(reader: JsonReader): Long {
|
||||
return reader.nextLong()
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Long?) {
|
||||
writer.value(knownNotNull(value).toLong())
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Long)"
|
||||
}
|
||||
|
||||
private val SHORT_JSON_ADAPTER: JsonAdapter<Short> = object : JsonAdapter<Short>() {
|
||||
override fun fromJson(reader: JsonReader): Short {
|
||||
return rangeCheckNextInt(reader, "a short", Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort()
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Short?) {
|
||||
writer.value(knownNotNull(value).toInt().toLong())
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Short)"
|
||||
}
|
||||
|
||||
private val STRING_JSON_ADAPTER: JsonAdapter<String> = object : JsonAdapter<String>() {
|
||||
override fun fromJson(reader: JsonReader): String? {
|
||||
return reader.nextString()
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: String?) {
|
||||
writer.value(value)
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(String)"
|
||||
}
|
||||
|
||||
internal class EnumJsonAdapter<T : Enum<T>>(private val enumType: Class<T>) : JsonAdapter<T>() {
|
||||
private val constants: Array<T> = enumType.enumConstants
|
||||
private val nameStrings: Array<String> = Array(constants.size) { i ->
|
||||
val constant = constants[i]
|
||||
val constantName = constant.name
|
||||
try {
|
||||
enumType.getField(constantName).jsonName(constantName)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
throw AssertionError("Missing field in ${enumType.name}", e)
|
||||
}
|
||||
}
|
||||
private var options: JsonReader.Options = JsonReader.Options.of(*nameStrings)
|
||||
|
||||
override fun fromJson(reader: JsonReader): T {
|
||||
val index = reader.selectString(options)
|
||||
if (index != -1) return constants[index]
|
||||
|
||||
// We can consume the string safely, we are terminating anyway.
|
||||
val path = reader.path
|
||||
val name = reader.nextString()
|
||||
throw JsonDataException(
|
||||
"Expected one of ${nameStrings.toList()} but was $name at path $path"
|
||||
)
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: T?) {
|
||||
writer.value(nameStrings[knownNotNull(value).ordinal])
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(${enumType.name})"
|
||||
}
|
||||
|
||||
/**
|
||||
* This adapter is used when the declared type is [Any]. Typically the runtime
|
||||
* type is something else, and when encoding JSON this delegates to the runtime type's adapter.
|
||||
* For decoding (where there is no runtime type to inspect), this uses maps and lists.
|
||||
*
|
||||
* This adapter needs a Moshi instance to look up the appropriate adapter for runtime types as
|
||||
* they are encountered.
|
||||
*/
|
||||
internal class ObjectJsonAdapter(private val moshi: Moshi) : JsonAdapter<Any>() {
|
||||
private val listJsonAdapter: JsonAdapter<List<*>> = moshi.adapter(List::class.java)
|
||||
private val mapAdapter: JsonAdapter<Map<*, *>> = moshi.adapter(Map::class.java)
|
||||
private val stringAdapter: JsonAdapter<String> = moshi.adapter(String::class.java)
|
||||
private val doubleAdapter: JsonAdapter<Double> = moshi.adapter(Double::class.java)
|
||||
private val booleanAdapter: JsonAdapter<Boolean> = moshi.adapter(Boolean::class.java)
|
||||
|
||||
override fun fromJson(reader: JsonReader): Any? {
|
||||
return when (reader.peek()) {
|
||||
JsonReader.Token.BEGIN_ARRAY -> listJsonAdapter.fromJson(reader)
|
||||
JsonReader.Token.BEGIN_OBJECT -> mapAdapter.fromJson(reader)
|
||||
JsonReader.Token.STRING -> stringAdapter.fromJson(reader)
|
||||
JsonReader.Token.NUMBER -> doubleAdapter.fromJson(reader)
|
||||
JsonReader.Token.BOOLEAN -> booleanAdapter.fromJson(reader)
|
||||
JsonReader.Token.NULL -> reader.nextNull()
|
||||
else -> throw IllegalStateException(
|
||||
"Expected a value but was ${reader.peek()} at path ${reader.path}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Any?) {
|
||||
val valueClass: Class<*> = knownNotNull(value).javaClass
|
||||
if (valueClass == Any::class.java) {
|
||||
// Don't recurse infinitely when the runtime type is also Object.class.
|
||||
writer.beginObject()
|
||||
writer.endObject()
|
||||
} else {
|
||||
moshi.adapter<Any>(toJsonType(valueClass), NO_ANNOTATIONS).toJson(writer, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type to look up a type adapter for when writing `value` to JSON. Without
|
||||
* this, attempts to emit standard types like `LinkedHashMap` would fail because Moshi doesn't
|
||||
* provide built-in adapters for implementation types. It knows how to **write**
|
||||
* those types, but lacks a mechanism to read them because it doesn't know how to find the
|
||||
* appropriate constructor.
|
||||
*/
|
||||
private fun toJsonType(valueClass: Class<*>): Class<*> {
|
||||
if (Map::class.java.isAssignableFrom(valueClass)) return Map::class.java
|
||||
return if (Collection::class.java.isAssignableFrom(valueClass)) Collection::class.java else valueClass
|
||||
}
|
||||
|
||||
override fun toString() = "JsonAdapter(Object)"
|
||||
}
|
||||
}
|
@@ -1160,7 +1160,10 @@ public final class MoshiTest {
|
||||
public void qualifierWithElementsMayNotBeDirectlyRegistered() throws IOException {
|
||||
try {
|
||||
new Moshi.Builder()
|
||||
.add(Boolean.class, Localized.class, StandardJsonAdapters.BOOLEAN_JSON_ADAPTER);
|
||||
.add(
|
||||
Boolean.class,
|
||||
Localized.class,
|
||||
StandardJsonAdapters.INSTANCE.getBOOLEAN_JSON_ADAPTER());
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertThat(expected)
|
||||
|
Reference in New Issue
Block a user