mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Add @Json.ignore
(#1417)
* Default Json.name to an unset value * Promote shared transient tests to DualKotlinTest * Add new ignore property to Json * Support it in ClassJsonAdapter * Mention no enum/record support * Support in KotlinJsonAdapter * Rework code gen API to know of "ignored" * Support in apt code gen * Support in KSP * Update old non-working transient example test * Synthetic holders * Use field on both
This commit is contained in:
@@ -26,5 +26,6 @@ public data class TargetParameter(
|
||||
val type: TypeName,
|
||||
val hasDefault: Boolean,
|
||||
val jsonName: String? = null,
|
||||
val jsonIgnore: Boolean = false,
|
||||
val qualifiers: Set<AnnotationSpec>? = null
|
||||
)
|
||||
|
@@ -25,7 +25,8 @@ public data class TargetProperty(
|
||||
val propertySpec: PropertySpec,
|
||||
val parameter: TargetParameter?,
|
||||
val visibility: KModifier,
|
||||
val jsonName: String?
|
||||
val jsonName: String?,
|
||||
val jsonIgnore: Boolean
|
||||
) {
|
||||
val name: String get() = propertySpec.name
|
||||
val type: TypeName get() = propertySpec.type
|
||||
|
@@ -64,6 +64,7 @@ import javax.tools.Diagnostic.Kind.WARNING
|
||||
|
||||
private val JSON_QUALIFIER = JsonQualifier::class.java
|
||||
private val JSON = Json::class.asClassName()
|
||||
private val TRANSIENT = Transient::class.asClassName()
|
||||
private val OBJECT_CLASS = ClassName("java.lang", "Object")
|
||||
private val VISIBILITY_MODIFIERS = setOf(
|
||||
KModifier.INTERNAL,
|
||||
@@ -95,7 +96,8 @@ internal fun primaryConstructor(
|
||||
type = parameter.type,
|
||||
hasDefault = parameter.defaultValue != null,
|
||||
qualifiers = parameter.annotations.qualifiers(messager, elements),
|
||||
jsonName = parameter.annotations.jsonName()
|
||||
jsonName = parameter.annotations.jsonName(),
|
||||
jsonIgnore = parameter.annotations.jsonIgnore(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -386,7 +388,6 @@ private fun declaredProperties(
|
||||
currentClass: ClassName,
|
||||
resolvedTypes: List<ResolvedTypeMapping>
|
||||
): Map<String, TargetProperty> {
|
||||
|
||||
val result = mutableMapOf<String, TargetProperty>()
|
||||
for (initialProperty in kotlinApi.propertySpecs) {
|
||||
val resolvedType = resolveTypeArgs(
|
||||
@@ -398,19 +399,22 @@ private fun declaredProperties(
|
||||
val property = initialProperty.toBuilder(type = resolvedType).build()
|
||||
val name = property.name
|
||||
val parameter = constructor.parameters[name]
|
||||
val isIgnored = property.annotations.any { it.typeName == TRANSIENT } ||
|
||||
parameter?.jsonIgnore == true ||
|
||||
property.annotations.jsonIgnore()
|
||||
result[name] = TargetProperty(
|
||||
propertySpec = property,
|
||||
parameter = parameter,
|
||||
visibility = property.modifiers.visibility(),
|
||||
jsonName = parameter?.jsonName ?: property.annotations.jsonName()
|
||||
?: name.escapeDollarSigns()
|
||||
?: name.escapeDollarSigns(),
|
||||
jsonIgnore = isIgnored
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private val TargetProperty.isTransient get() = propertySpec.annotations.any { it.typeName == Transient::class.asClassName() }
|
||||
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
|
||||
private val TargetProperty.isVisible: Boolean
|
||||
get() {
|
||||
@@ -429,11 +433,11 @@ internal fun TargetProperty.generator(
|
||||
elements: Elements,
|
||||
instantiateAnnotations: Boolean
|
||||
): PropertyGenerator? {
|
||||
if (isTransient) {
|
||||
if (jsonIgnore) {
|
||||
if (!hasDefault) {
|
||||
messager.printMessage(
|
||||
ERROR,
|
||||
"No default value for transient property $name",
|
||||
"No default value for transient/ignored property $name",
|
||||
sourceElement
|
||||
)
|
||||
return null
|
||||
@@ -510,16 +514,36 @@ private fun List<AnnotationSpec>?.qualifiers(
|
||||
|
||||
private fun List<AnnotationSpec>?.jsonName(): String? {
|
||||
if (this == null) return null
|
||||
return find { it.typeName == JSON }?.let { annotation ->
|
||||
val mirror = requireNotNull(annotation.tag<AnnotationMirror>()) {
|
||||
"Could not get the annotation mirror from the annotation spec"
|
||||
}
|
||||
mirror.elementValues.entries.single {
|
||||
it.key.simpleName.contentEquals("name")
|
||||
}.value.value as String
|
||||
return filter { it.typeName == JSON }.firstNotNullOfOrNull { annotation ->
|
||||
annotation.jsonName()
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<AnnotationSpec>?.jsonIgnore(): Boolean {
|
||||
if (this == null) return false
|
||||
return filter { it.typeName == JSON }.firstNotNullOfOrNull { annotation ->
|
||||
annotation.jsonIgnore()
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private fun AnnotationSpec.jsonName(): String? {
|
||||
return elementValue<String>("name").takeUnless { it == Json.UNSET_NAME }
|
||||
}
|
||||
|
||||
private fun AnnotationSpec.jsonIgnore(): Boolean {
|
||||
return elementValue<Boolean>("ignore") ?: false
|
||||
}
|
||||
|
||||
private fun <T> AnnotationSpec.elementValue(name: String): T? {
|
||||
val mirror = requireNotNull(tag<AnnotationMirror>()) {
|
||||
"Could not get the annotation mirror from the annotation spec"
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return mirror.elementValues.entries.firstOrNull {
|
||||
it.key.simpleName.contentEquals(name)
|
||||
}?.value?.value as? T
|
||||
}
|
||||
|
||||
private fun String.escapeDollarSigns(): String {
|
||||
return replace("\$", "\${\'\$\'}")
|
||||
}
|
||||
|
@@ -21,14 +21,12 @@ import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSDeclaration
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.asClassName
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import com.squareup.moshi.kotlin.codegen.api.DelegateKey
|
||||
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
||||
import com.squareup.moshi.kotlin.codegen.api.TargetProperty
|
||||
import com.squareup.moshi.kotlin.codegen.api.rawType
|
||||
|
||||
private val TargetProperty.isTransient get() = propertySpec.annotations.any { it.typeName == Transient::class.asClassName() }
|
||||
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
|
||||
private val TargetProperty.isVisible: Boolean
|
||||
get() {
|
||||
@@ -47,10 +45,10 @@ internal fun TargetProperty.generator(
|
||||
originalType: KSDeclaration,
|
||||
instantiateAnnotations: Boolean
|
||||
): PropertyGenerator? {
|
||||
if (isTransient) {
|
||||
if (jsonIgnore) {
|
||||
if (!hasDefault) {
|
||||
logger.error(
|
||||
"No default value for transient property $name",
|
||||
"No default value for transient/ignored property $name",
|
||||
originalType
|
||||
)
|
||||
return null
|
||||
|
@@ -213,7 +213,11 @@ private fun KSAnnotated?.qualifiers(resolver: Resolver): Set<AnnotationSpec> {
|
||||
}
|
||||
|
||||
private fun KSAnnotated?.jsonName(): String? {
|
||||
return this?.findAnnotationWithType<Json>()?.name
|
||||
return this?.findAnnotationWithType<Json>()?.name?.takeUnless { it == Json.UNSET_NAME }
|
||||
}
|
||||
|
||||
private fun KSAnnotated?.jsonIgnore(): Boolean {
|
||||
return this?.findAnnotationWithType<Json>()?.ignore ?: false
|
||||
}
|
||||
|
||||
private fun declaredProperties(
|
||||
@@ -223,7 +227,6 @@ private fun declaredProperties(
|
||||
resolver: Resolver,
|
||||
typeParameterResolver: TypeParameterResolver,
|
||||
): Map<String, TargetProperty> {
|
||||
|
||||
val result = mutableMapOf<String, TargetProperty>()
|
||||
for (property in classDecl.getDeclaredProperties()) {
|
||||
val initialType = property.type.resolve()
|
||||
@@ -235,12 +238,14 @@ private fun declaredProperties(
|
||||
val propertySpec = property.toPropertySpec(resolver, resolvedType, typeParameterResolver)
|
||||
val name = propertySpec.name
|
||||
val parameter = constructor.parameters[name]
|
||||
val isTransient = property.isAnnotationPresent(Transient::class)
|
||||
result[name] = TargetProperty(
|
||||
propertySpec = propertySpec,
|
||||
parameter = parameter,
|
||||
visibility = property.getVisibility().toKModifier() ?: KModifier.PUBLIC,
|
||||
jsonName = parameter?.jsonName ?: property.jsonName()
|
||||
?: name.escapeDollarSigns()
|
||||
?: name.escapeDollarSigns(),
|
||||
jsonIgnore = isTransient || parameter?.jsonIgnore == true || property.jsonIgnore()
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -334,7 +334,27 @@ class JsonClassCodegenProcessorTest(
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: No default value for transient property a"
|
||||
"error: No default value for transient/ignored property a"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requiredIgnoredConstructorParameterFails() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class RequiredIgnoredConstructorParameter(@Json(ignore = true) var a: Int)
|
||||
"""
|
||||
)
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: No default value for transient/ignored property a"
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -357,7 +357,28 @@ class JsonClassSymbolProcessorTest(
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"No default value for transient property a"
|
||||
"No default value for transient/ignored property a"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requiredIgnoredConstructorParameterFails() {
|
||||
val result = compile(
|
||||
kotlin(
|
||||
"source.kt",
|
||||
"""
|
||||
package test
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class RequiredTransientConstructorParameter(@Json(ignore = true) var a: Int)
|
||||
"""
|
||||
)
|
||||
)
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"No default value for transient/ignored property a"
|
||||
)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user