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 <jakew@google.com>

* Upper case JSON

Co-Authored-By: Jake Wharton <jakew@google.com>

* 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`
This commit is contained in:
Zac Sweers
2019-09-30 23:04:21 -04:00
committed by GitHub
parent 336ca952b0
commit 7804d74318
6 changed files with 310 additions and 254 deletions

View File

@@ -203,7 +203,7 @@ internal class AdapterGenerator(
result.addStatement("%N = %N.fromJson(%N)", result.addStatement("%N = %N.fromJson(%N)",
property.localName, nameAllocator[property.delegateKey], readerParam) property.localName, nameAllocator[property.delegateKey], readerParam)
} else { } else {
val exception = unexpectedNull(property.localName, readerParam) val exception = unexpectedNull(property, readerParam)
result.addStatement("%N = %N.fromJson(%N) ?: throw·%L", result.addStatement("%N = %N.fromJson(%N) ?: throw·%L",
property.localName, nameAllocator[property.delegateKey], readerParam, exception) property.localName, nameAllocator[property.delegateKey], readerParam, exception)
} }
@@ -221,7 +221,7 @@ internal class AdapterGenerator(
result.addStatement("%L -> %N = %N.fromJson(%N)", result.addStatement("%L -> %N = %N.fromJson(%N)",
propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam) propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam)
} else { } else {
val exception = unexpectedNull(property.localName, readerParam) val exception = unexpectedNull(property, readerParam)
result.addStatement("%L -> %N = %N.fromJson(%N) ?: throw·%L", result.addStatement("%L -> %N = %N.fromJson(%N) ?: throw·%L",
propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam, propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam,
exception) exception)
@@ -308,7 +308,8 @@ internal class AdapterGenerator(
} }
if (!property.isTransient && property.isRequired) { if (!property.isTransient && property.isRequired) {
val missingPropertyBlock = 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) result.addCode(" ?: throw·%L", missingPropertyBlock)
} }
separator = ",\n" separator = ",\n"
@@ -341,8 +342,9 @@ internal class AdapterGenerator(
return result.build() return result.build()
} }
private fun unexpectedNull(identifier: String, reader: ParameterSpec): CodeBlock { private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock {
return CodeBlock.of("%T.unexpectedNull(%S, %N)", Util::class, identifier, reader) return CodeBlock.of("%T.unexpectedNull(%S, %S, %N)",
MOSHI_UTIL, property.localName, property.jsonName, reader)
} }
private fun generateToJsonFun(): FunSpec { private fun generateToJsonFun(): FunSpec {

View File

@@ -83,8 +83,11 @@ internal class KotlinJsonAdapter<T>(
values[index] = binding.adapter.fromJson(reader) values[index] = binding.adapter.fromJson(reader)
if (values[index] == null && !binding.property.returnType.isMarkedNullable) { if (values[index] == null && !binding.property.returnType.isMarkedNullable) {
throw JsonDataException( throw Util.unexpectedNull(
"Non-null value '${binding.property.name}' was null at ${reader.path}") binding.property.name,
binding.jsonName,
reader
)
} }
} }
reader.endObject() reader.endObject()
@@ -93,8 +96,11 @@ internal class KotlinJsonAdapter<T>(
for (i in 0 until constructorSize) { for (i in 0 until constructorSize) {
if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) { if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) {
if (!constructor.parameters[i].type.isMarkedNullable) { if (!constructor.parameters[i].type.isMarkedNullable) {
throw JsonDataException( throw Util.missingProperty(
"Required value '${constructor.parameters[i].name}' missing at ${reader.path}") constructor.parameters[i].name,
bindings[i]?.jsonName,
reader
)
} }
values[i] = null // Replace absent with null. values[i] = null // Replace absent with null.
} }
@@ -130,6 +136,7 @@ internal class KotlinJsonAdapter<T>(
data class Binding<K, P>( data class Binding<K, P>(
val name: String, val name: 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?) {
@@ -245,8 +252,13 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()), property.name) resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()), property.name)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
bindingsByName[property.name] = KotlinJsonAdapter.Binding(name, adapter, bindingsByName[property.name] = KotlinJsonAdapter.Binding(
property as KProperty1<Any, Any?>, parameter) name,
jsonAnnotation?.name ?: name,
adapter,
property as KProperty1<Any, Any?>,
parameter
)
} }
val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>() val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()

View File

@@ -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<Array<*>> {
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<out Annotation>,
moshi: Moshi
): JsonAdapter<*>? {
// Prevent falling back to generated adapter lookup
val rawType = Types.getRawType(type)
val metadataClass = Class.forName("kotlin.Metadata") as Class<out Annotation>
check(!rawType.isAnnotationPresent(metadataClass)) {
"Unhandled Kotlin type in reflective test! $rawType"
}
return moshi.nextAdapter<Any>(this, type, annotations)
}
})
}
}
.build()
@Test fun requiredValueAbsent() {
val jsonAdapter = moshi.adapter<RequiredValueAbsent>()
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<RequiredValueWithDifferentJsonNameAbsent>()
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<HasNonNullProperty>()
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<HasNonNullProperty>()
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<HasNonNullPropertyDifferentJsonName>()
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<HasNonNullPropertyDifferentJsonName>()
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<HasNonNullConstructorParameter>()
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<HasNonNullConstructorParameter>()
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<HasNonNullConstructorParameter>()
assertThat(hasNonNullConstructorParameterAdapter
//language=JSON
.fromJson("{\"a\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback"))
val hasNullableConstructorParameterAdapter =
localMoshi.adapter<HasNullableConstructorParameter>()
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<HasNullableTypeWithGeneratedAdapter>()
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)
}
}

View File

@@ -410,53 +410,6 @@ class GeneratedAdaptersTest {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2) class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2)
@Test fun requiredValueAbsent() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter<RequiredValueAbsent>()
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<HasNonNullConstructorParameter>()
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<HasNonNullConstructorParameter>()
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() { @Test fun explicitNull() {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter<ExplicitNull>() val jsonAdapter = moshi.adapter<ExplicitNull>()
@@ -610,37 +563,6 @@ class GeneratedAdaptersTest {
} }
} }
@Test fun nonNullPropertySetToNullFailsWithJsonDataException() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter<HasNonNullProperty>()
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<HasNonNullProperty>()
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() { @Test fun manyProperties32() {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter<ManyProperties32>() val jsonAdapter = moshi.adapter<ManyProperties32>()
@@ -1114,62 +1036,11 @@ class GeneratedAdaptersTest {
@Test fun adaptersAreNullSafe() { @Test fun adaptersAreNullSafe() {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val adapter = moshi.adapter<HasNonNullConstructorParameter>() val adapter = moshi.adapter<HasNullableBoolean>()
assertThat(adapter.fromJson("null")).isNull() assertThat(adapter.fromJson("null")).isNull()
assertThat(adapter.toJson(null)).isEqualTo("null") 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<HasNonNullConstructorParameter>()
assertThat(hasNonNullConstructorParameterAdapter
.fromJson("{\"a\":null}")).isEqualTo(
HasNonNullConstructorParameter(
"fallback"))
val hasNullableConstructorParameterAdapter =
moshi.adapter<HasNullableConstructorParameter>()
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<HasNullableTypeWithGeneratedAdapter>()
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) @JsonClass(generateAdapter = true)
data class HasCollectionOfPrimitives(val listOfInts: List<Int>) data class HasCollectionOfPrimitives(val listOfInts: List<Int>)

View File

@@ -133,80 +133,6 @@ class KotlinJsonAdapterTest {
class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2) class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2)
@Test fun requiredValueAbsent() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonAdapter = moshi.adapter<RequiredValueAbsent>()
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<HasNonNullConstructorParameter>()
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<HasNonNullConstructorParameter>()
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<HasNonNullProperty>()
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<HasNonNullProperty>()
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() { @Test fun duplicatedValueParameter() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonAdapter = moshi.adapter<DuplicateValueParameter>() val jsonAdapter = moshi.adapter<DuplicateValueParameter>()
@@ -875,32 +801,6 @@ class KotlinJsonAdapterTest {
assertThat(adapter.toJson(value)).isEqualTo(json) 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<HasNonNullConstructorParameter>()
.fromJson("{\"a\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback"))
assertThat(moshi.adapter<HasNullableConstructorParameter>()
.fromJson("{\"a\":null}")).isEqualTo(HasNullableConstructorParameter("fallback"))
assertThat(moshi.adapter<HasNullableConstructorParameter>()
.toJson(HasNullableConstructorParameter(null))).isEqualTo("{\"a\":\"fallback\"}")
}
@Test fun mixingReflectionAndCodegen() { @Test fun mixingReflectionAndCodegen() {
val moshi = Moshi.Builder() val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory()) .add(KotlinJsonAdapterFactory())
@@ -970,7 +870,7 @@ class KotlinJsonAdapterTest {
.build() .build()
// TODO in CR: We had to mark this as nullable, vs before the jsonadapter factory would always run // TODO in CR: We had to mark this as nullable, vs before the jsonadapter factory would always run
val adapter = moshi.adapter<HasNonNullConstructorParameter?>() val adapter = moshi.adapter<HasNullableBoolean?>()
assertThat(adapter.fromJson("null")).isNull() assertThat(adapter.fromJson("null")).isNull()
assertThat(adapter.toJson(null)).isEqualTo("null") assertThat(adapter.toJson(null)).isEqualTo("null")
} }

View File

@@ -44,8 +44,6 @@ import static com.squareup.moshi.Types.subtypeOf;
import static com.squareup.moshi.Types.supertypeOf; import static com.squareup.moshi.Types.supertypeOf;
public final class Util { 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<Annotation> NO_ANNOTATIONS = Collections.emptySet(); public static final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {}; public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
@Nullable private static final Class<?> DEFAULT_CONSTRUCTOR_MARKER; @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); throw new IllegalStateException("No defaults constructor found for " + targetClass);
} }
public static JsonDataException missingProperty(String property, JsonReader reader) { public static JsonDataException missingProperty(
return jsonDataException(REQUIRED_PROPERTY_TEMPLATE, property, reader); 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) { public static JsonDataException unexpectedNull(
return jsonDataException(UNEXPECTED_NULL_TEMPLATE, property, reader); 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);
private static JsonDataException jsonDataException(
String template, String property, JsonReader reader) {
return new JsonDataException(
String.format(template, property, reader.getPath()));
} }
} }