Support non-data classes for generated JsonAdapters

This is towards making the reflection and codegen adapters work the same.
The process is relatively straightforward: try to promote all of the tests
in KotlinCodeGenTest to be passing tests in GeneratedAdaptersTest or
compile failures in CompilerTest
This commit is contained in:
Jesse Wilson 2018-04-02 00:37:17 -04:00
parent 7750d179be
commit b3d7dfd603
6 changed files with 263 additions and 167 deletions

View file

@ -35,20 +35,17 @@ import javax.lang.model.util.Elements
/** Generates a JSON adapter for a target type. */ /** Generates a JSON adapter for a target type. */
internal class AdapterGenerator( internal class AdapterGenerator(
val fqClassName: String, val className: ClassName,
val packageName: String,
val propertyList: List<PropertyGenerator>, val propertyList: List<PropertyGenerator>,
val originalElement: Element, val originalElement: Element,
name: String = fqClassName.substringAfter(packageName) val isDataClass: Boolean,
.replace('.', '_')
.removePrefix("_"),
val hasCompanionObject: Boolean, val hasCompanionObject: Boolean,
val visibility: ProtoBuf.Visibility, val visibility: ProtoBuf.Visibility,
val elements: Elements, val elements: Elements,
val genericTypeNames: List<TypeVariableName>? val genericTypeNames: List<TypeVariableName>?
) { ) {
val nameAllocator = NameAllocator() val nameAllocator = NameAllocator()
val adapterName = "${name}JsonAdapter" val adapterName = "${className.simpleNames().joinToString(separator = "_")}JsonAdapter"
val originalTypeName = originalElement.asType().asTypeName() val originalTypeName = originalElement.asType().asTypeName()
val moshiParam = ParameterSpec.builder( val moshiParam = ParameterSpec.builder(
@ -92,7 +89,7 @@ internal class AdapterGenerator(
property.allocateNames(nameAllocator) property.allocateNames(nameAllocator)
} }
val result = FileSpec.builder(packageName, adapterName) val result = FileSpec.builder(className.packageName(), adapterName)
if (hasCompanionObject) { if (hasCompanionObject) {
result.addFunction(generateJsonAdapterFun()) result.addFunction(generateJsonAdapterFun())
} }
@ -148,6 +145,8 @@ internal class AdapterGenerator(
} }
private fun generateFromJsonFun(): FunSpec { private fun generateFromJsonFun(): FunSpec {
val resultName = nameAllocator.newName("result")
val result = FunSpec.builder("fromJson") val result = FunSpec.builder("fromJson")
.addModifiers(KModifier.OVERRIDE) .addModifiers(KModifier.OVERRIDE)
.addParameter(readerParam) .addParameter(readerParam)
@ -187,36 +186,72 @@ internal class AdapterGenerator(
result.endControlFlow() // while result.endControlFlow() // while
result.addStatement("%N.endObject()", readerParam) result.addStatement("%N.endObject()", readerParam)
val propertiesWithoutDefaults = propertyList.filter { !it.hasDefault } // Call the constructor providing only required parameters.
result.addCode("%[return %T(\n", originalTypeName) var hasOptionalParameters = false
propertiesWithoutDefaults.forEachIndexed { index, property -> result.addCode("%[var %N = %T(", resultName, originalTypeName)
var separator = "\n"
for (property in propertyList) {
if (!property.hasConstructorParameter) {
continue
}
if (property.hasDefault) {
hasOptionalParameters = true
continue
}
result.addCode(separator)
result.addCode("%N = %N", property.name, property.localName) result.addCode("%N = %N", property.name, property.localName)
if (property.isRequired) { if (property.isRequired) {
result.addCode(" ?: throw %T(\"Required property '%L' missing at \${%N.path}\")", result.addCode(" ?: throw %T(\"Required property '%L' missing at \${%N.path}\")",
JsonDataException::class, property.localName, readerParam) JsonDataException::class, property.localName, readerParam)
} }
result.addCode(if (index + 1 < propertiesWithoutDefaults.size) ",\n" else "\n") separator = ",\n"
} }
result.addCode("%])\n", originalTypeName) result.addCode(")%]\n", originalTypeName)
val propertiesWithDefaults = propertyList.filter { it.hasDefault } // Call either the constructor again, or the copy() method, this time providing any optional
if (!propertiesWithDefaults.isEmpty()) { // parameters that we have.
result.addCode(".let {%>\n") if (hasOptionalParameters) {
result.addCode("%[it.copy(\n") if (isDataClass) {
propertiesWithDefaults.forEachIndexed { index, property -> result.addCode("%[%1N = %1N.copy(", resultName)
if (property.differentiateAbsentFromNull) { } else {
result.addCode("%1N = if (%2N) %3N else it.%1N", result.addCode("%[%1N = %2T(", resultName, originalTypeName)
property.name, property.localIsPresentName, property.localName) }
} else { separator = "\n"
result.addCode("%1N = %2N ?: it.%1N", for (property in propertyList) {
property.name, property.localName) if (!property.hasConstructorParameter) {
continue // No constructor parameter for this property.
} }
result.addCode(if (index + 1 < propertiesWithDefaults.size) ",\n" else "\n") if (isDataClass && !property.hasDefault) {
continue // Property already assigned.
}
result.addCode(separator)
if (property.differentiateAbsentFromNull) {
result.addCode("%2N = if (%3N) %4N else %1N.%2N",
resultName, property.name, property.localIsPresentName, property.localName)
} else {
result.addCode("%2N = %3N ?: %1N.%2N", resultName, property.name, property.localName)
}
separator = ",\n"
} }
result.addCode("%])\n") result.addCode("%])\n")
result.addCode("%<}\n")
} }
// Assign properties not present in the constructor.
for (property in propertyList) {
if (property.hasConstructorParameter) {
continue // Property already handled.
}
if (property.differentiateAbsentFromNull) {
result.addStatement("%1N.%2N = if (%3N) %4N else %1N.%2N",
resultName, property.name, property.localIsPresentName, property.localName)
} else {
result.addStatement("%1N.%2N = %3N ?: %1N.%2N",
resultName, property.name, property.localName)
}
}
result.addStatement("return %1N", resultName)
return result.build() return result.build()
} }
@ -244,8 +279,7 @@ internal class AdapterGenerator(
private fun generateJsonAdapterFun(): FunSpec { private fun generateJsonAdapterFun(): FunSpec {
val rawType = when (originalTypeName) { val rawType = when (originalTypeName) {
is TypeVariableName -> throw IllegalArgumentException( is TypeVariableName -> throw IllegalArgumentException("Cannot get raw type of TypeVariable!")
"Cannot get raw type of TypeVariable!")
is ParameterizedTypeName -> originalTypeName.rawType is ParameterizedTypeName -> originalTypeName.rawType
else -> originalTypeName as ClassName else -> originalTypeName as ClassName
} }

View file

@ -17,13 +17,16 @@ package com.squareup.moshi
import com.google.auto.common.AnnotationMirrors import com.google.auto.common.AnnotationMirrors
import com.google.auto.service.AutoService import com.google.auto.service.AutoService
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier.OUT import com.squareup.kotlinpoet.KModifier.OUT
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.TypeVariableName import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asTypeName
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
import me.eugeniomarletti.kotlin.metadata.classKind
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
import me.eugeniomarletti.kotlin.metadata.extractFullName
import me.eugeniomarletti.kotlin.metadata.isDataClass import me.eugeniomarletti.kotlin.metadata.isDataClass
import me.eugeniomarletti.kotlin.metadata.isPrimary import me.eugeniomarletti.kotlin.metadata.isPrimary
import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
@ -31,14 +34,17 @@ import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
import me.eugeniomarletti.kotlin.metadata.visibility import me.eugeniomarletti.kotlin.metadata.visibility
import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
import org.jetbrains.kotlin.serialization.ProtoBuf import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.ProtoBuf.ValueParameter
import java.io.File import java.io.File
import javax.annotation.processing.Processor import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion import javax.lang.model.SourceVersion
import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.Element import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.tools.Diagnostic.Kind.ERROR import javax.tools.Diagnostic.Kind.ERROR
/** /**
@ -77,24 +83,24 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
val metadata = element.kotlinMetadata val metadata = element.kotlinMetadata
if (metadata !is KotlinClassMetadata) { if (metadata !is KotlinClassMetadata) {
errorMustBeDataClass(element) errorMustBeKotlinClass(element)
return null return null
} }
val classData = metadata.data val classData = metadata.data
val (nameResolver, classProto) = classData val (nameResolver, classProto) = classData
fun ProtoBuf.Type.extractFullName() = extractFullName(classData) if (classProto.classKind != ProtoBuf.Class.Kind.CLASS) {
errorMustBeKotlinClass(element)
if (!classProto.isDataClass) {
errorMustBeDataClass(element)
return null return null
} }
val fqClassName = nameResolver.getString(classProto.fqName).replace('/', '.') val typeName = element.asType().asTypeName()
val className = when (typeName) {
val packageName = nameResolver.getString(classProto.fqName).substringBeforeLast('/').replace( is ClassName -> typeName
'/', '.') is ParameterizedTypeName -> typeName.rawType
else -> throw IllegalStateException("unexpected TypeName: ${typeName::class}")
}
val hasCompanionObject = classProto.hasCompanionObjectName() val hasCompanionObject = classProto.hasCompanionObjectName()
// todo allow custom constructor // todo allow custom constructor
@ -113,34 +119,49 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
.first() .first()
// TODO Temporary until jvm method signature matching is better // TODO Temporary until jvm method signature matching is better
// .single { it.jvmMethodSignature == constructorJvmSignature } // .single { it.jvmMethodSignature == constructorJvmSignature }
val parameters = protoConstructor val parameters: Map<String, ValueParameter> = protoConstructor.valueParameterList.associateBy {
.valueParameterList nameResolver.getString(it.name)
.mapIndexed { index, valueParameter -> }
val paramName = nameResolver.getString(valueParameter.name)
val nullable = valueParameter.type.nullable val properties = classData.classProto.propertyList.associateBy {
val paramFqcn = valueParameter.type.extractFullName() nameResolver.getString(it.name)
.replace("`", "") }
.removeSuffix("?")
val actualElement = constructor.parameters[index] val propertyGenerators = mutableListOf<PropertyGenerator>()
for (enclosedElement in element.enclosedElements) {
if (enclosedElement !is VariableElement) continue
val serializedName = actualElement.getAnnotation(Json::class.java)?.name val name = enclosedElement.simpleName.toString()
?: paramName val property = properties[name] ?: continue
val parameter = parameters[name]
val jsonQualifiers = AnnotationMirrors.getAnnotatedAnnotations(actualElement, val parameterElement = if (parameter != null) {
JsonQualifier::class.java) val parameterIndex = protoConstructor.valueParameterList.indexOf(parameter)
constructor.parameters[parameterIndex]
} else {
null
}
PropertyGenerator( if (property.visibility != ProtoBuf.Visibility.INTERNAL
name = paramName, && property.visibility != ProtoBuf.Visibility.PROTECTED
serializedName = serializedName, && property.visibility != ProtoBuf.Visibility.PUBLIC) {
hasDefault = valueParameter.declaresDefaultValue, messager.printMessage(ERROR, "property $name is not visible", enclosedElement)
nullable = nullable, return null
typeName = valueParameter.type.asTypeName(nameResolver, classProto::getTypeParameter), }
unaliasedName = valueParameter.type.asTypeName(nameResolver,
classProto::getTypeParameter, true), propertyGenerators += PropertyGenerator(
jsonQualifiers = jsonQualifiers) name,
} serializedName(name, enclosedElement, parameterElement),
parameter != null,
parameter?.declaresDefaultValue ?: true,
property.returnType.nullable,
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter),
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter, true),
jsonQualifiers(enclosedElement, parameterElement))
}
// Sort properties so that those with constructor parameters come first.
propertyGenerators.sortBy { if (it.hasConstructorParameter) -1 else 1 }
val genericTypeNames = classProto.typeParameterList val genericTypeNames = classProto.typeParameterList
.map { .map {
@ -168,19 +189,56 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
} }
return AdapterGenerator( return AdapterGenerator(
fqClassName = fqClassName, className,
packageName = packageName, propertyList = propertyGenerators,
propertyList = parameters,
originalElement = element, originalElement = element,
hasCompanionObject = hasCompanionObject, hasCompanionObject = hasCompanionObject,
visibility = classProto.visibility!!, visibility = classProto.visibility!!,
genericTypeNames = genericTypeNames, genericTypeNames = genericTypeNames,
elements = elementUtils) elements = elementUtils,
isDataClass = classProto.isDataClass)
} }
private fun errorMustBeDataClass(element: Element) { /** Returns the JsonQualifiers on the field and parameter of a property. */
private fun jsonQualifiers(
field: VariableElement,
parameter: VariableElement?
): Set<AnnotationMirror> {
val fieldJsonQualifiers = AnnotationMirrors.getAnnotatedAnnotations(
field, JsonQualifier::class.java)
val parameterJsonQualifiers: Set<AnnotationMirror> = if (parameter != null) {
AnnotationMirrors.getAnnotatedAnnotations(parameter, JsonQualifier::class.java)
} else {
setOf()
}
// TODO(jwilson): union the qualifiers somehow?
if (fieldJsonQualifiers.isNotEmpty()) {
return fieldJsonQualifiers
} else {
return parameterJsonQualifiers
}
}
/** Returns the @Json name of a property, or `propertyName` if none is provided. */
private fun serializedName(
propertyName: String,
field: VariableElement,
parameter: VariableElement?
): String {
val fieldAnnotation = field.getAnnotation(Json::class.java)
if (fieldAnnotation != null) return fieldAnnotation.name
val parameterAnnotation = parameter?.getAnnotation(Json::class.java)
if (parameterAnnotation != null) return parameterAnnotation.name
return propertyName
}
private fun errorMustBeKotlinClass(element: Element) {
messager.printMessage(ERROR, messager.printMessage(ERROR,
"@${JsonClass::class.java.simpleName} can't be applied to $element: must be a Kotlin data class", "@${JsonClass::class.java.simpleName} can't be applied to $element: must be a Kotlin class",
element) element)
} }

View file

@ -32,6 +32,7 @@ import javax.lang.model.element.AnnotationMirror
internal class PropertyGenerator( internal class PropertyGenerator(
val name: String, val name: String,
val serializedName: String, val serializedName: String,
val hasConstructorParameter: Boolean,
val hasDefault: Boolean, val hasDefault: Boolean,
val nullable: Boolean, val nullable: Boolean,
val typeName: TypeName, val typeName: TypeName,

View file

@ -35,13 +35,11 @@ class CompilerTest {
|import com.squareup.moshi.JsonClass |import com.squareup.moshi.JsonClass
| |
|@JsonClass(generateAdapter = true) |@JsonClass(generateAdapter = true)
|class ConstructorParameters(var a: Int, var b: Int) |class PrivateConstructorParameter(private var a: Int)
|""".trimMargin()) |""".trimMargin())
val result = call.execute() val result = call.execute()
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR) assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains( assertThat(result.systemErr).contains("property a is not visible")
"@JsonClass can't be applied to ConstructorParameters: must be a Kotlin data class")
} }
} }

View file

@ -20,7 +20,7 @@ import org.assertj.core.api.Assertions.fail
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.junit.Test import org.junit.Test
class DataClassesTest { class GeneratedAdaptersTest {
private val moshi = Moshi.Builder().build() private val moshi = Moshi.Builder().build()
@ -252,6 +252,107 @@ class DataClassesTest {
@JsonClass(generateAdapter = false) @JsonClass(generateAdapter = false)
data class DoNotGenerateAdapter(val foo: String) data class DoNotGenerateAdapter(val foo: String)
@Test fun constructorParameters() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ConstructorParameters::class.java)
val encoded = ConstructorParameters(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)
}
@JsonClass(generateAdapter = true)
class ConstructorParameters(var a: Int, var b: Int)
@Test fun properties() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(Properties::class.java)
val encoded = Properties()
encoded.a = 3
encoded.b = 5
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
val decoded = jsonAdapter.fromJson("""{"a":3,"b":5}""")!!
assertThat(decoded.a).isEqualTo(3)
assertThat(decoded.b).isEqualTo(5)
}
@JsonClass(generateAdapter = true)
class Properties {
var a: Int = -1
var b: Int = -1
}
@Test fun constructorParametersAndProperties() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ConstructorParametersAndProperties::class.java)
val encoded = ConstructorParametersAndProperties(3)
encoded.b = 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)
}
@JsonClass(generateAdapter = true)
class ConstructorParametersAndProperties(var a: Int) {
var b: Int = -1
}
@Test fun immutableConstructorParameters() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ImmutableConstructorParameters::class.java)
val encoded = ImmutableConstructorParameters(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)
}
@JsonClass(generateAdapter = true)
class ImmutableConstructorParameters(val a: Int, val b: Int)
@Test fun immutableProperties() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ImmutableProperties::class.java)
val encoded = ImmutableProperties(3, 5)
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
val decoded = jsonAdapter.fromJson("""{"a":3,"b":5}""")!!
assertThat(decoded.a).isEqualTo(3)
assertThat(decoded.b).isEqualTo(5)
}
@JsonClass(generateAdapter = true)
class ImmutableProperties(a: Int, b: Int) {
val a = a
val b = b
}
@Test fun constructorDefaults() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ConstructorDefaultValues::class.java)
val encoded = ConstructorDefaultValues(3, 5)
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
val decoded = jsonAdapter.fromJson("""{"b":6}""")!!
assertThat(decoded.a).isEqualTo(-1)
assertThat(decoded.b).isEqualTo(6)
}
@JsonClass(generateAdapter = true)
class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2)
} }
// Has to be outside to avoid Types seeing an owning class // Has to be outside to avoid Types seeing an owning class

View file

@ -25,102 +25,6 @@ import java.util.SimpleTimeZone
import kotlin.annotation.AnnotationRetention.RUNTIME import kotlin.annotation.AnnotationRetention.RUNTIME
class KotlinCodeGenTest { class KotlinCodeGenTest {
@Ignore @Test fun constructorParameters() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ConstructorParameters::class.java)
val encoded = ConstructorParameters(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)
}
class ConstructorParameters(var a: Int, var b: Int)
@Ignore @Test fun properties() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(Properties::class.java)
val encoded = Properties()
encoded.a = 3
encoded.b = 5
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
val decoded = jsonAdapter.fromJson("""{"a":3,"b":5}""")!!
assertThat(decoded.a).isEqualTo(3)
assertThat(decoded.b).isEqualTo(5)
}
class Properties {
var a: Int = -1
var b: Int = -1
}
@Ignore @Test fun constructorParametersAndProperties() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ConstructorParametersAndProperties::class.java)
val encoded = ConstructorParametersAndProperties(3)
encoded.b = 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)
}
class ConstructorParametersAndProperties(var a: Int) {
var b: Int = -1
}
@Ignore @Test fun immutableConstructorParameters() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ImmutableConstructorParameters::class.java)
val encoded = ImmutableConstructorParameters(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)
}
class ImmutableConstructorParameters(val a: Int, val b: Int)
@Ignore @Test fun immutableProperties() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ImmutableProperties::class.java)
val encoded = ImmutableProperties(3, 5)
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
val decoded = jsonAdapter.fromJson("""{"a":3,"b":5}""")!!
assertThat(decoded.a).isEqualTo(3)
assertThat(decoded.b).isEqualTo(5)
}
class ImmutableProperties(a: Int, b: Int) {
val a = a
val b = b
}
@Ignore @Test fun constructorDefaults() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ConstructorDefaultValues::class.java)
val encoded = ConstructorDefaultValues(3, 5)
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
val decoded = jsonAdapter.fromJson("""{"b":6}""")!!
assertThat(decoded.a).isEqualTo(-1)
assertThat(decoded.b).isEqualTo(6)
}
class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2)
@Ignore @Test fun requiredValueAbsent() { @Ignore @Test fun requiredValueAbsent() {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(RequiredValueAbsent::class.java) val jsonAdapter = moshi.adapter(RequiredValueAbsent::class.java)