Support multiple transient properties in KotlinJsonAdapter.

This commit is contained in:
Eric Cochran
2019-01-15 10:36:43 -08:00
committed by Jesse Wilson
parent 5912dfaaf6
commit efb0fc0923
3 changed files with 62 additions and 23 deletions

View File

@@ -54,35 +54,36 @@ private val ABSENT_VALUE = Any()
* constructor, and then by setting any additional properties that exist, if any. * constructor, and then by setting any additional properties that exist, if any.
*/ */
internal class KotlinJsonAdapter<T>( internal class KotlinJsonAdapter<T>(
private val constructor: KFunction<T>, val constructor: KFunction<T>,
private val bindings: List<Binding<T, Any?>?>, val allBindings: List<Binding<T, Any?>?>,
private val options: JsonReader.Options val nonTransientBindings: List<Binding<T, Any?>>,
val options: JsonReader.Options
) : JsonAdapter<T>() { ) : JsonAdapter<T>() {
override fun fromJson(reader: JsonReader): T { override fun fromJson(reader: JsonReader): T {
val constructorSize = constructor.parameters.size val constructorSize = constructor.parameters.size
// Read each value into its slot in the array. // 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() reader.beginObject()
while (reader.hasNext()) { while (reader.hasNext()) {
val index = reader.selectName(options) val index = reader.selectName(options)
val binding = if (index != -1) bindings[index] else null if (index == -1) {
if (binding == null) {
reader.skipName() reader.skipName()
reader.skipValue() reader.skipValue()
continue continue
} }
val binding = nonTransientBindings[index]
if (values[index] !== ABSENT_VALUE) { val propertyIndex = binding.propertyIndex
if (values[propertyIndex] !== ABSENT_VALUE) {
throw JsonDataException( throw JsonDataException(
"Multiple values for '${binding.property.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 Util.unexpectedNull( throw Util.unexpectedNull(
binding.property.name, binding.property.name,
binding.jsonName, binding.jsonName,
@@ -98,7 +99,7 @@ internal class KotlinJsonAdapter<T>(
if (!constructor.parameters[i].type.isMarkedNullable) { if (!constructor.parameters[i].type.isMarkedNullable) {
throw Util.missingProperty( throw Util.missingProperty(
constructor.parameters[i].name, constructor.parameters[i].name,
bindings[i]?.jsonName, allBindings[i]?.jsonName,
reader reader
) )
} }
@@ -110,8 +111,8 @@ internal class KotlinJsonAdapter<T>(
val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values)) val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))
// Set remaining properties. // Set remaining properties.
for (i in constructorSize until bindings.size) { for (i in constructorSize until allBindings.size) {
val binding = bindings[i]!! val binding = allBindings[i]!!
val value = values[i] val value = values[i]
binding.set(result, value) binding.set(result, value)
} }
@@ -123,7 +124,7 @@ internal class KotlinJsonAdapter<T>(
if (value == null) throw NullPointerException("value == null") if (value == null) throw NullPointerException("value == null")
writer.beginObject() writer.beginObject()
for (binding in bindings) { for (binding in allBindings) {
if (binding == null) continue // Skip constructor parameters that aren't properties. if (binding == null) continue // Skip constructor parameters that aren't properties.
writer.name(binding.name) writer.name(binding.name)
@@ -139,7 +140,9 @@ internal class KotlinJsonAdapter<T>(
val jsonName: String?, val jsonName: String?,
val adapter: JsonAdapter<P>, val adapter: JsonAdapter<P>,
val property: KProperty1<K, P>, val property: KProperty1<K, P>,
val parameter: KParameter?) { val parameter: KParameter?,
val propertyIndex: Int
) {
fun get(value: K) = property.get(value) fun get(value: K) = property.get(value)
fun set(result: K, value: P) { fun set(result: K, value: P) {
@@ -257,7 +260,8 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
jsonAnnotation?.name ?: name, jsonAnnotation?.name ?: name,
adapter, adapter,
property as KProperty1<Any, Any?>, property as KProperty1<Any, Any?>,
parameter parameter,
parameter?.index ?: -1
) )
} }
@@ -271,9 +275,13 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
bindings += binding 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()) val nonTransientBindings = bindings.filterNotNull()
return KotlinJsonAdapter(constructor, bindings, options).nullSafe() val options = JsonReader.Options.of(*nonTransientBindings.map { it.name }.toTypedArray())
return KotlinJsonAdapter(constructor, bindings, nonTransientBindings, options).nullSafe()
} }
} }

View File

@@ -535,6 +535,22 @@ class GeneratedAdaptersTest {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1) 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() { @Test fun transientProperty() {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter<TransientProperty>() val jsonAdapter = moshi.adapter<TransientProperty>()

View File

@@ -294,6 +294,21 @@ class KotlinJsonAdapterTest {
class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1) 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() { @Test fun requiredTransientConstructorParameterFails() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
try { try {