From efb0fc09230b656387808cf78b47ce966e9322f6 Mon Sep 17 00:00:00 2001 From: Eric Cochran Date: Tue, 15 Jan 2019 10:36:43 -0800 Subject: [PATCH] Support multiple transient properties in KotlinJsonAdapter. --- .../moshi/kotlin/reflect/KotlinJsonAdapter.kt | 54 +++++++++++-------- .../kotlin/codegen/GeneratedAdaptersTest.kt | 16 ++++++ .../kotlin/reflect/KotlinJsonAdapterTest.kt | 15 ++++++ 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt index 555638b..b33a5ce 100644 --- a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt +++ b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt @@ -54,35 +54,36 @@ private val ABSENT_VALUE = Any() * constructor, and then by setting any additional properties that exist, if any. */ internal class KotlinJsonAdapter( - private val constructor: KFunction, - private val bindings: List?>, - private val options: JsonReader.Options + val constructor: KFunction, + val allBindings: List?>, + val nonTransientBindings: List>, + val options: JsonReader.Options ) : JsonAdapter() { override fun fromJson(reader: JsonReader): T { val constructorSize = constructor.parameters.size // Read each value into its slot in the array. - val values = Array(bindings.size) { ABSENT_VALUE } + val values = Array(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 '${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( binding.property.name, binding.jsonName, @@ -98,7 +99,7 @@ internal class KotlinJsonAdapter( if (!constructor.parameters[i].type.isMarkedNullable) { throw Util.missingProperty( constructor.parameters[i].name, - bindings[i]?.jsonName, + allBindings[i]?.jsonName, reader ) } @@ -110,8 +111,8 @@ internal class KotlinJsonAdapter( 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) } @@ -123,7 +124,7 @@ internal class KotlinJsonAdapter( 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) @@ -135,11 +136,13 @@ internal class KotlinJsonAdapter( override fun toString() = "KotlinJsonAdapter(${constructor.returnType})" data class Binding( - val name: String, - val jsonName: String?, - val adapter: JsonAdapter

, - val property: KProperty1, - val parameter: KParameter?) { + val name: String, + val jsonName: String?, + val adapter: JsonAdapter

, + val property: KProperty1, + val parameter: KParameter?, + val propertyIndex: Int + ) { fun get(value: K) = property.get(value) fun set(result: K, value: P) { @@ -257,7 +260,8 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory { jsonAnnotation?.name ?: name, adapter, property as KProperty1, - parameter + parameter, + parameter?.index ?: -1 ) } @@ -271,9 +275,13 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory { 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() } } diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt index 9466831..a583841 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt @@ -535,6 +535,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() diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt index 0cb9dc2..739bad4 100644 --- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt @@ -294,6 +294,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 {