mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Support properties that don't have a backing field.
Currently our main loop to gather PropertyGenerators goes over the backing fields. This needs to change to iterate over the properties themselves. That leads to a lot of churn. The net result is slightly more compatibility with the reflective adapter.
This commit is contained in:
@@ -28,6 +28,7 @@ import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
|
|||||||
import me.eugeniomarletti.kotlin.metadata.classKind
|
import me.eugeniomarletti.kotlin.metadata.classKind
|
||||||
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
|
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
|
||||||
import me.eugeniomarletti.kotlin.metadata.getPropertyOrNull
|
import me.eugeniomarletti.kotlin.metadata.getPropertyOrNull
|
||||||
|
import me.eugeniomarletti.kotlin.metadata.hasSetter
|
||||||
import me.eugeniomarletti.kotlin.metadata.isDataClass
|
import me.eugeniomarletti.kotlin.metadata.isDataClass
|
||||||
import me.eugeniomarletti.kotlin.metadata.isInnerClass
|
import me.eugeniomarletti.kotlin.metadata.isInnerClass
|
||||||
import me.eugeniomarletti.kotlin.metadata.isPrimary
|
import me.eugeniomarletti.kotlin.metadata.isPrimary
|
||||||
@@ -41,6 +42,7 @@ import org.jetbrains.kotlin.serialization.ProtoBuf.Modality
|
|||||||
import org.jetbrains.kotlin.serialization.ProtoBuf.Property
|
import org.jetbrains.kotlin.serialization.ProtoBuf.Property
|
||||||
import org.jetbrains.kotlin.serialization.ProtoBuf.ValueParameter
|
import org.jetbrains.kotlin.serialization.ProtoBuf.ValueParameter
|
||||||
import org.jetbrains.kotlin.serialization.ProtoBuf.Visibility
|
import org.jetbrains.kotlin.serialization.ProtoBuf.Visibility
|
||||||
|
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.annotation.processing.ProcessingEnvironment
|
import javax.annotation.processing.ProcessingEnvironment
|
||||||
import javax.annotation.processing.Processor
|
import javax.annotation.processing.Processor
|
||||||
@@ -118,12 +120,12 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processElement(element: Element): AdapterGenerator? {
|
private fun processElement(model: Element): AdapterGenerator? {
|
||||||
val metadata = element.kotlinMetadata
|
val metadata = model.kotlinMetadata
|
||||||
|
|
||||||
if (metadata !is KotlinClassMetadata) {
|
if (metadata !is KotlinClassMetadata) {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
ERROR, "@JsonClass can't be applied to $model: must be a Kotlin class", model)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,27 +135,28 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
when {
|
when {
|
||||||
classProto.classKind != Class.Kind.CLASS -> {
|
classProto.classKind != Class.Kind.CLASS -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
ERROR, "@JsonClass can't be applied to $model: must be a Kotlin class", model)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
classProto.isInnerClass -> {
|
classProto.isInnerClass -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element)
|
ERROR, "@JsonClass can't be applied to $model: must not be an inner class",
|
||||||
|
model)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
classProto.modality == Modality.ABSTRACT -> {
|
classProto.modality == Modality.ABSTRACT -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be abstract", element)
|
ERROR, "@JsonClass can't be applied to $model: must not be abstract", model)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
classProto.visibility == Visibility.LOCAL -> {
|
classProto.visibility == Visibility.LOCAL -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be local", element)
|
ERROR, "@JsonClass can't be applied to $model: must not be local", model)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val typeName = element.asType().asTypeName()
|
val typeName = model.asType().asTypeName()
|
||||||
val className = when (typeName) {
|
val className = when (typeName) {
|
||||||
is ClassName -> typeName
|
is ClassName -> typeName
|
||||||
is ParameterizedTypeName -> typeName.rawType
|
is ParameterizedTypeName -> typeName.rawType
|
||||||
@@ -185,62 +188,106 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
nameResolver.getString(it.name)
|
nameResolver.getString(it.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The compiler might emit methods just so it has a place to put annotations. Find these.
|
val annotationHolders = mutableMapOf<Property, ExecutableElement>()
|
||||||
val annotatedElements = mutableMapOf<Property, ExecutableElement>()
|
val fields = mutableMapOf<String, VariableElement>()
|
||||||
for (enclosedElement in element.enclosedElements) {
|
val setters = mutableMapOf<String, ExecutableElement>()
|
||||||
if (enclosedElement !is ExecutableElement) continue
|
val getters = mutableMapOf<String, ExecutableElement>()
|
||||||
val property = classData.getPropertyOrNull(enclosedElement) ?: continue
|
for (element in model.enclosedElements) {
|
||||||
annotatedElements[property] = enclosedElement
|
if (element is VariableElement) {
|
||||||
|
fields[element.name] = element
|
||||||
|
} else if (element is ExecutableElement) {
|
||||||
|
when {
|
||||||
|
element.name.startsWith("get") -> {
|
||||||
|
getters[element.name.substring("get".length).decapitalizeAsciiOnly()] = element
|
||||||
|
}
|
||||||
|
element.name.startsWith("is") -> {
|
||||||
|
getters[element.name.substring("is".length).decapitalizeAsciiOnly()] = element
|
||||||
|
}
|
||||||
|
element.name.startsWith("set") -> {
|
||||||
|
setters[element.name.substring("set".length).decapitalizeAsciiOnly()] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val property = classData.getPropertyOrNull(element)
|
||||||
|
if (property != null) {
|
||||||
|
annotationHolders[property] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val propertyGenerators = mutableListOf<PropertyGenerator>()
|
val propertiesByName = mutableMapOf<String, PropertyGenerator>()
|
||||||
for (enclosedElement in element.enclosedElements) {
|
for (property in properties.values) {
|
||||||
if (enclosedElement !is VariableElement) continue
|
val name = nameResolver.getString(property.name)
|
||||||
|
|
||||||
|
val fieldElement = fields[name]
|
||||||
|
val setterElement = setters[name]
|
||||||
|
val getterElement = getters[name]
|
||||||
|
val element = fieldElement ?: setterElement ?: getterElement!!
|
||||||
|
|
||||||
val name = enclosedElement.simpleName.toString()
|
|
||||||
val property = properties[name] ?: continue
|
|
||||||
val parameter = parameters[name]
|
val parameter = parameters[name]
|
||||||
|
var parameterIndex: Int = -1
|
||||||
val parameterElement = if (parameter != null) {
|
var parameterElement: VariableElement? = null
|
||||||
val parameterIndex = protoConstructor.valueParameterList.indexOf(parameter)
|
if (parameter != null) {
|
||||||
constructor.parameters[parameterIndex]
|
parameterIndex = protoConstructor.valueParameterList.indexOf(parameter)
|
||||||
} else {
|
parameterElement = constructor.parameters[parameterIndex]
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val annotatedElement = annotatedElements[property]
|
val annotationHolder = annotationHolders[property]
|
||||||
|
|
||||||
if (property.visibility != Visibility.INTERNAL
|
if (property.visibility != Visibility.INTERNAL
|
||||||
&& property.visibility != Visibility.PROTECTED
|
&& property.visibility != Visibility.PROTECTED
|
||||||
&& property.visibility != Visibility.PUBLIC) {
|
&& property.visibility != Visibility.PUBLIC) {
|
||||||
messager.printMessage(ERROR, "property $name is not visible", enclosedElement)
|
messager.printMessage(ERROR, "property $name is not visible", element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasDefault = parameter?.declaresDefaultValue ?: true
|
val hasDefault = parameter?.declaresDefaultValue ?: true
|
||||||
|
|
||||||
if (Modifier.TRANSIENT in enclosedElement.modifiers) {
|
if (Modifier.TRANSIENT in element.modifiers) {
|
||||||
if (!hasDefault) {
|
if (!hasDefault) {
|
||||||
throw IllegalArgumentException("No default value for transient property $name")
|
messager.printMessage(
|
||||||
|
ERROR, "No default value for transient property $name", element)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
continue
|
continue // This property is transient and has a default value. Ignore it.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!property.hasSetter && parameter == null) {
|
||||||
|
continue // This property is not settable. Ignore it.
|
||||||
}
|
}
|
||||||
|
|
||||||
val delegateKey = DelegateKey(
|
val delegateKey = DelegateKey(
|
||||||
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter, true),
|
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter, true),
|
||||||
jsonQualifiers(enclosedElement, annotatedElement, parameterElement))
|
jsonQualifiers(element, annotationHolder, parameterElement))
|
||||||
|
|
||||||
propertyGenerators += PropertyGenerator(
|
propertiesByName[name] = PropertyGenerator(
|
||||||
delegateKey,
|
delegateKey,
|
||||||
name,
|
name,
|
||||||
jsonName(name, enclosedElement, annotatedElement, parameterElement),
|
jsonName(name, element, annotationHolder, parameterElement),
|
||||||
parameter != null,
|
parameterIndex,
|
||||||
hasDefault,
|
hasDefault,
|
||||||
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter))
|
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (parameterElement in constructor.parameters) {
|
||||||
|
val name = parameterElement.name
|
||||||
|
val valueParameter = parameters[name]!!
|
||||||
|
if (properties[name] == null && !valueParameter.declaresDefaultValue) {
|
||||||
|
messager.printMessage(
|
||||||
|
ERROR, "No property for required constructor parameter $name", parameterElement)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sort properties so that those with constructor parameters come first.
|
// Sort properties so that those with constructor parameters come first.
|
||||||
propertyGenerators.sortBy { if (it.hasConstructorParameter) -1 else 1 }
|
val propertyGenerators = propertiesByName.values.toMutableList()
|
||||||
|
propertyGenerators.sortBy {
|
||||||
|
if (it.hasConstructorParameter) {
|
||||||
|
it.parameterIndex
|
||||||
|
} else {
|
||||||
|
Integer.MAX_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val genericTypeNames = classProto.typeParameterList
|
val genericTypeNames = classProto.typeParameterList
|
||||||
.map {
|
.map {
|
||||||
@@ -268,9 +315,9 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AdapterGenerator(
|
return AdapterGenerator(
|
||||||
className,
|
className = className,
|
||||||
propertyList = propertyGenerators,
|
propertyList = propertyGenerators,
|
||||||
originalElement = element,
|
originalElement = model,
|
||||||
hasCompanionObject = hasCompanionObject,
|
hasCompanionObject = hasCompanionObject,
|
||||||
visibility = classProto.visibility!!,
|
visibility = classProto.visibility!!,
|
||||||
genericTypeNames = genericTypeNames,
|
genericTypeNames = genericTypeNames,
|
||||||
@@ -280,18 +327,18 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
|
|
||||||
/** Returns the JsonQualifiers on the field and parameter of a property. */
|
/** Returns the JsonQualifiers on the field and parameter of a property. */
|
||||||
private fun jsonQualifiers(
|
private fun jsonQualifiers(
|
||||||
field: VariableElement,
|
element: Element,
|
||||||
method: ExecutableElement?,
|
annotationHolder: ExecutableElement?,
|
||||||
parameter: VariableElement?
|
parameter: VariableElement?
|
||||||
): Set<AnnotationMirror> {
|
): Set<AnnotationMirror> {
|
||||||
val fieldQualifiers = field.qualifiers
|
val elementQualifiers = element.qualifiers
|
||||||
val methodQualifiers = method.qualifiers
|
val annotationHolderQualifiers = annotationHolder.qualifiers
|
||||||
val parameterQualifiers = parameter.qualifiers
|
val parameterQualifiers = parameter.qualifiers
|
||||||
|
|
||||||
// TODO(jwilson): union the qualifiers somehow?
|
// TODO(jwilson): union the qualifiers somehow?
|
||||||
return when {
|
return when {
|
||||||
fieldQualifiers.isNotEmpty() -> fieldQualifiers
|
elementQualifiers.isNotEmpty() -> elementQualifiers
|
||||||
methodQualifiers.isNotEmpty() -> methodQualifiers
|
annotationHolderQualifiers.isNotEmpty() -> annotationHolderQualifiers
|
||||||
parameterQualifiers.isNotEmpty() -> parameterQualifiers
|
parameterQualifiers.isNotEmpty() -> parameterQualifiers
|
||||||
else -> setOf()
|
else -> setOf()
|
||||||
}
|
}
|
||||||
@@ -300,28 +347,22 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
/** Returns the @Json name of a property, or `propertyName` if none is provided. */
|
/** Returns the @Json name of a property, or `propertyName` if none is provided. */
|
||||||
private fun jsonName(
|
private fun jsonName(
|
||||||
propertyName: String,
|
propertyName: String,
|
||||||
field: VariableElement,
|
element: Element,
|
||||||
method: ExecutableElement?,
|
annotationHolder: ExecutableElement?,
|
||||||
parameter: VariableElement?
|
parameter: VariableElement?
|
||||||
): String {
|
): String {
|
||||||
val fieldJsonName = field.jsonName
|
val fieldJsonName = element.jsonName
|
||||||
val methodJsonName = method.jsonName
|
val annotationHolderJsonName = annotationHolder.jsonName
|
||||||
val parameterJsonName = parameter.jsonName
|
val parameterJsonName = parameter.jsonName
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
fieldJsonName != null -> fieldJsonName
|
fieldJsonName != null -> fieldJsonName
|
||||||
methodJsonName != null -> methodJsonName
|
annotationHolderJsonName != null -> annotationHolderJsonName
|
||||||
parameterJsonName != null -> parameterJsonName
|
parameterJsonName != null -> parameterJsonName
|
||||||
else -> propertyName
|
else -> propertyName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun errorMustBeKotlinClass(element: Element) {
|
|
||||||
messager.printMessage(ERROR,
|
|
||||||
"@${JsonClass::class.java.simpleName} can't be applied to $element: must be a Kotlin class",
|
|
||||||
element)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) {
|
private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) {
|
||||||
val fileSpec = generateFile(generatedOption)
|
val fileSpec = generateFile(generatedOption)
|
||||||
val adapterName = fileSpec.members.filterIsInstance<TypeSpec>().first().name!!
|
val adapterName = fileSpec.members.filterIsInstance<TypeSpec>().first().name!!
|
||||||
@@ -348,3 +389,8 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
return getAnnotation(Json::class.java)?.name
|
return getAnnotation(Json::class.java)?.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val Element.name: String
|
||||||
|
get() {
|
||||||
|
return simpleName.toString()
|
||||||
|
}
|
@@ -25,7 +25,7 @@ internal class PropertyGenerator(
|
|||||||
val delegateKey: DelegateKey,
|
val delegateKey: DelegateKey,
|
||||||
val name: String,
|
val name: String,
|
||||||
val serializedName: String,
|
val serializedName: String,
|
||||||
val hasConstructorParameter: Boolean,
|
val parameterIndex: Int,
|
||||||
val hasDefault: Boolean,
|
val hasDefault: Boolean,
|
||||||
val typeName: TypeName
|
val typeName: TypeName
|
||||||
) {
|
) {
|
||||||
@@ -35,6 +35,9 @@ internal class PropertyGenerator(
|
|||||||
val isRequired
|
val isRequired
|
||||||
get() = !delegateKey.nullable && !hasDefault
|
get() = !delegateKey.nullable && !hasDefault
|
||||||
|
|
||||||
|
val hasConstructorParameter
|
||||||
|
get() = parameterIndex != -1
|
||||||
|
|
||||||
/** We prefer to use 'null' to mean absent, but for some properties those are distinct. */
|
/** We prefer to use 'null' to mean absent, but for some properties those are distinct. */
|
||||||
val differentiateAbsentFromNull
|
val differentiateAbsentFromNull
|
||||||
get() = delegateKey.nullable && hasDefault
|
get() = delegateKey.nullable && hasDefault
|
||||||
|
@@ -154,4 +154,38 @@ class CompilerTest {
|
|||||||
assertThat(result.systemErr).contains(
|
assertThat(result.systemErr).contains(
|
||||||
"error: @JsonClass can't be applied to expression\$annotations(): must be a Kotlin class")
|
"error: @JsonClass can't be applied to expression\$annotations(): must be a Kotlin class")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun requiredTransientConstructorParameterFails() {
|
||||||
|
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||||
|
call.inheritClasspath = true
|
||||||
|
call.addService(Processor::class, JsonClassCodeGenProcessor::class)
|
||||||
|
call.addKt("source.kt", """
|
||||||
|
|import com.squareup.moshi.JsonClass
|
||||||
|
|
|
||||||
|
|@JsonClass(generateAdapter = true)
|
||||||
|
|class RequiredTransientConstructorParameter(@Transient var a: Int)
|
||||||
|
|""".trimMargin())
|
||||||
|
|
||||||
|
val result = call.execute()
|
||||||
|
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||||
|
assertThat(result.systemErr).contains(
|
||||||
|
"error: No default value for transient property a")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun nonPropertyConstructorParameter() {
|
||||||
|
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||||
|
call.inheritClasspath = true
|
||||||
|
call.addService(Processor::class, JsonClassCodeGenProcessor::class)
|
||||||
|
call.addKt("source.kt", """
|
||||||
|
|import com.squareup.moshi.JsonClass
|
||||||
|
|
|
||||||
|
|@JsonClass(generateAdapter = true)
|
||||||
|
|class NonPropertyConstructorParameter(a: Int, val b: Int)
|
||||||
|
|""".trimMargin())
|
||||||
|
|
||||||
|
val result = call.execute()
|
||||||
|
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||||
|
assertThat(result.systemErr).contains(
|
||||||
|
"error: No property for required constructor parameter a")
|
||||||
|
}
|
||||||
}
|
}
|
@@ -20,6 +20,7 @@ import org.intellij.lang.annotations.Language
|
|||||||
import org.junit.Assert.fail
|
import org.junit.Assert.fail
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.SimpleTimeZone
|
||||||
|
|
||||||
class GeneratedAdaptersTest {
|
class GeneratedAdaptersTest {
|
||||||
|
|
||||||
@@ -81,10 +82,11 @@ class GeneratedAdaptersTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class DefaultValues(val foo: String,
|
data class DefaultValues(
|
||||||
val bar: String = "",
|
val foo: String,
|
||||||
val nullableBar: String? = null,
|
val bar: String = "",
|
||||||
val bazList: List<String> = emptyList())
|
val nullableBar: String? = null,
|
||||||
|
val bazList: List<String> = emptyList())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nullableArray() {
|
fun nullableArray() {
|
||||||
@@ -626,6 +628,88 @@ class GeneratedAdaptersTest {
|
|||||||
var v26: Int, var v27: Int, var v28: Int, var v29: Int, var v30: Int,
|
var v26: Int, var v27: Int, var v28: Int, var v29: Int, var v30: Int,
|
||||||
var v31: Int, var v32: Int, var v33: Int)
|
var v31: Int, var v32: Int, var v33: Int)
|
||||||
|
|
||||||
|
@Test fun extendsPlatformClassWithPrivateField() {
|
||||||
|
val moshi = Moshi.Builder().build()
|
||||||
|
val jsonAdapter = moshi.adapter(ExtendsPlatformClassWithPrivateField::class.java)
|
||||||
|
|
||||||
|
val encoded = ExtendsPlatformClassWithPrivateField(3)
|
||||||
|
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3}""")
|
||||||
|
|
||||||
|
val decoded = jsonAdapter.fromJson("""{"a":4,"id":"B"}""")!!
|
||||||
|
assertThat(decoded.a).isEqualTo(4)
|
||||||
|
assertThat(decoded.id).isEqualTo("C")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal class ExtendsPlatformClassWithPrivateField(var a: Int) : SimpleTimeZone(0, "C")
|
||||||
|
|
||||||
|
@Test fun unsettablePropertyIgnored() {
|
||||||
|
val moshi = Moshi.Builder().build()
|
||||||
|
val jsonAdapter = moshi.adapter(UnsettableProperty::class.java)
|
||||||
|
|
||||||
|
val encoded = UnsettableProperty()
|
||||||
|
encoded.b = 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class UnsettableProperty {
|
||||||
|
val a: Int = -1
|
||||||
|
var b: Int = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun getterOnlyNoBackingField() {
|
||||||
|
val moshi = Moshi.Builder().build()
|
||||||
|
val jsonAdapter = moshi.adapter(GetterOnly::class.java)
|
||||||
|
|
||||||
|
val encoded = GetterOnly(3, 5)
|
||||||
|
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
|
||||||
|
|
||||||
|
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
||||||
|
assertThat(decoded.a).isEqualTo(4)
|
||||||
|
assertThat(decoded.b).isEqualTo(6)
|
||||||
|
assertThat(decoded.total).isEqualTo(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class GetterOnly(var a: Int, var b: Int) {
|
||||||
|
val total : Int
|
||||||
|
get() = a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun getterAndSetterNoBackingField() {
|
||||||
|
val moshi = Moshi.Builder().build()
|
||||||
|
val jsonAdapter = moshi.adapter(GetterAndSetter::class.java)
|
||||||
|
|
||||||
|
val encoded = GetterAndSetter(3, 5)
|
||||||
|
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5,"total":8}""")
|
||||||
|
|
||||||
|
// Whether b is 6 or 7 is an implementation detail. Currently we call constructors then setters.
|
||||||
|
val decoded1 = jsonAdapter.fromJson("""{"a":4,"b":6,"total":11}""")!!
|
||||||
|
assertThat(decoded1.a).isEqualTo(4)
|
||||||
|
assertThat(decoded1.b).isEqualTo(7)
|
||||||
|
assertThat(decoded1.total).isEqualTo(11)
|
||||||
|
|
||||||
|
// Whether b is 6 or 7 is an implementation detail. Currently we call constructors then setters.
|
||||||
|
val decoded2 = jsonAdapter.fromJson("""{"a":4,"total":11,"b":6}""")!!
|
||||||
|
assertThat(decoded2.a).isEqualTo(4)
|
||||||
|
assertThat(decoded2.b).isEqualTo(7)
|
||||||
|
assertThat(decoded2.total).isEqualTo(11)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class GetterAndSetter(var a: Int, var b: Int) {
|
||||||
|
var total : Int
|
||||||
|
get() = a + b
|
||||||
|
set(value) {
|
||||||
|
b = value - a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@JsonQualifier
|
@JsonQualifier
|
||||||
annotation class Uppercase
|
annotation class Uppercase
|
||||||
|
@@ -20,9 +20,6 @@ import org.junit.Assert.fail
|
|||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.Locale
|
|
||||||
import java.util.SimpleTimeZone
|
|
||||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
|
||||||
|
|
||||||
class KotlinCodeGenTest {
|
class KotlinCodeGenTest {
|
||||||
@Ignore @Test fun duplicatedValue() {
|
@Ignore @Test fun duplicatedValue() {
|
||||||
@@ -53,20 +50,6 @@ class KotlinCodeGenTest {
|
|||||||
|
|
||||||
class RepeatedValue(var a: Int, var b: Int?)
|
class RepeatedValue(var a: Int, var b: Int?)
|
||||||
|
|
||||||
@Ignore @Test fun requiredTransientConstructorParameterFails() {
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
try {
|
|
||||||
moshi.adapter(RequiredTransientConstructorParameter::class.java)
|
|
||||||
fail()
|
|
||||||
} catch (expected: IllegalArgumentException) {
|
|
||||||
assertThat(expected).hasMessage("No default value for transient constructor parameter #0 " +
|
|
||||||
"a of fun <init>(kotlin.Int): " +
|
|
||||||
"com.squareup.moshi.KotlinJsonAdapterTest.RequiredTransientConstructorParameter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RequiredTransientConstructorParameter(@Transient var a: Int)
|
|
||||||
|
|
||||||
@Ignore @Test fun supertypeConstructorParameters() {
|
@Ignore @Test fun supertypeConstructorParameters() {
|
||||||
val moshi = Moshi.Builder().build()
|
val moshi = Moshi.Builder().build()
|
||||||
val jsonAdapter = moshi.adapter(SubtypeConstructorParameters::class.java)
|
val jsonAdapter = moshi.adapter(SubtypeConstructorParameters::class.java)
|
||||||
@@ -105,20 +88,6 @@ class KotlinCodeGenTest {
|
|||||||
var b: Int = -1
|
var b: Int = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore @Test fun extendsPlatformClassWithPrivateField() {
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
val jsonAdapter = moshi.adapter(ExtendsPlatformClassWithPrivateField::class.java)
|
|
||||||
|
|
||||||
val encoded = ExtendsPlatformClassWithPrivateField(3)
|
|
||||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3}""")
|
|
||||||
|
|
||||||
val decoded = jsonAdapter.fromJson("""{"a":4,"id":"B"}""")!!
|
|
||||||
assertThat(decoded.a).isEqualTo(4)
|
|
||||||
assertThat(decoded.id).isEqualTo("C")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ExtendsPlatformClassWithPrivateField(var a: Int) : SimpleTimeZone(0, "C")
|
|
||||||
|
|
||||||
@Ignore @Test fun extendsPlatformClassWithProtectedField() {
|
@Ignore @Test fun extendsPlatformClassWithProtectedField() {
|
||||||
val moshi = Moshi.Builder().build()
|
val moshi = Moshi.Builder().build()
|
||||||
val jsonAdapter = moshi.adapter(ExtendsPlatformClassWithProtectedField::class.java)
|
val jsonAdapter = moshi.adapter(ExtendsPlatformClassWithProtectedField::class.java)
|
||||||
@@ -216,84 +185,6 @@ class KotlinCodeGenTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore @Test fun unsettablePropertyIgnored() {
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
val jsonAdapter = moshi.adapter(UnsettableProperty::class.java)
|
|
||||||
|
|
||||||
val encoded = UnsettableProperty()
|
|
||||||
encoded.b = 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 UnsettableProperty {
|
|
||||||
val a: Int = -1
|
|
||||||
var b: Int = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore @Test fun getterOnlyNoBackingField() {
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
val jsonAdapter = moshi.adapter(GetterOnly::class.java)
|
|
||||||
|
|
||||||
val encoded = GetterOnly(3, 5)
|
|
||||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
|
|
||||||
|
|
||||||
val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
|
|
||||||
assertThat(decoded.a).isEqualTo(4)
|
|
||||||
assertThat(decoded.b).isEqualTo(6)
|
|
||||||
assertThat(decoded.total).isEqualTo(10)
|
|
||||||
}
|
|
||||||
|
|
||||||
class GetterOnly(var a: Int, var b: Int) {
|
|
||||||
val total : Int
|
|
||||||
get() = a + b
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore @Test fun getterAndSetterNoBackingField() {
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
val jsonAdapter = moshi.adapter(GetterAndSetter::class.java)
|
|
||||||
|
|
||||||
val encoded = GetterAndSetter(3, 5)
|
|
||||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5,"total":8}""")
|
|
||||||
|
|
||||||
// Whether b is 6 or 7 is an implementation detail. Currently we call constructors then setters.
|
|
||||||
val decoded1 = jsonAdapter.fromJson("""{"a":4,"b":6,"total":11}""")!!
|
|
||||||
assertThat(decoded1.a).isEqualTo(4)
|
|
||||||
assertThat(decoded1.b).isEqualTo(7)
|
|
||||||
assertThat(decoded1.total).isEqualTo(11)
|
|
||||||
|
|
||||||
// Whether b is 6 or 7 is an implementation detail. Currently we call constructors then setters.
|
|
||||||
val decoded2 = jsonAdapter.fromJson("""{"a":4,"total":11,"b":6}""")!!
|
|
||||||
assertThat(decoded2.a).isEqualTo(4)
|
|
||||||
assertThat(decoded2.b).isEqualTo(7)
|
|
||||||
assertThat(decoded2.total).isEqualTo(11)
|
|
||||||
}
|
|
||||||
|
|
||||||
class GetterAndSetter(var a: Int, var b: Int) {
|
|
||||||
var total : Int
|
|
||||||
get() = a + b
|
|
||||||
set(value) {
|
|
||||||
b = value - a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore @Test fun nonPropertyConstructorParameter() {
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
try {
|
|
||||||
moshi.adapter(NonPropertyConstructorParameter::class.java)
|
|
||||||
fail()
|
|
||||||
} catch(expected: IllegalArgumentException) {
|
|
||||||
assertThat(expected).hasMessage(
|
|
||||||
"No property for required constructor parameter #0 a of fun <init>(" +
|
|
||||||
"kotlin.Int, kotlin.Int): ${NonPropertyConstructorParameter::class.qualifiedName}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NonPropertyConstructorParameter(a: Int, val b: Int)
|
|
||||||
|
|
||||||
@Ignore @Test fun kotlinEnumsAreNotCovered() {
|
@Ignore @Test fun kotlinEnumsAreNotCovered() {
|
||||||
val moshi = Moshi.Builder().build()
|
val moshi = Moshi.Builder().build()
|
||||||
val adapter = moshi.adapter(UsingEnum::class.java)
|
val adapter = moshi.adapter(UsingEnum::class.java)
|
||||||
@@ -306,19 +197,4 @@ class KotlinCodeGenTest {
|
|||||||
enum class KotlinEnum {
|
enum class KotlinEnum {
|
||||||
A, B
|
A, B
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jwilson): resolve generic types?
|
|
||||||
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
@JsonQualifier
|
|
||||||
annotation class Uppercase
|
|
||||||
|
|
||||||
class UppercaseJsonAdapter {
|
|
||||||
@ToJson fun toJson(@Uppercase s: String) : String {
|
|
||||||
return s.toUpperCase(Locale.US)
|
|
||||||
}
|
|
||||||
@FromJson @Uppercase fun fromJson(s: String) : String {
|
|
||||||
return s.toLowerCase(Locale.US)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user