Merge pull request #660 from square/eric.fix-preemptive-null-check

Allow user adapters to convert null to non-null in codegen.
This commit is contained in:
Jesse Wilson
2018-09-20 23:36:40 -04:00
committed by GitHub
3 changed files with 79 additions and 7 deletions

View File

@@ -69,11 +69,9 @@ internal data class DelegateKey(
} }
val finalArgs = arrayOf(*standardArgs, *args) val finalArgs = arrayOf(*standardArgs, *args)
val nullModifier = if (nullable) ".nullSafe()" else ".nonNull()"
return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE) return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
.addAnnotations(jsonQualifiers) .addAnnotations(jsonQualifiers)
.initializer("%1N.adapter%2L(%3L$initializerString)$nullModifier", *finalArgs) .initializer("%1N.adapter%2L(%3L$initializerString)", *finalArgs)
.build() .build()
} }
} }

View File

@@ -413,7 +413,7 @@ class GeneratedAdaptersTest {
jsonAdapter.fromJson("{\"a\":null}") jsonAdapter.fromJson("{\"a\":null}")
fail() fail()
} catch (expected: JsonDataException) { } catch (expected: JsonDataException) {
assertThat(expected).hasMessage("Unexpected null at \$.a") assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a")
} }
} }
@@ -432,7 +432,10 @@ class GeneratedAdaptersTest {
} }
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
class HasNonNullConstructorParameter(val a: String) 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()
@@ -592,7 +595,7 @@ class GeneratedAdaptersTest {
jsonAdapter.fromJson("{\"a\":null}") jsonAdapter.fromJson("{\"a\":null}")
fail() fail()
} catch (expected: JsonDataException) { } catch (expected: JsonDataException) {
assertThat(expected).hasMessage("Unexpected null at \$.a") assertThat(expected).hasMessage("Non-null value 'a' was null at \$.a")
} }
} }
@@ -1083,6 +1086,50 @@ class GeneratedAdaptersTest {
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::class.java)
assertThat(hasNonNullConstructorParameterAdapter
.fromJson("{\"a\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback"))
val hasNullableConstructorParameterAdapter =
moshi.adapter(HasNullableConstructorParameter::class.java)
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::class.java)
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)
}
} }
// Has to be outside to avoid Types seeing an owning class // Has to be outside to avoid Types seeing an owning class

View File

@@ -170,7 +170,9 @@ class KotlinJsonAdapterTest {
} }
} }
class HasNonNullConstructorParameter(val a: String) data class HasNonNullConstructorParameter(val a: String)
data class HasNullableConstructorParameter(val a: String?)
@Test fun nonNullPropertySetToNullFailsWithJsonDataException() { @Test fun nonNullPropertySetToNullFailsWithJsonDataException() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
@@ -853,6 +855,31 @@ 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"
}
})
.build()
assertThat(moshi.adapter(HasNonNullConstructorParameter::class.java)
.fromJson("{\"a\":null}")).isEqualTo(HasNonNullConstructorParameter("fallback"))
assertThat(moshi.adapter(HasNullableConstructorParameter::class.java)
.fromJson("{\"a\":null}")).isEqualTo(HasNullableConstructorParameter("fallback"))
assertThat(moshi.adapter(HasNullableConstructorParameter::class.java)
.toJson(HasNullableConstructorParameter(null))).isEqualTo("{\"a\":\"fallback\"}")
}
@Test fun mixingReflectionAndCodegen() { @Test fun mixingReflectionAndCodegen() {
val moshi = Moshi.Builder() val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory()) .add(KotlinJsonAdapterFactory())