mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Move modules into their own packages.
This sets the Automatic-Module-Name for moshi, moshi-adapters, and moshi-kotlin. It moves moshi-adapters into its own .adapters package and forwards the existing adapter. It moves the moshi-kotlin into its own .kotlin package and forwards the existing adapter. I'm not certain this is necessary or sufficient, but I think it's the right idea for JPMS compatibility.
This commit is contained in:
@@ -33,4 +33,20 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Automatic-Module-Name>com.squareup.moshi.adapters</Automatic-Module-Name>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@@ -19,17 +19,18 @@ import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Formats dates using <a href="https://www.ietf.org/rfc/rfc3339.txt">RFC 3339</a>, which is
|
||||
* formatted like {@code 2015-09-26T18:23:50.250Z}.
|
||||
* @deprecated this class moved to avoid a package name conflict in the Java Platform Module System.
|
||||
* The new class is {@code com.squareup.moshi.adapters.Rfc3339DateJsonAdapter}.
|
||||
*/
|
||||
public final class Rfc3339DateJsonAdapter extends JsonAdapter<Date> {
|
||||
@Override public synchronized Date fromJson(JsonReader reader) throws IOException {
|
||||
String string = reader.nextString();
|
||||
return Iso8601Utils.parse(string);
|
||||
com.squareup.moshi.adapters.Rfc3339DateJsonAdapter delegate
|
||||
= new com.squareup.moshi.adapters.Rfc3339DateJsonAdapter();
|
||||
|
||||
@Override public Date fromJson(JsonReader reader) throws IOException {
|
||||
return delegate.fromJson(reader);
|
||||
}
|
||||
|
||||
@Override public synchronized void toJson(JsonWriter writer, Date value) throws IOException {
|
||||
String string = Iso8601Utils.format(value);
|
||||
writer.value(string);
|
||||
@Override public void toJson(JsonWriter writer, Date value) throws IOException {
|
||||
delegate.toJson(writer, value);
|
||||
}
|
||||
}
|
||||
|
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
package com.squareup.moshi.adapters;
|
||||
|
||||
import com.squareup.moshi.JsonDataException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* http://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.adapters;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Formats dates using <a href="https://www.ietf.org/rfc/rfc3339.txt">RFC 3339</a>, which is
|
||||
* formatted like {@code 2015-09-26T18:23:50.250Z}.
|
||||
*/
|
||||
public final class Rfc3339DateJsonAdapter extends JsonAdapter<Date> {
|
||||
@Override public synchronized Date fromJson(JsonReader reader) throws IOException {
|
||||
String string = reader.nextString();
|
||||
return Iso8601Utils.parse(string);
|
||||
}
|
||||
|
||||
@Override public synchronized void toJson(JsonWriter writer, Date value) throws IOException {
|
||||
String string = Iso8601Utils.format(value);
|
||||
writer.value(string);
|
||||
}
|
||||
}
|
@@ -13,8 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
package com.squareup.moshi.adapters;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
@@ -17,7 +17,7 @@ package com.squareup.moshi.recipes;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.squareup.moshi.Rfc3339DateJsonAdapter;
|
||||
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter;
|
||||
import com.squareup.moshi.recipes.models.Tournament;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
@@ -84,6 +84,17 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Automatic-Module-Name>com.squareup.moshi.kotlin</Automatic-Module-Name>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@@ -15,223 +15,9 @@
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import java.lang.reflect.Modifier
|
||||
import java.lang.reflect.Type
|
||||
import java.util.AbstractMap.SimpleEntry
|
||||
import kotlin.collections.Map.Entry
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
/** Classes annotated with this are eligible for this adapter. */
|
||||
private val KOTLIN_METADATA = Class.forName("kotlin.Metadata") as Class<out Annotation>
|
||||
|
||||
/**
|
||||
* Placeholder value used when a field is absent from the JSON. Note that this code
|
||||
* distinguishes between absent values and present-but-null values.
|
||||
*/
|
||||
private object ABSENT_VALUE
|
||||
|
||||
/**
|
||||
* This class encodes Kotlin classes using their properties. It decodes them by first invoking the
|
||||
* constructor, and then by setting any additional properties that exist, if any.
|
||||
*/
|
||||
internal class KotlinJsonAdapter<T>(
|
||||
val constructor: KFunction<T>,
|
||||
val bindings: List<Binding<T, Any?>?>,
|
||||
val options: JsonReader.Options) : JsonAdapter<T>() {
|
||||
|
||||
override fun fromJson(reader: JsonReader): T {
|
||||
val constructorSize = constructor.parameters.size
|
||||
|
||||
// Read each value into its slot in the array.
|
||||
val values = Array<Any?>(bindings.size) { ABSENT_VALUE }
|
||||
reader.beginObject()
|
||||
while (reader.hasNext()) {
|
||||
val index = reader.selectName(options)
|
||||
val binding = if (index != -1) bindings[index] else null
|
||||
|
||||
if (binding == null) {
|
||||
reader.nextName()
|
||||
reader.skipValue()
|
||||
continue
|
||||
}
|
||||
|
||||
if (values[index] !== ABSENT_VALUE) {
|
||||
throw JsonDataException(
|
||||
"Multiple values for ${constructor.parameters[index].name} at ${reader.path}")
|
||||
}
|
||||
|
||||
values[index] = binding.adapter.fromJson(reader)
|
||||
}
|
||||
reader.endObject()
|
||||
|
||||
// Confirm all parameters are present, optional, or nullable.
|
||||
for (i in 0 until constructorSize) {
|
||||
if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) {
|
||||
if (!constructor.parameters[i].type.isMarkedNullable) {
|
||||
throw JsonDataException(
|
||||
"Required value ${constructor.parameters[i].name} missing at ${reader.path}")
|
||||
}
|
||||
values[i] = null // Replace absent with null.
|
||||
} else if (values[i] == null && !constructor.parameters[i].type.isMarkedNullable) {
|
||||
throw JsonDataException("Non-null value ${constructor.parameters[i].name} " +
|
||||
"was null at ${reader.path}")
|
||||
}
|
||||
}
|
||||
|
||||
// Call the constructor using a Map so that absent optionals get defaults.
|
||||
val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))
|
||||
|
||||
// Set remaining properties.
|
||||
for (i in constructorSize until bindings.size) {
|
||||
val binding = bindings[i]!!
|
||||
val value = values[i]
|
||||
if (value == null && !binding.property.returnType.isMarkedNullable) {
|
||||
throw JsonDataException("Non-null value ${binding.property.name} " +
|
||||
"was null at ${reader.path}")
|
||||
}
|
||||
binding.set(result, value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: T?) {
|
||||
if (value == null) throw NullPointerException("value == null")
|
||||
|
||||
writer.beginObject()
|
||||
for (binding in bindings) {
|
||||
if (binding == null) continue // Skip constructor parameters that aren't properties.
|
||||
|
||||
writer.name(binding.name)
|
||||
binding.adapter.toJson(writer, binding.get(value))
|
||||
}
|
||||
writer.endObject()
|
||||
}
|
||||
|
||||
override fun toString() = "KotlinJsonAdapter(${constructor.returnType})"
|
||||
|
||||
data class Binding<K, P>(
|
||||
val name: String,
|
||||
val adapter: JsonAdapter<P>,
|
||||
val property: KProperty1<K, P>,
|
||||
val parameter: KParameter?) {
|
||||
fun get(value: K) = property.get(value)
|
||||
|
||||
fun set(result: K, value: P) {
|
||||
if (value !== ABSENT_VALUE) {
|
||||
(property as KMutableProperty1<K, P>).set(result, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A simple [Map] that uses parameter indexes instead of sorting or hashing. */
|
||||
class IndexedParameterMap(val parameterKeys: List<KParameter>, val parameterValues: Array<Any?>)
|
||||
: AbstractMap<KParameter, Any?>() {
|
||||
|
||||
override val entries: Set<Entry<KParameter, Any?>>
|
||||
get() {
|
||||
val allPossibleEntries = parameterKeys.mapIndexed { index, value ->
|
||||
SimpleEntry<KParameter, Any?>(value, parameterValues[index])
|
||||
}
|
||||
return allPossibleEntries.filterTo(LinkedHashSet<Entry<KParameter, Any?>>()) {
|
||||
it.value !== ABSENT_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
override fun containsKey(key: KParameter) = parameterValues[key.index] !== ABSENT_VALUE
|
||||
|
||||
override fun get(key: KParameter): Any? {
|
||||
val value = parameterValues[key.index]
|
||||
return if (value !== ABSENT_VALUE) value else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
||||
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi)
|
||||
: JsonAdapter<*>? {
|
||||
if (!annotations.isEmpty()) return null
|
||||
|
||||
val rawType = Types.getRawType(type)
|
||||
if (rawType.isInterface) return null
|
||||
if (rawType.isEnum) return null
|
||||
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
|
||||
if (ClassJsonAdapter.isPlatformType(rawType)) return null
|
||||
|
||||
if (rawType.isLocalClass) {
|
||||
throw IllegalArgumentException("Cannot serialize local class or object expression ${rawType.name}")
|
||||
}
|
||||
val rawTypeKotlin = rawType.kotlin
|
||||
if (rawTypeKotlin.isAbstract) {
|
||||
throw IllegalArgumentException("Cannot serialize abstract class ${rawType.name}")
|
||||
}
|
||||
if (rawTypeKotlin.isInner) {
|
||||
throw IllegalArgumentException("Cannot serialize inner class ${rawType.name}")
|
||||
}
|
||||
if (rawTypeKotlin.objectInstance != null) {
|
||||
throw IllegalArgumentException("Cannot serialize object declaration ${rawType.name}")
|
||||
}
|
||||
|
||||
val constructor = rawTypeKotlin.primaryConstructor ?: return null
|
||||
val parametersByName = constructor.parameters.associateBy { it.name }
|
||||
constructor.isAccessible = true
|
||||
|
||||
val bindingsByName = LinkedHashMap<String, KotlinJsonAdapter.Binding<Any, Any?>>()
|
||||
|
||||
for (property in rawTypeKotlin.memberProperties) {
|
||||
val parameter = parametersByName[property.name]
|
||||
|
||||
if (Modifier.isTransient(property.javaField?.modifiers ?: 0)) {
|
||||
if (parameter != null && !parameter.isOptional) {
|
||||
throw IllegalArgumentException(
|
||||
"No default value for transient constructor $parameter")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (property !is KMutableProperty1 && parameter == null) continue
|
||||
|
||||
property.isAccessible = true
|
||||
var allAnnotations = property.annotations
|
||||
var jsonAnnotation = property.findAnnotation<Json>()
|
||||
|
||||
if (parameter != null) {
|
||||
allAnnotations += parameter.annotations
|
||||
if (jsonAnnotation == null) {
|
||||
jsonAnnotation = parameter.findAnnotation<Json>()
|
||||
}
|
||||
}
|
||||
|
||||
val name = jsonAnnotation?.name ?: property.name
|
||||
val adapter = moshi.adapter<Any>(
|
||||
property.returnType.javaType, Util.jsonAnnotations(allAnnotations.toTypedArray()))
|
||||
|
||||
bindingsByName[property.name] =
|
||||
KotlinJsonAdapter.Binding(name, adapter, property as KProperty1<Any, Any?>, parameter)
|
||||
}
|
||||
|
||||
val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()
|
||||
|
||||
for (parameter in constructor.parameters) {
|
||||
val binding = bindingsByName.remove(parameter.name)
|
||||
if (binding == null && !parameter.isOptional) {
|
||||
throw IllegalArgumentException("No property for required constructor ${parameter}")
|
||||
}
|
||||
bindings += binding
|
||||
}
|
||||
|
||||
bindings += bindingsByName.values
|
||||
|
||||
val options = JsonReader.Options.of(*bindings.map { it?.name ?: "\u0000" }.toTypedArray())
|
||||
return KotlinJsonAdapter(constructor, bindings, options).nullSafe()
|
||||
}
|
||||
}
|
||||
@Deprecated(
|
||||
message = "this moved to avoid a package name conflict in the Java Platform Module System.",
|
||||
replaceWith = ReplaceWith("com.squareup.moshi.kotlin.KotlinJsonAdapterFactory")
|
||||
)
|
||||
class KotlinJsonAdapterFactory
|
||||
: JsonAdapter.Factory by com.squareup.moshi.kotlin.KotlinJsonAdapterFactory()
|
||||
|
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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
|
||||
*
|
||||
* http://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.kotlin
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.squareup.moshi.internal.Util
|
||||
import java.lang.reflect.Modifier
|
||||
import java.lang.reflect.Type
|
||||
import java.util.AbstractMap.SimpleEntry
|
||||
import kotlin.collections.Map.Entry
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
/** Classes annotated with this are eligible for this adapter. */
|
||||
private val KOTLIN_METADATA = Class.forName("kotlin.Metadata") as Class<out Annotation>
|
||||
|
||||
/**
|
||||
* Placeholder value used when a field is absent from the JSON. Note that this code
|
||||
* distinguishes between absent values and present-but-null values.
|
||||
*/
|
||||
private object ABSENT_VALUE
|
||||
|
||||
/**
|
||||
* This class encodes Kotlin classes using their properties. It decodes them by first invoking the
|
||||
* constructor, and then by setting any additional properties that exist, if any.
|
||||
*/
|
||||
internal class KotlinJsonAdapter<T>(
|
||||
val constructor: KFunction<T>,
|
||||
val bindings: List<Binding<T, Any?>?>,
|
||||
val options: JsonReader.Options) : JsonAdapter<T>() {
|
||||
|
||||
override fun fromJson(reader: JsonReader): T {
|
||||
val constructorSize = constructor.parameters.size
|
||||
|
||||
// Read each value into its slot in the array.
|
||||
val values = Array<Any?>(bindings.size) { ABSENT_VALUE }
|
||||
reader.beginObject()
|
||||
while (reader.hasNext()) {
|
||||
val index = reader.selectName(options)
|
||||
val binding = if (index != -1) bindings[index] else null
|
||||
|
||||
if (binding == null) {
|
||||
reader.nextName()
|
||||
reader.skipValue()
|
||||
continue
|
||||
}
|
||||
|
||||
if (values[index] !== ABSENT_VALUE) {
|
||||
throw JsonDataException(
|
||||
"Multiple values for ${constructor.parameters[index].name} at ${reader.path}")
|
||||
}
|
||||
|
||||
values[index] = binding.adapter.fromJson(reader)
|
||||
}
|
||||
reader.endObject()
|
||||
|
||||
// Confirm all parameters are present, optional, or nullable.
|
||||
for (i in 0 until constructorSize) {
|
||||
if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) {
|
||||
if (!constructor.parameters[i].type.isMarkedNullable) {
|
||||
throw JsonDataException(
|
||||
"Required value ${constructor.parameters[i].name} missing at ${reader.path}")
|
||||
}
|
||||
values[i] = null // Replace absent with null.
|
||||
} else if (values[i] == null && !constructor.parameters[i].type.isMarkedNullable) {
|
||||
throw JsonDataException("Non-null value ${constructor.parameters[i].name} " +
|
||||
"was null at ${reader.path}")
|
||||
}
|
||||
}
|
||||
|
||||
// Call the constructor using a Map so that absent optionals get defaults.
|
||||
val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))
|
||||
|
||||
// Set remaining properties.
|
||||
for (i in constructorSize until bindings.size) {
|
||||
val binding = bindings[i]!!
|
||||
val value = values[i]
|
||||
if (value == null && !binding.property.returnType.isMarkedNullable) {
|
||||
throw JsonDataException("Non-null value ${binding.property.name} " +
|
||||
"was null at ${reader.path}")
|
||||
}
|
||||
binding.set(result, value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: T?) {
|
||||
if (value == null) throw NullPointerException("value == null")
|
||||
|
||||
writer.beginObject()
|
||||
for (binding in bindings) {
|
||||
if (binding == null) continue // Skip constructor parameters that aren't properties.
|
||||
|
||||
writer.name(binding.name)
|
||||
binding.adapter.toJson(writer, binding.get(value))
|
||||
}
|
||||
writer.endObject()
|
||||
}
|
||||
|
||||
override fun toString() = "KotlinJsonAdapter(${constructor.returnType})"
|
||||
|
||||
data class Binding<K, P>(
|
||||
val name: String,
|
||||
val adapter: JsonAdapter<P>,
|
||||
val property: KProperty1<K, P>,
|
||||
val parameter: KParameter?) {
|
||||
fun get(value: K) = property.get(value)
|
||||
|
||||
fun set(result: K, value: P) {
|
||||
if (value !== ABSENT_VALUE) {
|
||||
(property as KMutableProperty1<K, P>).set(result, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A simple [Map] that uses parameter indexes instead of sorting or hashing. */
|
||||
class IndexedParameterMap(val parameterKeys: List<KParameter>, val parameterValues: Array<Any?>)
|
||||
: AbstractMap<KParameter, Any?>() {
|
||||
|
||||
override val entries: Set<Entry<KParameter, Any?>>
|
||||
get() {
|
||||
val allPossibleEntries = parameterKeys.mapIndexed { index, value ->
|
||||
SimpleEntry<KParameter, Any?>(value, parameterValues[index])
|
||||
}
|
||||
return allPossibleEntries.filterTo(LinkedHashSet<Entry<KParameter, Any?>>()) {
|
||||
it.value !== ABSENT_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
override fun containsKey(key: KParameter) = parameterValues[key.index] !== ABSENT_VALUE
|
||||
|
||||
override fun get(key: KParameter): Any? {
|
||||
val value = parameterValues[key.index]
|
||||
return if (value !== ABSENT_VALUE) value else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
||||
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi)
|
||||
: JsonAdapter<*>? {
|
||||
if (!annotations.isEmpty()) return null
|
||||
|
||||
val rawType = Types.getRawType(type)
|
||||
if (rawType.isInterface) return null
|
||||
if (rawType.isEnum) return null
|
||||
if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
|
||||
if (Util.isPlatformType(rawType)) return null
|
||||
|
||||
if (rawType.isLocalClass) {
|
||||
throw IllegalArgumentException("Cannot serialize local class or object expression ${rawType.name}")
|
||||
}
|
||||
val rawTypeKotlin = rawType.kotlin
|
||||
if (rawTypeKotlin.isAbstract) {
|
||||
throw IllegalArgumentException("Cannot serialize abstract class ${rawType.name}")
|
||||
}
|
||||
if (rawTypeKotlin.isInner) {
|
||||
throw IllegalArgumentException("Cannot serialize inner class ${rawType.name}")
|
||||
}
|
||||
if (rawTypeKotlin.objectInstance != null) {
|
||||
throw IllegalArgumentException("Cannot serialize object declaration ${rawType.name}")
|
||||
}
|
||||
|
||||
val constructor = rawTypeKotlin.primaryConstructor ?: return null
|
||||
val parametersByName = constructor.parameters.associateBy { it.name }
|
||||
constructor.isAccessible = true
|
||||
|
||||
val bindingsByName = LinkedHashMap<String, KotlinJsonAdapter.Binding<Any, Any?>>()
|
||||
|
||||
for (property in rawTypeKotlin.memberProperties) {
|
||||
val parameter = parametersByName[property.name]
|
||||
|
||||
if (Modifier.isTransient(property.javaField?.modifiers ?: 0)) {
|
||||
if (parameter != null && !parameter.isOptional) {
|
||||
throw IllegalArgumentException(
|
||||
"No default value for transient constructor $parameter")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (property !is KMutableProperty1 && parameter == null) continue
|
||||
|
||||
property.isAccessible = true
|
||||
var allAnnotations = property.annotations
|
||||
var jsonAnnotation = property.findAnnotation<Json>()
|
||||
|
||||
if (parameter != null) {
|
||||
allAnnotations += parameter.annotations
|
||||
if (jsonAnnotation == null) {
|
||||
jsonAnnotation = parameter.findAnnotation<Json>()
|
||||
}
|
||||
}
|
||||
|
||||
val name = jsonAnnotation?.name ?: property.name
|
||||
val adapter = moshi.adapter<Any>(
|
||||
property.returnType.javaType, Util.jsonAnnotations(allAnnotations.toTypedArray()))
|
||||
|
||||
bindingsByName[property.name] =
|
||||
KotlinJsonAdapter.Binding(name, adapter, property as KProperty1<Any, Any?>, parameter)
|
||||
}
|
||||
|
||||
val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()
|
||||
|
||||
for (parameter in constructor.parameters) {
|
||||
val binding = bindingsByName.remove(parameter.name)
|
||||
if (binding == null && !parameter.isOptional) {
|
||||
throw IllegalArgumentException("No property for required constructor ${parameter}")
|
||||
}
|
||||
bindings += binding
|
||||
}
|
||||
|
||||
bindings += bindingsByName.values
|
||||
|
||||
val options = JsonReader.Options.of(*bindings.map { it?.name ?: "\u0000" }.toTypedArray())
|
||||
return KotlinJsonAdapter(constructor, bindings, options).nullSafe()
|
||||
}
|
||||
}
|
@@ -13,8 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
package com.squareup.moshi.kotlin
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.ToJson
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
@@ -315,7 +321,7 @@ class KotlinJsonAdapterTest {
|
||||
} catch (expected: IllegalArgumentException) {
|
||||
assertThat(expected).hasMessage("No default value for transient constructor parameter #0 " +
|
||||
"a of fun <init>(kotlin.Int): " +
|
||||
"com.squareup.moshi.KotlinJsonAdapterTest.RequiredTransientConstructorParameter")
|
||||
"com.squareup.moshi.kotlin.KotlinJsonAdapterTest.RequiredTransientConstructorParameter")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +593,7 @@ class KotlinJsonAdapterTest {
|
||||
fail()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
assertThat(e).hasMessage("No JsonAdapter for interface " +
|
||||
"com.squareup.moshi.KotlinJsonAdapterTest\$Interface annotated []")
|
||||
"com.squareup.moshi.kotlin.KotlinJsonAdapterTest\$Interface annotated []")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,8 +605,8 @@ class KotlinJsonAdapterTest {
|
||||
moshi.adapter(AbstractClass::class.java)
|
||||
fail()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
assertThat(e).hasMessage(
|
||||
"Cannot serialize abstract class com.squareup.moshi.KotlinJsonAdapterTest\$AbstractClass")
|
||||
assertThat(e).hasMessage("Cannot serialize abstract class " +
|
||||
"com.squareup.moshi.kotlin.KotlinJsonAdapterTest\$AbstractClass")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,8 +618,8 @@ class KotlinJsonAdapterTest {
|
||||
moshi.adapter(InnerClass::class.java)
|
||||
fail()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
assertThat(e).hasMessage(
|
||||
"Cannot serialize inner class com.squareup.moshi.KotlinJsonAdapterTest\$InnerClass")
|
||||
assertThat(e).hasMessage("Cannot serialize inner class " +
|
||||
"com.squareup.moshi.kotlin.KotlinJsonAdapterTest\$InnerClass")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +633,7 @@ class KotlinJsonAdapterTest {
|
||||
fail()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
assertThat(e).hasMessage("Cannot serialize local class or object expression " +
|
||||
"com.squareup.moshi.KotlinJsonAdapterTest\$localClassesNotSupported\$LocalClass")
|
||||
"com.squareup.moshi.kotlin.KotlinJsonAdapterTest\$localClassesNotSupported\$LocalClass")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,7 +644,7 @@ class KotlinJsonAdapterTest {
|
||||
fail()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
assertThat(e).hasMessage("Cannot serialize object declaration " +
|
||||
"com.squareup.moshi.KotlinJsonAdapterTest\$ObjectDeclaration")
|
||||
"com.squareup.moshi.kotlin.KotlinJsonAdapterTest\$ObjectDeclaration")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +662,8 @@ class KotlinJsonAdapterTest {
|
||||
fail()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
assertThat(e).hasMessage("Cannot serialize local class or object expression " +
|
||||
"com.squareup.moshi.KotlinJsonAdapterTest\$objectExpressionsNotSupported\$expression$1")
|
||||
"com.squareup.moshi.kotlin.KotlinJsonAdapterTest\$objectExpressionsNotSupported" +
|
||||
"\$expression$1")
|
||||
}
|
||||
}
|
||||
|
@@ -33,4 +33,20 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Automatic-Module-Name>com.squareup.moshi</Automatic-Module-Name>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -26,7 +27,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.squareup.moshi.Util.jsonAnnotations;
|
||||
import static com.squareup.moshi.internal.Util.jsonAnnotations;
|
||||
|
||||
final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
private final List<AdapterMethod> toAdapters;
|
||||
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -48,7 +49,7 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||
if (!(type instanceof Class)) return null;
|
||||
Class<?> rawType = (Class<?>) type;
|
||||
if (rawType.isInterface() || rawType.isEnum()) return null;
|
||||
if (isPlatformType(rawType) && !Types.isAllowedPlatformType(rawType)) {
|
||||
if (Util.isPlatformType(rawType) && !Types.isAllowedPlatformType(rawType)) {
|
||||
throw new IllegalArgumentException("Platform "
|
||||
+ type
|
||||
+ " annotated "
|
||||
@@ -83,7 +84,7 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||
private void createFieldBindings(
|
||||
Moshi moshi, Type type, Map<String, FieldBinding<?>> fieldBindings) {
|
||||
Class<?> rawType = Types.getRawType(type);
|
||||
boolean platformType = isPlatformType(rawType);
|
||||
boolean platformType = Util.isPlatformType(rawType);
|
||||
for (Field field : rawType.getDeclaredFields()) {
|
||||
if (!includeField(platformType, field.getModifiers())) continue;
|
||||
|
||||
@@ -115,19 +116,6 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if {@code rawType} is built in. We don't reflect on private fields of platform
|
||||
* types because they're unspecified and likely to be different on Java vs. Android.
|
||||
*/
|
||||
static boolean isPlatformType(Class<?> rawType) {
|
||||
String name = rawType.getName();
|
||||
return name.startsWith("android.")
|
||||
|| name.startsWith("java.")
|
||||
|| name.startsWith("javax.")
|
||||
|| name.startsWith("kotlin.")
|
||||
|| name.startsWith("scala.");
|
||||
}
|
||||
|
||||
private final ClassFactory<T> classFactory;
|
||||
private final FieldBinding<?>[] fieldsArray;
|
||||
private final JsonReader.Options options;
|
||||
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
|
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
package com.squareup.moshi.internal;
|
||||
|
||||
import com.squareup.moshi.JsonQualifier;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Type;
|
||||
@@ -22,7 +23,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
final class Util {
|
||||
public final class Util {
|
||||
public static final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
|
||||
|
||||
private Util() {
|
||||
@@ -66,4 +67,17 @@ final class Util {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if {@code rawType} is built in. We don't reflect on private fields of platform
|
||||
* types because they're unspecified and likely to be different on Java vs. Android.
|
||||
*/
|
||||
public static boolean isPlatformType(Class<?> rawType) {
|
||||
String name = rawType.getName();
|
||||
return name.startsWith("android.")
|
||||
|| name.startsWith("java.")
|
||||
|| name.startsWith("javax.")
|
||||
|| name.startsWith("kotlin.")
|
||||
|| name.startsWith("scala.");
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Retention;
|
||||
|
@@ -25,7 +25,7 @@ import okio.Buffer;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.squareup.moshi.TestUtil.newReader;
|
||||
import static com.squareup.moshi.Util.NO_ANNOTATIONS;
|
||||
import static com.squareup.moshi.internal.Util.NO_ANNOTATIONS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
@@ -26,7 +26,7 @@ import org.assertj.core.data.MapEntry;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.squareup.moshi.TestUtil.newReader;
|
||||
import static com.squareup.moshi.Util.NO_ANNOTATIONS;
|
||||
import static com.squareup.moshi.internal.Util.NO_ANNOTATIONS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
@@ -16,6 +16,7 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import android.util.Pair;
|
||||
import com.squareup.moshi.internal.Util;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
|
Reference in New Issue
Block a user