mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
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:
@@ -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 {
|
||||||
|
@@ -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?>?>()
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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>)
|
||||||
|
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
private static JsonDataException jsonDataException(
|
) {
|
||||||
String template, String property, JsonReader reader) {
|
String path = reader.getPath();
|
||||||
return new JsonDataException(
|
String message;
|
||||||
String.format(template, property, reader.getPath()));
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user