From 7804d7431865aadc869b9fe41991fcc79288e16b Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Mon, 30 Sep 2019 23:04:21 -0400 Subject: [PATCH] Report json name if different from property name in kotlin (#917) * Report json name in code gen if different from property name Resolves #800 * Unify required property name error message * Report json name in kotlinjsonadapter, match code gen * Upper case JSON Co-Authored-By: Jake Wharton * Upper case JSON Co-Authored-By: Jake Wharton * Don't keep constants * Inline json name - property name comparison to util methods * Remove unnecessary constructor keyword * Consolidate non-null/missing property tests to parameterized suite * Add custom json name tests for nonNull property checks * Rename test to make maven happy Maven won't run the test unless it ends with `Test` or `TestCase` --- .../kotlin/codegen/api/AdapterGenerator.kt | 12 +- .../moshi/kotlin/reflect/KotlinJsonAdapter.kt | 24 +- .../squareup/moshi/kotlin/DualKotlinTest.kt | 255 ++++++++++++++++++ .../kotlin/codegen/GeneratedAdaptersTest.kt | 131 +-------- .../kotlin/reflect/KotlinJsonAdapterTest.kt | 102 +------ .../com/squareup/moshi/internal/Util.java | 40 ++- 6 files changed, 310 insertions(+), 254 deletions(-) create mode 100644 kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt index 3ea13ed..638fd1d 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt @@ -203,7 +203,7 @@ internal class AdapterGenerator( result.addStatement("%N = %N.fromJson(%N)", property.localName, nameAllocator[property.delegateKey], readerParam) } else { - val exception = unexpectedNull(property.localName, readerParam) + val exception = unexpectedNull(property, readerParam) result.addStatement("%N = %N.fromJson(%N) ?: throw·%L", property.localName, nameAllocator[property.delegateKey], readerParam, exception) } @@ -221,7 +221,7 @@ internal class AdapterGenerator( result.addStatement("%L -> %N = %N.fromJson(%N)", propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam) } else { - val exception = unexpectedNull(property.localName, readerParam) + val exception = unexpectedNull(property, readerParam) result.addStatement("%L -> %N = %N.fromJson(%N) ?: throw·%L", propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam, exception) @@ -308,7 +308,8 @@ internal class AdapterGenerator( } if (!property.isTransient && property.isRequired) { val missingPropertyBlock = - CodeBlock.of("%T.missingProperty(%S, %N)", Util::class, property.localName, readerParam) + CodeBlock.of("%T.missingProperty(%S, %S, %N)", + MOSHI_UTIL, property.localName, property.jsonName, readerParam) result.addCode(" ?: throw·%L", missingPropertyBlock) } separator = ",\n" @@ -341,8 +342,9 @@ internal class AdapterGenerator( return result.build() } - private fun unexpectedNull(identifier: String, reader: ParameterSpec): CodeBlock { - return CodeBlock.of("%T.unexpectedNull(%S, %N)", Util::class, identifier, reader) + private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock { + return CodeBlock.of("%T.unexpectedNull(%S, %S, %N)", + MOSHI_UTIL, property.localName, property.jsonName, reader) } private fun generateToJsonFun(): FunSpec { 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 0aafe56..555638b 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 @@ -83,8 +83,11 @@ internal class KotlinJsonAdapter( values[index] = binding.adapter.fromJson(reader) if (values[index] == null && !binding.property.returnType.isMarkedNullable) { - throw JsonDataException( - "Non-null value '${binding.property.name}' was null at ${reader.path}") + throw Util.unexpectedNull( + binding.property.name, + binding.jsonName, + reader + ) } } reader.endObject() @@ -93,8 +96,11 @@ internal class KotlinJsonAdapter( 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}") + throw Util.missingProperty( + constructor.parameters[i].name, + bindings[i]?.jsonName, + reader + ) } values[i] = null // Replace absent with null. } @@ -130,6 +136,7 @@ internal class KotlinJsonAdapter( data class Binding( val name: String, + val jsonName: String?, val adapter: JsonAdapter

, val property: KProperty1, val parameter: KParameter?) { @@ -245,8 +252,13 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory { resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()), property.name) @Suppress("UNCHECKED_CAST") - bindingsByName[property.name] = KotlinJsonAdapter.Binding(name, adapter, - property as KProperty1, parameter) + bindingsByName[property.name] = KotlinJsonAdapter.Binding( + name, + jsonAnnotation?.name ?: name, + adapter, + property as KProperty1, + parameter + ) } val bindings = ArrayList?>() diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt new file mode 100644 index 0000000..82488d6 --- /dev/null +++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DualKotlinTest.kt @@ -0,0 +1,255 @@ +package com.squareup.moshi.kotlin + +import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonAdapter.Factory +import com.squareup.moshi.JsonClass +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.Moshi +import com.squareup.moshi.ToJson +import com.squareup.moshi.Types +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.squareup.moshi.kotlin.reflect.adapter +import org.assertj.core.api.Assertions.assertThat +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import java.lang.reflect.Type +import kotlin.annotation.AnnotationRetention.RUNTIME + +/** + * Parameterized tests that test serialization with both [KotlinJsonAdapterFactory] and code gen. + */ +@RunWith(Parameterized::class) +class DualKotlinTest(useReflection: Boolean) { + + companion object { + @Parameters(name = "reflective={0}") + @JvmStatic + fun parameters(): List> { + return listOf( + arrayOf(true), + arrayOf(false) + ) + } + } + + @Suppress("UNCHECKED_CAST") + private val moshi = Moshi.Builder() + .apply { + if (useReflection) { + add(KotlinJsonAdapterFactory()) + add(object : Factory { + override fun create( + type: Type, + annotations: MutableSet, + moshi: Moshi + ): JsonAdapter<*>? { + // Prevent falling back to generated adapter lookup + val rawType = Types.getRawType(type) + val metadataClass = Class.forName("kotlin.Metadata") as Class + check(!rawType.isAnnotationPresent(metadataClass)) { + "Unhandled Kotlin type in reflective test! $rawType" + } + return moshi.nextAdapter(this, type, annotations) + } + }) + } + } + .build() + + + @Test fun requiredValueAbsent() { + val jsonAdapter = moshi.adapter() + + try { + //language=JSON + jsonAdapter.fromJson("""{"a":4}""") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Required value 'b' missing at $") + } + } + + @JsonClass(generateAdapter = true) + class RequiredValueAbsent(var a: Int = 3, var b: Int) + + @Test fun requiredValueWithDifferentJsonNameAbsent() { + val jsonAdapter = moshi.adapter() + + try { + //language=JSON + jsonAdapter.fromJson("""{"a":4}""") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Required value 'b' (JSON name 'bPrime') missing at \$") + } + } + + @JsonClass(generateAdapter = true) + class RequiredValueWithDifferentJsonNameAbsent(var a: Int = 3, @Json(name = "bPrime") var b: Int) + + @Test fun nonNullPropertySetToNullFailsWithJsonDataException() { + val jsonAdapter = moshi.adapter() + + try { + //language=JSON + jsonAdapter.fromJson("{\"a\":null}") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") + } + } + + @Test fun nonNullPropertySetToNullFromAdapterFailsWithJsonDataException() { + val jsonAdapter = moshi.newBuilder() + .add(object { + @Suppress("UNUSED_PARAMETER") + @FromJson + fun fromJson(string: String): String? = null + }) + .build() + .adapter() + + try { + //language=JSON + jsonAdapter.fromJson("{\"a\":\"hello\"}") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") + } + } + + @JsonClass(generateAdapter = true) + class HasNonNullProperty { + var a: String = "" + } + + @Test fun nonNullPropertyWithJsonNameSetToNullFailsWithJsonDataException() { + val jsonAdapter = moshi.adapter() + + try { + //language=JSON + jsonAdapter.fromJson("{\"aPrime\":null}") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Non-null value 'a' (JSON name 'aPrime') was null at \$.aPrime") + } + } + + @Test fun nonNullPropertyWithJsonNameSetToNullFromAdapterFailsWithJsonDataException() { + val jsonAdapter = moshi.newBuilder() + .add(object { + @Suppress("UNUSED_PARAMETER") + @FromJson + fun fromJson(string: String): String? = null + }) + .build() + .adapter() + + try { + //language=JSON + jsonAdapter.fromJson("{\"aPrime\":\"hello\"}") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Non-null value 'a' (JSON name 'aPrime') was null at \$.aPrime") + } + } + + @JsonClass(generateAdapter = true) + class HasNonNullPropertyDifferentJsonName { + @Json(name = "aPrime") var a: String = "" + } + + @Test fun nonNullConstructorParameterCalledWithNullFailsWithJsonDataException() { + val jsonAdapter = moshi.adapter() + + try { + //language=JSON + jsonAdapter.fromJson("{\"a\":null}") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") + } + } + + @Test fun nonNullConstructorParameterCalledWithNullFromAdapterFailsWithJsonDataException() { + val jsonAdapter = moshi.newBuilder() + .add(object { + @Suppress("UNUSED_PARAMETER") + @FromJson + fun fromJson(string: String): String? = null + }) + .build() + .adapter() + + try { + //language=JSON + jsonAdapter.fromJson("{\"a\":\"hello\"}") + fail() + } catch (expected: JsonDataException) { + assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") + } + } + + @Retention(RUNTIME) + annotation class Nullable + + @JsonClass(generateAdapter = true) + data class HasNonNullConstructorParameter(val a: String) + + @JsonClass(generateAdapter = true) + data class HasNullableConstructorParameter(val a: String?) + + @Test fun delegatesToInstalledAdaptersBeforeNullChecking() { + val localMoshi = moshi.newBuilder() + .add(object { + @FromJson + fun fromJson(@Nullable string: String?): String { + return string ?: "fallback" + } + + @ToJson + fun toJson(@Nullable value: String?): String { + return value ?: "fallback" + } + }) + .build() + + val hasNonNullConstructorParameterAdapter = + localMoshi.adapter() + assertThat(hasNonNullConstructorParameterAdapter + //language=JSON + .fromJson("{\"a\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback")) + + val hasNullableConstructorParameterAdapter = + localMoshi.adapter() + assertThat(hasNullableConstructorParameterAdapter + //language=JSON + .fromJson("{\"a\":null}")).isEqualTo(HasNullableConstructorParameter("fallback")) + assertThat(hasNullableConstructorParameterAdapter + //language=JSON + .toJson(HasNullableConstructorParameter(null))).isEqualTo("{\"a\":\"fallback\"}") + } + + @JsonClass(generateAdapter = true) + data class HasNullableTypeWithGeneratedAdapter(val a: HasNonNullConstructorParameter?) + + @Test fun delegatesToInstalledAdaptersBeforeNullCheckingWithGeneratedAdapter() { + val adapter = moshi.adapter() + + val encoded = HasNullableTypeWithGeneratedAdapter(null) + //language=JSON + assertThat(adapter.toJson(encoded)).isEqualTo("""{}""") + //language=JSON + assertThat(adapter.serializeNulls().toJson(encoded)).isEqualTo("""{"a":null}""") + + //language=JSON + val decoded = adapter.fromJson("""{"a":null}""")!! + assertThat(decoded.a).isEqualTo(null) + } + +} 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 05af515..aa756fb 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 @@ -410,53 +410,6 @@ class GeneratedAdaptersTest { @JsonClass(generateAdapter = true) class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2) - @Test fun requiredValueAbsent() { - val moshi = Moshi.Builder().build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("""{"a":4}""") - fail() - } catch(expected: JsonDataException) { - assertThat(expected).hasMessage("Required property 'b' missing at \$") - } - } - - @JsonClass(generateAdapter = true) - class RequiredValueAbsent(var a: Int = 3, var b: Int) - - @Test fun nonNullConstructorParameterCalledWithNullFailsWithJsonDataException() { - val moshi = Moshi.Builder().build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":null}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - @Test fun nonNullConstructorParameterCalledWithNullFromAdapterFailsWithJsonDataException() { - val moshi = Moshi.Builder().add(object { - @FromJson fun fromJson(string: String): String? = null - }).build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":\"hello\"}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - @JsonClass(generateAdapter = true) - data class HasNonNullConstructorParameter(val a: String) - - @JsonClass(generateAdapter = true) - data class HasNullableConstructorParameter(val a: String?) - @Test fun explicitNull() { val moshi = Moshi.Builder().build() val jsonAdapter = moshi.adapter() @@ -610,37 +563,6 @@ class GeneratedAdaptersTest { } } - @Test fun nonNullPropertySetToNullFailsWithJsonDataException() { - val moshi = Moshi.Builder().build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":null}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - @Test fun nonNullPropertySetToNullFromAdapterFailsWithJsonDataException() { - val moshi = Moshi.Builder().add(object { - @FromJson fun fromJson(string: String): String? = null - }).build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":\"hello\"}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - @JsonClass(generateAdapter = true) - class HasNonNullProperty { - var a: String = "" - } - @Test fun manyProperties32() { val moshi = Moshi.Builder().build() val jsonAdapter = moshi.adapter() @@ -1114,62 +1036,11 @@ class GeneratedAdaptersTest { @Test fun adaptersAreNullSafe() { val moshi = Moshi.Builder().build() - val adapter = moshi.adapter() + val adapter = moshi.adapter() assertThat(adapter.fromJson("null")).isNull() assertThat(adapter.toJson(null)).isEqualTo("null") } - @Retention(AnnotationRetention.RUNTIME) - annotation class Nullable - - @Test fun delegatesToInstalledAdaptersBeforeNullChecking() { - val moshi = Moshi.Builder() - .add(object { - @FromJson fun fromJson(@Nullable string: String?): String { - return string ?: "fallback" - } - - @ToJson fun toJson(@Nullable value: String?): String { - return value ?: "fallback" - } - }) - .build() - - val hasNonNullConstructorParameterAdapter = - moshi.adapter() - assertThat(hasNonNullConstructorParameterAdapter - .fromJson("{\"a\":null}")).isEqualTo( - HasNonNullConstructorParameter( - "fallback")) - - val hasNullableConstructorParameterAdapter = - moshi.adapter() - assertThat(hasNullableConstructorParameterAdapter - .fromJson("{\"a\":null}")).isEqualTo( - HasNullableConstructorParameter( - "fallback")) - assertThat(hasNullableConstructorParameterAdapter - .toJson( - HasNullableConstructorParameter( - null))).isEqualTo("{\"a\":\"fallback\"}") - } - - @JsonClass(generateAdapter = true) - data class HasNullableTypeWithGeneratedAdapter(val a: HasNonNullConstructorParameter?) - - @Test fun delegatesToInstalledAdaptersBeforeNullCheckingWithGeneratedAdapter() { - val moshi = Moshi.Builder().build() - val adapter = moshi.adapter() - - val encoded = HasNullableTypeWithGeneratedAdapter( - null) - assertThat(adapter.toJson(encoded)).isEqualTo("""{}""") - assertThat(adapter.serializeNulls().toJson(encoded)).isEqualTo("""{"a":null}""") - - val decoded = adapter.fromJson("""{"a":null}""")!! - assertThat(decoded.a).isEqualTo(null) - } - @JsonClass(generateAdapter = true) data class HasCollectionOfPrimitives(val listOfInts: List) 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 953d71f..0cb9dc2 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 @@ -133,80 +133,6 @@ class KotlinJsonAdapterTest { class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2) - @Test fun requiredValueAbsent() { - val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("""{"a":4}""") - fail() - } catch(expected: JsonDataException) { - assertThat(expected).hasMessage("Required value 'b' missing at $") - } - } - - class RequiredValueAbsent(var a: Int = 3, var b: Int) - - @Test fun nonNullConstructorParameterCalledWithNullFailsWithJsonDataException() { - val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":null}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - @Test fun nonNullConstructorParameterCalledWithNullFromAdapterFailsWithJsonDataException() { - val moshi = Moshi.Builder().add(object { - @FromJson fun fromJson(string: String): String? = null - }).add(KotlinJsonAdapterFactory()).build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":\"hello\"}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - data class HasNonNullConstructorParameter(val a: String) - - data class HasNullableConstructorParameter(val a: String?) - - @Test fun nonNullPropertySetToNullFailsWithJsonDataException() { - val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":null}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - @Test fun nonNullPropertySetToNullFromAdapterFailsWithJsonDataException() { - val moshi = Moshi.Builder().add(object { - @FromJson fun fromJson(string: String): String? = null - }).add(KotlinJsonAdapterFactory()).build() - val jsonAdapter = moshi.adapter() - - try { - jsonAdapter.fromJson("{\"a\":\"hello\"}") - fail() - } catch (expected: JsonDataException) { - assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a") - } - } - - class HasNonNullProperty { - var a: String = "" - } - @Test fun duplicatedValueParameter() { val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() val jsonAdapter = moshi.adapter() @@ -875,32 +801,6 @@ class KotlinJsonAdapterTest { assertThat(adapter.toJson(value)).isEqualTo(json) } - @Retention(RUNTIME) - annotation class Nullable - - @Test fun delegatesToInstalledAdaptersBeforeNullChecking() { - val moshi = Moshi.Builder() - .add(object { - @FromJson fun fromJson(@Nullable string: String?): String { - return string ?: "fallback" - } - - @ToJson fun toJson(@Nullable value: String?): String { - return value ?: "fallback" - } - }) - .add(KotlinJsonAdapterFactory()) - .build() - - assertThat(moshi.adapter() - .fromJson("{\"a\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback")) - - assertThat(moshi.adapter() - .fromJson("{\"a\":null}")).isEqualTo(HasNullableConstructorParameter("fallback")) - assertThat(moshi.adapter() - .toJson(HasNullableConstructorParameter(null))).isEqualTo("{\"a\":\"fallback\"}") - } - @Test fun mixingReflectionAndCodegen() { val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) @@ -970,7 +870,7 @@ class KotlinJsonAdapterTest { .build() // TODO in CR: We had to mark this as nullable, vs before the jsonadapter factory would always run - val adapter = moshi.adapter() + val adapter = moshi.adapter() assertThat(adapter.fromJson("null")).isNull() assertThat(adapter.toJson(null)).isEqualTo("null") } diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.java b/moshi/src/main/java/com/squareup/moshi/internal/Util.java index 3f1c866..1c49869 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.java +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.java @@ -44,8 +44,6 @@ import static com.squareup.moshi.Types.subtypeOf; import static com.squareup.moshi.Types.supertypeOf; public final class Util { - private static final String REQUIRED_PROPERTY_TEMPLATE = "Required property '%s' missing at %s"; - private static final String UNEXPECTED_NULL_TEMPLATE = "Non-null value '%s' was null at %s"; public static final Set NO_ANNOTATIONS = Collections.emptySet(); public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {}; @Nullable private static final Class DEFAULT_CONSTRUCTOR_MARKER; @@ -567,17 +565,35 @@ public final class Util { throw new IllegalStateException("No defaults constructor found for " + targetClass); } - public static JsonDataException missingProperty(String property, JsonReader reader) { - return jsonDataException(REQUIRED_PROPERTY_TEMPLATE, property, reader); + public static JsonDataException missingProperty( + String propertyName, + String jsonName, + JsonReader reader + ) { + String path = reader.getPath(); + String message; + if (jsonName.equals(propertyName)) { + message = String.format("Required value '%s' missing at %s", propertyName, path); + } else { + message = String.format("Required value '%s' (JSON name '%s') missing at %s", + propertyName, jsonName, path); + } + return new JsonDataException(message); } - public static JsonDataException unexpectedNull(String property, JsonReader reader) { - return jsonDataException(UNEXPECTED_NULL_TEMPLATE, property, reader); - } - - private static JsonDataException jsonDataException( - String template, String property, JsonReader reader) { - return new JsonDataException( - String.format(template, property, reader.getPath())); + public static JsonDataException unexpectedNull( + String propertyName, + String jsonName, + JsonReader reader + ) { + String path = reader.getPath(); + String message; + if (jsonName.equals(propertyName)) { + message = String.format("Non-null value '%s' was null at %s", propertyName, path); + } else { + message = String.format("Non-null value '%s' (JSON name '%s') was null at %s", + propertyName, jsonName, path); + } + return new JsonDataException(message); } }