mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49: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:
@@ -68,7 +68,10 @@ public final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
|
||||
for (int i = 0; i < constants.length; i++) {
|
||||
String constantName = constants[i].name();
|
||||
Json annotation = enumType.getField(constantName).getAnnotation(Json.class);
|
||||
String name = annotation != null ? annotation.name() : constantName;
|
||||
String name =
|
||||
annotation != null && !Json.UNSET_NAME.equals(annotation.name())
|
||||
? annotation.name()
|
||||
: constantName;
|
||||
nameStrings[i] = name;
|
||||
}
|
||||
options = JsonReader.Options.of(nameStrings);
|
||||
|
@@ -79,7 +79,10 @@ final class FallbackEnum {
|
||||
for (int i = 0; i < constants.length; i++) {
|
||||
T constant = constants[i];
|
||||
Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class);
|
||||
String name = annotation != null ? annotation.name() : constant.name();
|
||||
String name =
|
||||
annotation != null && !Json.UNSET_NAME.equals(annotation.name())
|
||||
? annotation.name()
|
||||
: constant.name();
|
||||
nameStrings[i] = name;
|
||||
}
|
||||
options = JsonReader.Options.of(nameStrings);
|
||||
|
@@ -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,14 +514,34 @@ 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>()) {
|
||||
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"
|
||||
}
|
||||
mirror.elementValues.entries.single {
|
||||
it.key.simpleName.contentEquals("name")
|
||||
}.value.value as String
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return mirror.elementValues.entries.firstOrNull {
|
||||
it.key.simpleName.contentEquals(name)
|
||||
}?.value?.value as? T
|
||||
}
|
||||
|
||||
private fun String.escapeDollarSigns(): String {
|
||||
|
@@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -57,7 +57,7 @@ private val ABSENT_VALUE = Any()
|
||||
internal class KotlinJsonAdapter<T>(
|
||||
val constructor: KFunction<T>,
|
||||
val allBindings: List<Binding<T, Any?>?>,
|
||||
val nonTransientBindings: List<Binding<T, Any?>>,
|
||||
val nonIgnoredBindings: List<Binding<T, Any?>>,
|
||||
val options: JsonReader.Options
|
||||
) : JsonAdapter<T>() {
|
||||
|
||||
@@ -74,7 +74,7 @@ internal class KotlinJsonAdapter<T>(
|
||||
reader.skipValue()
|
||||
continue
|
||||
}
|
||||
val binding = nonTransientBindings[index]
|
||||
val binding = nonIgnoredBindings[index]
|
||||
|
||||
val propertyIndex = binding.propertyIndex
|
||||
if (values[propertyIndex] !== ABSENT_VALUE) {
|
||||
@@ -235,11 +235,27 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
||||
for (property in rawTypeKotlin.memberProperties) {
|
||||
val parameter = parametersByName[property.name]
|
||||
|
||||
property.isAccessible = true
|
||||
var jsonAnnotation = property.findAnnotation<Json>()
|
||||
val allAnnotations = property.annotations.toMutableList()
|
||||
|
||||
if (parameter != null) {
|
||||
allAnnotations += parameter.annotations
|
||||
if (jsonAnnotation == null) {
|
||||
jsonAnnotation = parameter.findAnnotation()
|
||||
}
|
||||
}
|
||||
|
||||
if (Modifier.isTransient(property.javaField?.modifiers ?: 0)) {
|
||||
require(parameter == null || parameter.isOptional) {
|
||||
"No default value for transient constructor $parameter"
|
||||
}
|
||||
continue
|
||||
} else if (jsonAnnotation?.ignore == true) {
|
||||
require(parameter == null || parameter.isOptional) {
|
||||
"No default value for ignored constructor $parameter"
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
require(parameter == null || parameter.type == property.returnType) {
|
||||
@@ -248,18 +264,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
||||
|
||||
if (property !is KMutableProperty1 && parameter == null) continue
|
||||
|
||||
property.isAccessible = true
|
||||
val allAnnotations = property.annotations.toMutableList()
|
||||
var jsonAnnotation = property.findAnnotation<Json>()
|
||||
|
||||
if (parameter != null) {
|
||||
allAnnotations += parameter.annotations
|
||||
if (jsonAnnotation == null) {
|
||||
jsonAnnotation = parameter.findAnnotation()
|
||||
}
|
||||
}
|
||||
|
||||
val name = jsonAnnotation?.name ?: property.name
|
||||
val name = jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: property.name
|
||||
val propertyType = when (val propertyTypeClassifier = property.returnType.classifier) {
|
||||
is KClass<*> -> {
|
||||
if (propertyTypeClassifier.isValue) {
|
||||
@@ -294,7 +299,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
bindingsByName[property.name] = KotlinJsonAdapter.Binding(
|
||||
name,
|
||||
jsonAnnotation?.name ?: name,
|
||||
jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: name,
|
||||
adapter,
|
||||
property as KProperty1<Any, Any?>,
|
||||
parameter,
|
||||
@@ -317,8 +322,8 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
||||
bindings += bindingByName.value.copy(propertyIndex = index++)
|
||||
}
|
||||
|
||||
val nonTransientBindings = bindings.filterNotNull()
|
||||
val options = JsonReader.Options.of(*nonTransientBindings.map { it.name }.toTypedArray())
|
||||
return KotlinJsonAdapter(constructor, bindings, nonTransientBindings, options).nullSafe()
|
||||
val nonIgnoredBindings = bindings.filterNotNull()
|
||||
val options = JsonReader.Options.of(*nonIgnoredBindings.map { it.name }.toTypedArray())
|
||||
return KotlinJsonAdapter(constructor, bindings, nonIgnoredBindings, options).nullSafe()
|
||||
}
|
||||
}
|
||||
|
@@ -17,3 +17,7 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":moshi"))
|
||||
}
|
||||
|
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.test.extra
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
public abstract class AbstractClassInModuleA {
|
||||
// Transients to ensure processor sees them across module boundaries since @Transient is
|
||||
// SOURCE-only
|
||||
// TODO uncomment these when https://github.com/google/ksp/issues/710 is fixed
|
||||
// @Transient private lateinit var lateinitTransient: String
|
||||
// @Transient private var regularTransient: String = "regularTransient"
|
||||
// Ignored to ensure processor sees them across module boundaries.
|
||||
// @Transient doesn't work for this case because it's source-only and jvm modifiers aren't currently visible in KSP.
|
||||
|
||||
// Note that we target the field because otherwise it is stored on the synthetic holder method for
|
||||
// annotations, which isn't visible from kapt
|
||||
@field:Json(ignore = true) private lateinit var lateinitIgnored: String
|
||||
@field:Json(ignore = true) private var regularIgnored: String = "regularIgnored"
|
||||
}
|
||||
|
@@ -625,6 +625,124 @@ class DualKotlinTest {
|
||||
data class IntersectionTypes<E>(
|
||||
val value: E
|
||||
) where E : Enum<E>, E : IntersectionTypeInterface<E>
|
||||
|
||||
@Test fun transientConstructorParameter() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter<TransientConstructorParameter>()
|
||||
|
||||
val encoded = TransientConstructorParameter(3, 5)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
}
|
||||
|
||||
class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1)
|
||||
|
||||
@Test fun multipleTransientConstructorParameters() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java)
|
||||
|
||||
val encoded = MultipleTransientConstructorParameters(3, 5, 7)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
assertThat(decoded.c).isEqualTo(-1)
|
||||
}
|
||||
|
||||
class MultipleTransientConstructorParameters(@Transient var a: Int = -1, var b: Int = -1, @Transient var c: Int = -1)
|
||||
|
||||
@Test fun transientProperty() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter<TransientProperty>()
|
||||
|
||||
val encoded = TransientProperty()
|
||||
encoded.a = 3
|
||||
encoded.setB(4)
|
||||
encoded.c = 5
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"c":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":5,"c":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.getB()).isEqualTo(-1)
|
||||
assertThat(decoded.c).isEqualTo(6)
|
||||
}
|
||||
|
||||
class TransientProperty {
|
||||
@Transient var a: Int = -1
|
||||
@Transient private var b: Int = -1
|
||||
var c: Int = -1
|
||||
|
||||
fun getB() = b
|
||||
|
||||
fun setB(b: Int) {
|
||||
this.b = b
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun ignoredConstructorParameter() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter<IgnoredConstructorParameter>()
|
||||
|
||||
val encoded = IgnoredConstructorParameter(3, 5)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
}
|
||||
|
||||
class IgnoredConstructorParameter(@Json(ignore = true) var a: Int = -1, var b: Int = -1)
|
||||
|
||||
@Test fun multipleIgnoredConstructorParameters() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter(MultipleIgnoredConstructorParameters::class.java)
|
||||
|
||||
val encoded = MultipleIgnoredConstructorParameters(3, 5, 7)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
assertThat(decoded.c).isEqualTo(-1)
|
||||
}
|
||||
|
||||
class MultipleIgnoredConstructorParameters(
|
||||
@Json(ignore = true) var a: Int = -1,
|
||||
var b: Int = -1,
|
||||
@Json(ignore = true) var c: Int = -1
|
||||
)
|
||||
|
||||
@Test fun ignoredProperty() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter<IgnoredProperty>()
|
||||
|
||||
val encoded = IgnoredProperty()
|
||||
encoded.a = 3
|
||||
encoded.setB(4)
|
||||
encoded.c = 5
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"c":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":5,"c":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.getB()).isEqualTo(-1)
|
||||
assertThat(decoded.c).isEqualTo(6)
|
||||
}
|
||||
|
||||
class IgnoredProperty {
|
||||
@Json(ignore = true) var a: Int = -1
|
||||
@Json(ignore = true) private var b: Int = -1
|
||||
var c: Int = -1
|
||||
|
||||
fun getB() = b
|
||||
|
||||
fun setB(b: Int) {
|
||||
this.b = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias TypeAlias = Int
|
||||
|
@@ -278,35 +278,6 @@ class KotlinJsonAdapterTest {
|
||||
var b: Int = -1
|
||||
}
|
||||
|
||||
@Test fun transientConstructorParameter() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter<TransientConstructorParameter>()
|
||||
|
||||
val encoded = TransientConstructorParameter(3, 5)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
}
|
||||
|
||||
class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1)
|
||||
|
||||
@Test fun multipleTransientConstructorParameters() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java)
|
||||
|
||||
val encoded = MultipleTransientConstructorParameters(3, 5, 7)
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.b).isEqualTo(6)
|
||||
assertThat(decoded.c).isEqualTo(-1)
|
||||
}
|
||||
|
||||
class MultipleTransientConstructorParameters(@Transient var a: Int = -1, var b: Int = -1, @Transient var c: Int = -1)
|
||||
|
||||
@Test fun requiredTransientConstructorParameterFails() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
try {
|
||||
@@ -323,34 +294,22 @@ class KotlinJsonAdapterTest {
|
||||
|
||||
class RequiredTransientConstructorParameter(@Transient var a: Int)
|
||||
|
||||
@Test fun transientProperty() {
|
||||
@Test fun requiredIgnoredConstructorParameterFails() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter<TransientProperty>()
|
||||
|
||||
val encoded = TransientProperty()
|
||||
encoded.a = 3
|
||||
encoded.setB(4)
|
||||
encoded.c = 5
|
||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"c":5}""")
|
||||
|
||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":5,"c":6}""")!!
|
||||
assertThat(decoded.a).isEqualTo(-1)
|
||||
assertThat(decoded.getB()).isEqualTo(-1)
|
||||
assertThat(decoded.c).isEqualTo(6)
|
||||
}
|
||||
|
||||
class TransientProperty {
|
||||
@Transient var a: Int = -1
|
||||
@Transient private var b: Int = -1
|
||||
var c: Int = -1
|
||||
|
||||
fun getB() = b
|
||||
|
||||
fun setB(b: Int) {
|
||||
this.b = b
|
||||
try {
|
||||
moshi.adapter<RequiredIgnoredConstructorParameter>()
|
||||
fail()
|
||||
} catch (expected: IllegalArgumentException) {
|
||||
assertThat(expected).hasMessageThat().isEqualTo(
|
||||
"No default value for ignored constructor parameter #0 " +
|
||||
"a of fun <init>(kotlin.Int): " +
|
||||
"com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest.RequiredIgnoredConstructorParameter"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class RequiredIgnoredConstructorParameter(@Json(ignore = true) var a: Int)
|
||||
|
||||
@Test fun constructorParametersAndPropertiesWithSameNamesMustHaveSameTypes() {
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
try {
|
||||
|
@@ -134,6 +134,8 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||
boolean platformType = Util.isPlatformType(rawType);
|
||||
for (Field field : rawType.getDeclaredFields()) {
|
||||
if (!includeField(platformType, field.getModifiers())) continue;
|
||||
Json jsonAnnotation = field.getAnnotation(Json.class);
|
||||
if (jsonAnnotation != null && jsonAnnotation.ignore()) continue;
|
||||
|
||||
// Look up a type adapter for this type.
|
||||
Type fieldType = resolve(type, rawType, field.getGenericType());
|
||||
@@ -145,8 +147,10 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||
field.setAccessible(true);
|
||||
|
||||
// Store it using the field's name. If there was already a field with this name, fail!
|
||||
Json jsonAnnotation = field.getAnnotation(Json.class);
|
||||
String name = jsonAnnotation != null ? jsonAnnotation.name() : fieldName;
|
||||
String name =
|
||||
jsonAnnotation != null && !Json.UNSET_NAME.equals(jsonAnnotation.name())
|
||||
? jsonAnnotation.name()
|
||||
: fieldName;
|
||||
FieldBinding<Object> fieldBinding = new FieldBinding<>(name, field, adapter);
|
||||
FieldBinding<?> replaced = fieldBindings.put(name, fieldBinding);
|
||||
if (replaced != null) {
|
||||
|
@@ -29,8 +29,9 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* <ul>
|
||||
* <li><strong>Java class fields</strong>
|
||||
* <li><strong>Kotlin properties</strong> for use with {@code moshi-kotlin}. This includes both
|
||||
* properties declared in the constructor and properties declared as members.
|
||||
* <li><strong>Kotlin properties</strong> for use with {@code moshi-kotlin} or {@code
|
||||
* moshi-kotlin-codegen}. This includes both properties declared in the constructor and
|
||||
* properties declared as members.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Users of the <a href="https://github.com/rharter/auto-value-moshi">AutoValue: Moshi
|
||||
@@ -39,5 +40,17 @@ import java.lang.annotation.Target;
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
public @interface Json {
|
||||
String name();
|
||||
/** The default value of {@link #name()}. Should only be used to check if it's been set. */
|
||||
String UNSET_NAME = "\u0000";
|
||||
|
||||
/** The name of the field when encoded as JSON. */
|
||||
String name() default UNSET_NAME;
|
||||
|
||||
/**
|
||||
* If true, this field/property will be ignored. This is semantically similar to use of {@code
|
||||
* transient} on the JVM.
|
||||
*
|
||||
* <p><strong>Note:</strong> this has no effect in enums or record classes.
|
||||
*/
|
||||
boolean ignore() default false;
|
||||
}
|
||||
|
@@ -275,7 +275,10 @@ final class StandardJsonAdapters {
|
||||
for (int i = 0; i < constants.length; i++) {
|
||||
T constant = constants[i];
|
||||
Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class);
|
||||
String name = annotation != null ? annotation.name() : constant.name();
|
||||
String name =
|
||||
annotation != null && !Json.UNSET_NAME.equals(annotation.name())
|
||||
? annotation.name()
|
||||
: constant.name();
|
||||
nameStrings[i] = name;
|
||||
}
|
||||
options = JsonReader.Options.of(nameStrings);
|
||||
|
@@ -91,7 +91,10 @@ final class RecordJsonAdapter<T> extends JsonAdapter<T> {
|
||||
Set<Annotation> qualifiers = null;
|
||||
for (var annotation : component.getDeclaredAnnotations()) {
|
||||
if (annotation instanceof Json jsonAnnotation) {
|
||||
var annotationName = jsonAnnotation.name();
|
||||
if (!Json.UNSET_NAME.equals(annotationName)) {
|
||||
jsonName = jsonAnnotation.name();
|
||||
}
|
||||
} else {
|
||||
if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) {
|
||||
if (qualifiers == null) {
|
||||
|
@@ -153,6 +153,26 @@ public final class ClassJsonAdapterTest {
|
||||
assertThat(fromJson.b).isEqualTo(12);
|
||||
}
|
||||
|
||||
static class IgnoredFields {
|
||||
@Json(ignore = true)
|
||||
int a;
|
||||
|
||||
int b;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoredFieldsOmitted() throws Exception {
|
||||
IgnoredFields value = new IgnoredFields();
|
||||
value.a = 11;
|
||||
value.b = 12;
|
||||
String toJson = toJson(IgnoredFields.class, value);
|
||||
assertThat(toJson).isEqualTo("{\"b\":12}");
|
||||
|
||||
IgnoredFields fromJson = fromJson(IgnoredFields.class, "{\"a\":13,\"b\":12}");
|
||||
assertThat(fromJson.a).isEqualTo(0); // Not assigned.
|
||||
assertThat(fromJson.b).isEqualTo(12);
|
||||
}
|
||||
|
||||
static class BaseA {
|
||||
int a;
|
||||
}
|
||||
|
Reference in New Issue
Block a user