mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Fix nullable properties (#992)
* Fix missing nullability/annotations when unwrapping typealiases Fixes #990 * Add extra nullable properties test + more nullability in alias test * Add more complex typeAliasNullability test * Recursively check nullability and track annotations in typealiases
This commit is contained in:
@@ -52,6 +52,7 @@ import java.lang.annotation.ElementType
|
|||||||
import java.lang.annotation.Retention
|
import java.lang.annotation.Retention
|
||||||
import java.lang.annotation.RetentionPolicy
|
import java.lang.annotation.RetentionPolicy
|
||||||
import java.lang.annotation.Target
|
import java.lang.annotation.Target
|
||||||
|
import java.util.TreeSet
|
||||||
import javax.annotation.processing.Messager
|
import javax.annotation.processing.Messager
|
||||||
import javax.lang.model.element.ElementKind
|
import javax.lang.model.element.ElementKind
|
||||||
import javax.lang.model.element.TypeElement
|
import javax.lang.model.element.TypeElement
|
||||||
@@ -322,10 +323,22 @@ private fun String.escapeDollarSigns(): String {
|
|||||||
private fun TypeName.unwrapTypeAlias(): TypeName {
|
private fun TypeName.unwrapTypeAlias(): TypeName {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is ClassName -> {
|
is ClassName -> {
|
||||||
tag<TypeNameAliasTag>()?.type?.unwrapTypeAlias() ?: this
|
tag<TypeNameAliasTag>()?.type?.let { unwrappedType ->
|
||||||
|
// If any type is nullable, then the whole thing is nullable
|
||||||
|
var isAnyNullable = isNullable
|
||||||
|
// Keep track of all annotations across type levels. Sort them too for consistency.
|
||||||
|
val runningAnnotations = TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
|
||||||
|
addAll(annotations)
|
||||||
|
}
|
||||||
|
val nestedUnwrappedType = unwrappedType.unwrapTypeAlias()
|
||||||
|
runningAnnotations.addAll(nestedUnwrappedType.annotations)
|
||||||
|
isAnyNullable = isAnyNullable || nestedUnwrappedType.isNullable
|
||||||
|
nestedUnwrappedType.copy(nullable = isAnyNullable, annotations = runningAnnotations.toList())
|
||||||
|
} ?: this
|
||||||
}
|
}
|
||||||
is ParameterizedTypeName -> {
|
is ParameterizedTypeName -> {
|
||||||
return (rawType.unwrapTypeAlias() as ClassName).parameterizedBy(typeArguments.map { it.unwrapTypeAlias() })
|
return (rawType.unwrapTypeAlias() as ClassName).parameterizedBy(typeArguments.map { it.unwrapTypeAlias() })
|
||||||
|
.copy(nullable = isNullable, annotations = annotations)
|
||||||
}
|
}
|
||||||
is TypeVariableName -> {
|
is TypeVariableName -> {
|
||||||
return copy(bounds = bounds.map { it.unwrapTypeAlias() })
|
return copy(bounds = bounds.map { it.unwrapTypeAlias() })
|
||||||
|
@@ -433,7 +433,7 @@ class DualKotlinTest(useReflection: Boolean) {
|
|||||||
val parameterized: GenericClass<TypeAlias>,
|
val parameterized: GenericClass<TypeAlias>,
|
||||||
val wildcardIn: GenericClass<in TypeAlias>,
|
val wildcardIn: GenericClass<in TypeAlias>,
|
||||||
val wildcardOut: GenericClass<out TypeAlias>,
|
val wildcardOut: GenericClass<out TypeAlias>,
|
||||||
val complex: GenericClass<GenericTypeAlias>
|
val complex: GenericClass<GenericTypeAlias>?
|
||||||
)
|
)
|
||||||
|
|
||||||
// Regression test for https://github.com/square/moshi/issues/991
|
// Regression test for https://github.com/square/moshi/issues/991
|
||||||
@@ -481,11 +481,52 @@ class DualKotlinTest(useReflection: Boolean) {
|
|||||||
val double: Double,
|
val double: Double,
|
||||||
val nullableDouble: Double? = null
|
val nullableDouble: Double? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Regression test for https://github.com/square/moshi/issues/990
|
||||||
|
@Test fun nullableProperties() {
|
||||||
|
val adapter = moshi.adapter<NullableList>()
|
||||||
|
|
||||||
|
@Language("JSON")
|
||||||
|
val testJson = """{"nullableList":null}"""
|
||||||
|
|
||||||
|
assertThat(adapter.serializeNulls().toJson(NullableList(null)))
|
||||||
|
.isEqualTo(testJson)
|
||||||
|
|
||||||
|
val result = adapter.fromJson(testJson)!!
|
||||||
|
assertThat(result.nullableList).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class NullableList(val nullableList: List<Any>?)
|
||||||
|
|
||||||
|
@Test fun typeAliasNullability() {
|
||||||
|
val adapter = moshi.adapter<TypeAliasNullability>()
|
||||||
|
|
||||||
|
@Language("JSON")
|
||||||
|
val testJson = """{"aShouldBeNonNull":3,"nullableAShouldBeNullable":null,"redundantNullableAShouldBeNullable":null,"manuallyNullableAShouldBeNullable":null,"convolutedMultiNullableShouldBeNullable":null,"deepNestedNullableShouldBeNullable":null}"""
|
||||||
|
|
||||||
|
val instance = TypeAliasNullability(3, null, null, null, null, null)
|
||||||
|
assertThat(adapter.serializeNulls().toJson(instance))
|
||||||
|
.isEqualTo(testJson)
|
||||||
|
|
||||||
|
val result = adapter.fromJson(testJson)!!
|
||||||
|
assertThat(result).isEqualTo(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class TypeAliasNullability(
|
||||||
|
val aShouldBeNonNull: A,
|
||||||
|
val nullableAShouldBeNullable: NullableA,
|
||||||
|
val redundantNullableAShouldBeNullable: NullableA?,
|
||||||
|
val manuallyNullableAShouldBeNullable: A?,
|
||||||
|
val convolutedMultiNullableShouldBeNullable: NullableB?,
|
||||||
|
val deepNestedNullableShouldBeNullable: E
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias TypeAlias = Int
|
typealias TypeAlias = Int
|
||||||
@Suppress("REDUNDANT_PROJECTION")
|
@Suppress("REDUNDANT_PROJECTION")
|
||||||
typealias GenericTypeAlias = List<out GenericClass<in TypeAlias>>
|
typealias GenericTypeAlias = List<out GenericClass<in TypeAlias>?>?
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class GenericClass<T>(val value: T)
|
data class GenericClass<T>(val value: T)
|
||||||
@@ -493,3 +534,11 @@ data class GenericClass<T>(val value: T)
|
|||||||
// Has to be outside since inline classes are only allowed on top level
|
// Has to be outside since inline classes are only allowed on top level
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
inline class InlineClass(val i: Int)
|
inline class InlineClass(val i: Int)
|
||||||
|
|
||||||
|
typealias A = Int
|
||||||
|
typealias NullableA = A?
|
||||||
|
typealias B = NullableA
|
||||||
|
typealias NullableB = B?
|
||||||
|
typealias C = NullableA
|
||||||
|
typealias D = C
|
||||||
|
typealias E = D
|
||||||
|
Reference in New Issue
Block a user