Support multiple transient properties in KotlinJsonAdapter.
This commit is contained in:
parent
ded3bccc60
commit
c03c251a16
3 changed files with 61 additions and 25 deletions
|
@ -17,7 +17,6 @@ package com.squareup.moshi.kotlin.reflect
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
|
@ -55,34 +54,35 @@ private val ABSENT_VALUE = Any()
|
|||
* 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>() {
|
||||
val constructor: KFunction<T>,
|
||||
val allBindings: List<Binding<T, Any?>?>,
|
||||
val nonTransientBindings: 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 }
|
||||
val values = Array<Any?>(allBindings.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) {
|
||||
if (index == -1) {
|
||||
reader.skipName()
|
||||
reader.skipValue()
|
||||
continue
|
||||
}
|
||||
val binding = nonTransientBindings[index]
|
||||
|
||||
if (values[index] !== ABSENT_VALUE) {
|
||||
val propertyIndex = binding.propertyIndex
|
||||
if (values[propertyIndex] !== ABSENT_VALUE) {
|
||||
throw JsonDataException(
|
||||
"Multiple values for '${constructor.parameters[index].name}' at ${reader.path}")
|
||||
"Multiple values for '${binding.property.name}' at ${reader.path}")
|
||||
}
|
||||
|
||||
values[index] = binding.adapter.fromJson(reader)
|
||||
values[propertyIndex] = binding.adapter.fromJson(reader)
|
||||
|
||||
if (values[index] == null && !binding.property.returnType.isMarkedNullable) {
|
||||
if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) {
|
||||
throw JsonDataException(
|
||||
"Non-null value '${binding.property.name}' was null at ${reader.path}")
|
||||
}
|
||||
|
@ -104,8 +104,8 @@ internal class KotlinJsonAdapter<T>(
|
|||
val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))
|
||||
|
||||
// Set remaining properties.
|
||||
for (i in constructorSize until bindings.size) {
|
||||
val binding = bindings[i]!!
|
||||
for (i in constructorSize until allBindings.size) {
|
||||
val binding = allBindings[i]!!
|
||||
val value = values[i]
|
||||
binding.set(result, value)
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ internal class KotlinJsonAdapter<T>(
|
|||
if (value == null) throw NullPointerException("value == null")
|
||||
|
||||
writer.beginObject()
|
||||
for (binding in bindings) {
|
||||
for (binding in allBindings) {
|
||||
if (binding == null) continue // Skip constructor parameters that aren't properties.
|
||||
|
||||
writer.name(binding.name)
|
||||
|
@ -129,10 +129,11 @@ internal class KotlinJsonAdapter<T>(
|
|||
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?) {
|
||||
val name: String,
|
||||
val adapter: JsonAdapter<P>,
|
||||
val property: KProperty1<K, P>,
|
||||
val parameter: KParameter?,
|
||||
val propertyIndex: Int) {
|
||||
fun get(value: K) = property.get(value)
|
||||
|
||||
fun set(result: K, value: P) {
|
||||
|
@ -232,7 +233,7 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||
if (parameter != null) {
|
||||
allAnnotations += parameter.annotations
|
||||
if (jsonAnnotation == null) {
|
||||
jsonAnnotation = parameter.findAnnotation<Json>()
|
||||
jsonAnnotation = parameter.findAnnotation()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +243,7 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||
resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()), property.name)
|
||||
|
||||
bindingsByName[property.name] = KotlinJsonAdapter.Binding(name, adapter,
|
||||
property as KProperty1<Any, Any?>, parameter)
|
||||
property as KProperty1<Any, Any?>, parameter, parameter?.index ?: -1)
|
||||
}
|
||||
|
||||
val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()
|
||||
|
@ -250,14 +251,18 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||
for (parameter in constructor.parameters) {
|
||||
val binding = bindingsByName.remove(parameter.name)
|
||||
if (binding == null && !parameter.isOptional) {
|
||||
throw IllegalArgumentException("No property for required constructor ${parameter}")
|
||||
throw IllegalArgumentException("No property for required constructor $parameter")
|
||||
}
|
||||
bindings += binding
|
||||
}
|
||||
|
||||
bindings += bindingsByName.values
|
||||
var index = bindings.size
|
||||
for (bindingByName in bindingsByName) {
|
||||
bindings += bindingByName.value.copy(propertyIndex = index++)
|
||||
}
|
||||
|
||||
val options = JsonReader.Options.of(*bindings.map { it?.name ?: "\u0000" }.toTypedArray())
|
||||
return KotlinJsonAdapter(constructor, bindings, options).nullSafe()
|
||||
val nonTransientBindings = bindings.filterNotNull()
|
||||
val options = JsonReader.Options.of(*nonTransientBindings.map { it.name }.toTypedArray())
|
||||
return KotlinJsonAdapter(constructor, bindings, nonTransientBindings, options).nullSafe()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -557,6 +557,22 @@ class GeneratedAdaptersTest {
|
|||
@JsonClass(generateAdapter = true)
|
||||
class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1)
|
||||
|
||||
@Test fun multipleTransientConstructorParameters() {
|
||||
val moshi = Moshi.Builder().build()
|
||||
val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java)
|
||||
|
||||
val encoded = MultipleTransientConstructorParameters(3, 5, 7)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
assertThat(decoded.c).isEqualTo(-1)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class MultipleTransientConstructorParameters(@Transient var a: Int = -1, var b: Int = -1, @Transient var c: Int = -1)
|
||||
|
||||
@Test fun transientProperty() {
|
||||
val moshi = Moshi.Builder().build()
|
||||
val jsonAdapter = moshi.adapter(TransientProperty::class.java)
|
||||
|
|
|
@ -349,6 +349,21 @@ class KotlinJsonAdapterTest {
|
|||
|
||||
class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1)
|
||||
|
||||
@Test fun multipleTransientConstructorParameters() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java)
|
||||
|
||||
val encoded = MultipleTransientConstructorParameters(3, 5, 7)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
assertThat(decoded.c).isEqualTo(-1)
|
||||
}
|
||||
|
||||
class MultipleTransientConstructorParameters(@Transient var a: Int = -1, var b: Int = -1, @Transient var c: Int = -1)
|
||||
|
||||
@Test fun requiredTransientConstructorParameterFails() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue