diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt index 78b7428..b0e9c66 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt @@ -35,20 +35,17 @@ import javax.lang.model.util.Elements /** Generates a JSON adapter for a target type. */ internal class AdapterGenerator( - val fqClassName: String, - val packageName: String, + val className: ClassName, val propertyList: List, val originalElement: Element, - name: String = fqClassName.substringAfter(packageName) - .replace('.', '_') - .removePrefix("_"), + val isDataClass: Boolean, val hasCompanionObject: Boolean, val visibility: ProtoBuf.Visibility, val elements: Elements, val genericTypeNames: List? ) { val nameAllocator = NameAllocator() - val adapterName = "${name}JsonAdapter" + val adapterName = "${className.simpleNames().joinToString(separator = "_")}JsonAdapter" val originalTypeName = originalElement.asType().asTypeName() val moshiParam = ParameterSpec.builder( @@ -92,7 +89,7 @@ internal class AdapterGenerator( property.allocateNames(nameAllocator) } - val result = FileSpec.builder(packageName, adapterName) + val result = FileSpec.builder(className.packageName(), adapterName) if (hasCompanionObject) { result.addFunction(generateJsonAdapterFun()) } @@ -148,6 +145,8 @@ internal class AdapterGenerator( } private fun generateFromJsonFun(): FunSpec { + val resultName = nameAllocator.newName("result") + val result = FunSpec.builder("fromJson") .addModifiers(KModifier.OVERRIDE) .addParameter(readerParam) @@ -187,36 +186,72 @@ internal class AdapterGenerator( result.endControlFlow() // while result.addStatement("%N.endObject()", readerParam) - val propertiesWithoutDefaults = propertyList.filter { !it.hasDefault } - result.addCode("%[return %T(\n", originalTypeName) - propertiesWithoutDefaults.forEachIndexed { index, property -> + // Call the constructor providing only required parameters. + var hasOptionalParameters = false + 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) if (property.isRequired) { result.addCode(" ?: throw %T(\"Required property '%L' missing at \${%N.path}\")", 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 } - if (!propertiesWithDefaults.isEmpty()) { - result.addCode(".let {%>\n") - result.addCode("%[it.copy(\n") - propertiesWithDefaults.forEachIndexed { index, property -> - if (property.differentiateAbsentFromNull) { - result.addCode("%1N = if (%2N) %3N else it.%1N", - property.name, property.localIsPresentName, property.localName) - } else { - result.addCode("%1N = %2N ?: it.%1N", - property.name, property.localName) + // Call either the constructor again, or the copy() method, this time providing any optional + // parameters that we have. + if (hasOptionalParameters) { + if (isDataClass) { + result.addCode("%[%1N = %1N.copy(", resultName) + } else { + result.addCode("%[%1N = %2T(", resultName, originalTypeName) + } + separator = "\n" + for (property in propertyList) { + 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") } + // 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() } @@ -244,8 +279,7 @@ internal class AdapterGenerator( private fun generateJsonAdapterFun(): FunSpec { val rawType = when (originalTypeName) { - is TypeVariableName -> throw IllegalArgumentException( - "Cannot get raw type of TypeVariable!") + is TypeVariableName -> throw IllegalArgumentException("Cannot get raw type of TypeVariable!") is ParameterizedTypeName -> originalTypeName.rawType else -> originalTypeName as ClassName } diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt index 345919e..91a5c94 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt @@ -17,13 +17,16 @@ package com.squareup.moshi import com.google.auto.common.AnnotationMirrors import com.google.auto.service.AutoService +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.KModifier.OUT +import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.asTypeName import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils +import me.eugeniomarletti.kotlin.metadata.classKind import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue -import me.eugeniomarletti.kotlin.metadata.extractFullName import me.eugeniomarletti.kotlin.metadata.isDataClass import me.eugeniomarletti.kotlin.metadata.isPrimary 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.processing.KotlinAbstractProcessor import org.jetbrains.kotlin.serialization.ProtoBuf +import org.jetbrains.kotlin.serialization.ProtoBuf.ValueParameter import java.io.File import javax.annotation.processing.Processor import javax.annotation.processing.RoundEnvironment import javax.lang.model.SourceVersion +import javax.lang.model.element.AnnotationMirror import javax.lang.model.element.Element import javax.lang.model.element.ElementKind import javax.lang.model.element.ExecutableElement import javax.lang.model.element.TypeElement +import javax.lang.model.element.VariableElement import javax.tools.Diagnostic.Kind.ERROR /** @@ -77,24 +83,24 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils val metadata = element.kotlinMetadata if (metadata !is KotlinClassMetadata) { - errorMustBeDataClass(element) + errorMustBeKotlinClass(element) return null } val classData = metadata.data val (nameResolver, classProto) = classData - fun ProtoBuf.Type.extractFullName() = extractFullName(classData) - - if (!classProto.isDataClass) { - errorMustBeDataClass(element) + if (classProto.classKind != ProtoBuf.Class.Kind.CLASS) { + errorMustBeKotlinClass(element) return null } - val fqClassName = nameResolver.getString(classProto.fqName).replace('/', '.') - - val packageName = nameResolver.getString(classProto.fqName).substringBeforeLast('/').replace( - '/', '.') + val typeName = element.asType().asTypeName() + val className = when (typeName) { + is ClassName -> typeName + is ParameterizedTypeName -> typeName.rawType + else -> throw IllegalStateException("unexpected TypeName: ${typeName::class}") + } val hasCompanionObject = classProto.hasCompanionObjectName() // todo allow custom constructor @@ -113,34 +119,49 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils .first() // TODO Temporary until jvm method signature matching is better // .single { it.jvmMethodSignature == constructorJvmSignature } - val parameters = protoConstructor - .valueParameterList - .mapIndexed { index, valueParameter -> - val paramName = nameResolver.getString(valueParameter.name) + val parameters: Map = protoConstructor.valueParameterList.associateBy { + nameResolver.getString(it.name) + } - val nullable = valueParameter.type.nullable - val paramFqcn = valueParameter.type.extractFullName() - .replace("`", "") - .removeSuffix("?") + val properties = classData.classProto.propertyList.associateBy { + nameResolver.getString(it.name) + } - val actualElement = constructor.parameters[index] + val propertyGenerators = mutableListOf() + for (enclosedElement in element.enclosedElements) { + if (enclosedElement !is VariableElement) continue - val serializedName = actualElement.getAnnotation(Json::class.java)?.name - ?: paramName + val name = enclosedElement.simpleName.toString() + val property = properties[name] ?: continue + val parameter = parameters[name] - val jsonQualifiers = AnnotationMirrors.getAnnotatedAnnotations(actualElement, - JsonQualifier::class.java) + val parameterElement = if (parameter != null) { + val parameterIndex = protoConstructor.valueParameterList.indexOf(parameter) + constructor.parameters[parameterIndex] + } else { + null + } - PropertyGenerator( - name = paramName, - serializedName = serializedName, - hasDefault = valueParameter.declaresDefaultValue, - nullable = nullable, - typeName = valueParameter.type.asTypeName(nameResolver, classProto::getTypeParameter), - unaliasedName = valueParameter.type.asTypeName(nameResolver, - classProto::getTypeParameter, true), - jsonQualifiers = jsonQualifiers) - } + if (property.visibility != ProtoBuf.Visibility.INTERNAL + && property.visibility != ProtoBuf.Visibility.PROTECTED + && property.visibility != ProtoBuf.Visibility.PUBLIC) { + messager.printMessage(ERROR, "property $name is not visible", enclosedElement) + return null + } + + propertyGenerators += PropertyGenerator( + 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 .map { @@ -168,19 +189,56 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils } return AdapterGenerator( - fqClassName = fqClassName, - packageName = packageName, - propertyList = parameters, + className, + propertyList = propertyGenerators, originalElement = element, hasCompanionObject = hasCompanionObject, visibility = classProto.visibility!!, 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 { + val fieldJsonQualifiers = AnnotationMirrors.getAnnotatedAnnotations( + field, JsonQualifier::class.java) + + val parameterJsonQualifiers: Set = 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, - "@${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) } diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/PropertyGenerator.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/PropertyGenerator.kt index 3d8319c..68b61d7 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/PropertyGenerator.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/PropertyGenerator.kt @@ -32,6 +32,7 @@ import javax.lang.model.element.AnnotationMirror internal class PropertyGenerator( val name: String, val serializedName: String, + val hasConstructorParameter: Boolean, val hasDefault: Boolean, val nullable: Boolean, val typeName: TypeName, diff --git a/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/CompilerTest.kt b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/CompilerTest.kt index 21ad81f..6a0ac2c 100644 --- a/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/CompilerTest.kt +++ b/kotlin-codegen/compiler/src/test/java/com/squareup/moshi/CompilerTest.kt @@ -35,13 +35,11 @@ class CompilerTest { |import com.squareup.moshi.JsonClass | |@JsonClass(generateAdapter = true) - |class ConstructorParameters(var a: Int, var b: Int) + |class PrivateConstructorParameter(private var a: Int) |""".trimMargin()) val result = call.execute() assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR) - assertThat(result.systemErr).contains( - "@JsonClass can't be applied to ConstructorParameters: must be a Kotlin data class") + assertThat(result.systemErr).contains("property a is not visible") } - } \ No newline at end of file diff --git a/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/DataClassesTest.kt b/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/GeneratedAdaptersTest.kt similarity index 74% rename from kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/DataClassesTest.kt rename to kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/GeneratedAdaptersTest.kt index 9c3bbd5..e077d80 100644 --- a/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/DataClassesTest.kt +++ b/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/GeneratedAdaptersTest.kt @@ -20,7 +20,7 @@ import org.assertj.core.api.Assertions.fail import org.intellij.lang.annotations.Language import org.junit.Test -class DataClassesTest { +class GeneratedAdaptersTest { private val moshi = Moshi.Builder().build() @@ -252,6 +252,107 @@ class DataClassesTest { @JsonClass(generateAdapter = false) 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 diff --git a/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/KotlinCodeGenTest.kt b/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/KotlinCodeGenTest.kt index f4133a0..151f124 100644 --- a/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/KotlinCodeGenTest.kt +++ b/kotlin-codegen/integration-test/src/test/kotlin/com/squareup/moshi/KotlinCodeGenTest.kt @@ -25,102 +25,6 @@ import java.util.SimpleTimeZone import kotlin.annotation.AnnotationRetention.RUNTIME 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() { val moshi = Moshi.Builder().build() val jsonAdapter = moshi.adapter(RequiredValueAbsent::class.java)