Use JsonAdapter.nonNull() in generated adapters.

Also extract a type for the delegate key.

Also fix the generator to reject inner classes, abstract classes,
and local classes.
This commit is contained in:
Jesse Wilson
2018-04-07 22:21:24 -04:00
parent 0a49ae3ac8
commit cb9c084d30
7 changed files with 373 additions and 289 deletions

View File

@@ -81,11 +81,11 @@ internal class AdapterGenerator(
.joinToString(", ") { "\"$it\"" }})", JsonReader.Options::class.asTypeName()) .joinToString(", ") { "\"$it\"" }})", JsonReader.Options::class.asTypeName())
.build() .build()
val delegateAdapters = propertyList.distinctBy { it.delegateKey() } val delegateAdapters = propertyList.distinctBy { it.delegateKey }
fun generateFile(generatedOption: TypeElement?): FileSpec { fun generateFile(generatedOption: TypeElement?): FileSpec {
for (property in delegateAdapters) { for (property in delegateAdapters) {
property.reserveDelegateNames(nameAllocator) property.delegateKey.reserveName(nameAllocator)
} }
for (property in propertyList) { for (property in propertyList) {
property.allocateNames(nameAllocator) property.allocateNames(nameAllocator)
@@ -125,7 +125,7 @@ internal class AdapterGenerator(
result.addProperty(optionsProperty) result.addProperty(optionsProperty)
for (uniqueAdapter in delegateAdapters) { for (uniqueAdapter in delegateAdapters) {
result.addProperty(uniqueAdapter.generateDelegateProperty(this)) result.addProperty(uniqueAdapter.delegateKey.generateProperty(nameAllocator, this))
} }
result.addFunction(generateToStringFun()) result.addFunction(generateToStringFun())
@@ -178,12 +178,12 @@ internal class AdapterGenerator(
if (property.differentiateAbsentFromNull) { if (property.differentiateAbsentFromNull) {
result.beginControlFlow("%L -> ", index) result.beginControlFlow("%L -> ", index)
result.addStatement("%N = %N.fromJson(%N)", result.addStatement("%N = %N.fromJson(%N)",
property.localName, property.delegateName, readerParam) property.localName, nameAllocator.get(property.delegateKey), readerParam)
result.addStatement("%N = true", property.localIsPresentName) result.addStatement("%N = true", property.localIsPresentName)
result.endControlFlow() result.endControlFlow()
} else { } else {
result.addStatement("%L -> %N = %N.fromJson(%N)", result.addStatement("%L -> %N = %N.fromJson(%N)",
index, property.localName, property.delegateName, readerParam) index, property.localName, nameAllocator.get(property.delegateKey), readerParam)
} }
} }
@@ -281,7 +281,7 @@ internal class AdapterGenerator(
propertyList.forEach { property -> propertyList.forEach { property ->
result.addStatement("%N.name(%S)", writerParam, property.serializedName) result.addStatement("%N.name(%S)", writerParam, property.serializedName)
result.addStatement("%N.toJson(%N, %N.%L)", result.addStatement("%N.toJson(%N, %N.%L)",
property.delegateName, writerParam, valueParam, property.name) nameAllocator.get(property.delegateKey), writerParam, valueParam, property.name)
} }
result.addStatement("%N.endObject()", writerParam) result.addStatement("%N.endObject()", writerParam)

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2018 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.moshi
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.asTypeName
import javax.lang.model.element.AnnotationMirror
/** A JsonAdapter that can be used to encode and decode a particular field. */
internal data class DelegateKey(
val type: TypeName,
val jsonQualifiers: Set<AnnotationMirror>
) {
val nullable
get() = type.nullable || type is TypeVariableName
fun reserveName(nameAllocator: NameAllocator) {
val qualifierNames = jsonQualifiers.joinToString("") {
"At${it.annotationType.asElement().simpleName}"
}
nameAllocator.newName("${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this)
}
/** Returns an adapter to use when encoding and decoding this property. */
fun generateProperty(nameAllocator: NameAllocator, enclosing: AdapterGenerator): PropertySpec {
val adapterTypeName = ParameterizedTypeName.get(
JsonAdapter::class.asTypeName(), type)
val qualifiers = jsonQualifiers
val standardArgs = arrayOf(enclosing.moshiParam,
if (type is ClassName && qualifiers.isEmpty()) {
""
} else {
CodeBlock.of("<%T>", type)
},
type.makeType(
enclosing.elements, enclosing.typesParam, enclosing.genericTypeNames ?: emptyList()))
val standardArgsSize = standardArgs.size + 1
val (initializerString, args) = when {
qualifiers.isEmpty() -> "" to emptyArray()
qualifiers.size == 1 -> {
", %${standardArgsSize}T::class.java" to arrayOf(
qualifiers.first().annotationType.asTypeName())
}
else -> {
val initString = qualifiers
.mapIndexed { index, _ ->
val annoClassIndex = standardArgsSize + index
return@mapIndexed "%${annoClassIndex}T::class.java"
}
.joinToString()
val initArgs = qualifiers
.map { it.annotationType.asTypeName() }
.toTypedArray()
", $initString" to initArgs
}
}
val finalArgs = arrayOf(*standardArgs, *args)
val nullModifier = if (nullable) ".nullSafe()" else ".nonNull()"
return PropertySpec.builder(nameAllocator.get(this), adapterTypeName, KModifier.PRIVATE)
.initializer("%1N.adapter%2L(%3L$initializerString)$nullModifier", *finalArgs)
.build()
}
}
/**
* Returns a suggested variable name derived from a list of type names. This just concatenates,
* yielding types like MapOfStringLong.
*/
private fun List<TypeName>.toVariableNames(): String {
return joinToString("") { it.toVariableName() }
}
/** Returns a suggested variable name derived from a type name, like nullableListOfString. */
private fun TypeName.toVariableName(): String {
val base = when (this) {
is ClassName -> simpleName()
is ParameterizedTypeName -> rawType.simpleName() + "Of" + typeArguments.toVariableNames()
is WildcardTypeName -> (lowerBounds + upperBounds).toVariableNames()
is TypeVariableName -> name + bounds.toVariableNames()
else -> throw IllegalArgumentException("Unrecognized type! $this")
}
return if (nullable) {
"Nullable$base"
} else {
base
}
}

View File

@@ -29,14 +29,18 @@ 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.isDataClass import me.eugeniomarletti.kotlin.metadata.isDataClass
import me.eugeniomarletti.kotlin.metadata.isInnerClass
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
import me.eugeniomarletti.kotlin.metadata.kotlinMetadata import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
import me.eugeniomarletti.kotlin.metadata.modality
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.Class
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 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,16 +122,35 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
val metadata = element.kotlinMetadata val metadata = element.kotlinMetadata
if (metadata !is KotlinClassMetadata) { if (metadata !is KotlinClassMetadata) {
errorMustBeKotlinClass(element) messager.printMessage(
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
return null return null
} }
val classData = metadata.data val classData = metadata.data
val (nameResolver, classProto) = classData val (nameResolver, classProto) = classData
if (classProto.classKind != ProtoBuf.Class.Kind.CLASS) { when {
errorMustBeKotlinClass(element) classProto.classKind != Class.Kind.CLASS -> {
return null messager.printMessage(
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
return null
}
classProto.isInnerClass -> {
messager.printMessage(
ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element)
return null
}
classProto.modality == Modality.ABSTRACT -> {
messager.printMessage(
ERROR, "@JsonClass can't be applied to $element: must not be abstract", element)
return null
}
classProto.visibility == Visibility.LOCAL -> {
messager.printMessage(
ERROR, "@JsonClass can't be applied to $element: must not be local", element)
return null
}
} }
val typeName = element.asType().asTypeName() val typeName = element.asType().asTypeName()
@@ -187,9 +210,9 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
val annotatedElement = annotatedElements[property] val annotatedElement = annotatedElements[property]
if (property.visibility != ProtoBuf.Visibility.INTERNAL if (property.visibility != Visibility.INTERNAL
&& property.visibility != ProtoBuf.Visibility.PROTECTED && property.visibility != Visibility.PROTECTED
&& property.visibility != ProtoBuf.Visibility.PUBLIC) { && property.visibility != Visibility.PUBLIC) {
messager.printMessage(ERROR, "property $name is not visible", enclosedElement) messager.printMessage(ERROR, "property $name is not visible", enclosedElement)
return null return null
} }
@@ -203,15 +226,17 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
continue continue
} }
val delegateKey = DelegateKey(
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter, true),
jsonQualifiers(enclosedElement, annotatedElement, parameterElement))
propertyGenerators += PropertyGenerator( propertyGenerators += PropertyGenerator(
delegateKey,
name, name,
jsonName(name, enclosedElement, annotatedElement, parameterElement), jsonName(name, enclosedElement, annotatedElement, parameterElement),
parameter != null, parameter != null,
hasDefault, hasDefault,
property.returnType.nullable, property.returnType.asTypeName(nameResolver, classProto::getTypeParameter))
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter),
property.returnType.asTypeName(nameResolver, classProto::getTypeParameter, true),
jsonQualifiers(enclosedElement, annotatedElement, parameterElement))
} }
// Sort properties so that those with constructor parameters come first. // Sort properties so that those with constructor parameters come first.

View File

@@ -16,97 +16,32 @@
package com.squareup.moshi package com.squareup.moshi
import com.squareup.kotlinpoet.BOOLEAN import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.NameAllocator import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.asTypeName
import javax.lang.model.element.AnnotationMirror
/** Generates functions to encode and decode a property as JSON. */ /** Generates functions to encode and decode a property as JSON. */
internal class PropertyGenerator( internal class PropertyGenerator(
val delegateKey: DelegateKey,
val name: String, val name: String,
val serializedName: String, val serializedName: String,
val hasConstructorParameter: Boolean, val hasConstructorParameter: Boolean,
val hasDefault: Boolean, val hasDefault: Boolean,
val nullable: Boolean, val typeName: TypeName
val typeName: TypeName,
val unaliasedName: TypeName,
val jsonQualifiers: Set<AnnotationMirror>
) { ) {
lateinit var delegateName: String
lateinit var localName: String lateinit var localName: String
lateinit var localIsPresentName: String lateinit var localIsPresentName: String
val isRequired val isRequired
get() = !nullable && !hasDefault get() = !delegateKey.nullable && !hasDefault
/** 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() = hasDefault && nullable get() = delegateKey.nullable && hasDefault
fun reserveDelegateNames(nameAllocator: NameAllocator) {
val qualifierNames = jsonQualifiers.joinToString("") {
"At${it.annotationType.asElement().simpleName.toString().capitalize()}"
}
nameAllocator.newName("${unaliasedName.toVariableName()}${qualifierNames}Adapter",
delegateKey())
}
fun allocateNames(nameAllocator: NameAllocator) { fun allocateNames(nameAllocator: NameAllocator) {
localName = nameAllocator.newName(name) localName = nameAllocator.newName(name)
localIsPresentName = nameAllocator.newName("${name}Set") localIsPresentName = nameAllocator.newName("${name}Set")
delegateName = nameAllocator.get(delegateKey())
}
/** Returns a key that matches keys of properties that can share an adapter. */
fun delegateKey() = unaliasedName to jsonQualifiers
/** Returns an adapter to use when encoding and decoding this property. */
fun generateDelegateProperty(enclosing: AdapterGenerator): PropertySpec {
val adapterTypeName = ParameterizedTypeName.get(
JsonAdapter::class.asTypeName(), unaliasedName)
val qualifiers = jsonQualifiers.toList()
val standardArgs = arrayOf(enclosing.moshiParam,
if (unaliasedName is ClassName && qualifiers.isEmpty()) {
""
} else {
CodeBlock.of("<%T>", unaliasedName)
},
unaliasedName.makeType(
enclosing.elements, enclosing.typesParam, enclosing.genericTypeNames ?: emptyList()))
val standardArgsSize = standardArgs.size + 1
val (initializerString, args) = when {
qualifiers.isEmpty() -> "" to emptyArray()
qualifiers.size == 1 -> {
", %${standardArgsSize}T::class.java" to arrayOf(
qualifiers.first().annotationType.asTypeName())
}
else -> {
val initString = qualifiers
.mapIndexed { index, _ ->
val annoClassIndex = standardArgsSize + index
return@mapIndexed "%${annoClassIndex}T::class.java"
}
.joinToString()
val initArgs = qualifiers
.map { it.annotationType.asTypeName() }
.toTypedArray()
", $initString" to initArgs
}
}
val finalArgs = arrayOf(*standardArgs, *args)
return PropertySpec.builder(delegateName, adapterTypeName,
KModifier.PRIVATE)
.initializer("%1N.adapter%2L(%3L$initializerString)${if (nullable) ".nullSafe()" else ""}",
*finalArgs)
.build()
} }
fun generateLocalProperty(): PropertySpec { fun generateLocalProperty(): PropertySpec {
@@ -123,25 +58,3 @@ internal class PropertyGenerator(
.build() .build()
} }
} }
/**
* Returns a suggested variable name derived from a list of type names.
*/
private fun List<TypeName>.toVariableNames(): String {
return joinToString("_") { it.toVariableName() }
}
/**
* Returns a suggested variable name derived from a type name.
*/
private fun TypeName.toVariableName(): String {
return when (this) {
is ClassName -> simpleName().decapitalize()
is ParameterizedTypeName -> {
rawType.simpleName().decapitalize() + if (typeArguments.isEmpty()) "" else "__" + typeArguments.toVariableNames()
}
is WildcardTypeName -> "wildcard__" + (lowerBounds + upperBounds).toVariableNames()
is TypeVariableName -> name.decapitalize() + if (bounds.isEmpty()) "" else "__" + bounds.toVariableNames()
else -> throw IllegalArgumentException("Unrecognized type! $this")
}.let { if (nullable) "${it}_nullable" else it }
}

View File

@@ -17,6 +17,7 @@ package com.squareup.moshi
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.ExitCode
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
@@ -26,8 +27,7 @@ import javax.annotation.processing.Processor
class CompilerTest { class CompilerTest {
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder() @Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
@Test @Test fun privateProperty() {
fun test() {
val call = KotlinCompilerCall(temporaryFolder.root) val call = KotlinCompilerCall(temporaryFolder.root)
call.inheritClasspath = true call.inheritClasspath = true
call.addService(Processor::class, JsonClassCodeGenProcessor::class) call.addService(Processor::class, JsonClassCodeGenProcessor::class)
@@ -42,4 +42,116 @@ class CompilerTest {
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR) assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains("property a is not visible") assertThat(result.systemErr).contains("property a is not visible")
} }
@Test fun interfacesNotSupported() {
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)
|interface Interface
|""".trimMargin())
val result = call.execute()
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains(
"error: @JsonClass can't be applied to Interface: must be a Kotlin class")
}
@Test fun abstractClassesNotSupported() {
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)
|abstract class AbstractClass(val a: Int)
|""".trimMargin())
val result = call.execute()
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains(
"error: @JsonClass can't be applied to AbstractClass: must not be abstract")
}
@Test fun innerClassesNotSupported() {
val call = KotlinCompilerCall(temporaryFolder.root)
call.inheritClasspath = true
call.addService(Processor::class, JsonClassCodeGenProcessor::class)
call.addKt("source.kt", """
|import com.squareup.moshi.JsonClass
|
|class Outer {
| @JsonClass(generateAdapter = true)
| inner class InnerClass(val a: Int)
|}
|""".trimMargin())
val result = call.execute()
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains(
"error: @JsonClass can't be applied to Outer.InnerClass: must not be an inner class")
}
// Annotation processors don't get called for local classes, so we don't have the opportunity to
// print an error message. Instead local classes will fail at runtime.
@Ignore
@Test fun localClassesNotSupported() {
val call = KotlinCompilerCall(temporaryFolder.root)
call.inheritClasspath = true
call.addService(Processor::class, JsonClassCodeGenProcessor::class)
call.addKt("source.kt", """
|import com.squareup.moshi.JsonClass
|
|fun outer() {
| @JsonClass(generateAdapter = true)
| class LocalClass(val a: Int)
|}
|""".trimMargin())
val result = call.execute()
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains(
"error: @JsonClass can't be applied to LocalClass: must not be local")
}
@Test fun objectDeclarationsNotSupported() {
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)
|object ObjectDeclaration {
| var a = 5
|}
|""".trimMargin())
val result = call.execute()
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains(
"error: @JsonClass can't be applied to ObjectDeclaration: must be a Kotlin class")
}
@Test fun objectExpressionsNotSupported() {
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)
|val expression = object : Any() {
| var a = 5
|}
|""".trimMargin())
val result = call.execute()
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
assertThat(result.systemErr).contains(
"error: @JsonClass can't be applied to expression\$annotations(): must be a Kotlin class")
}
} }

View File

@@ -378,7 +378,7 @@ class GeneratedAdaptersTest {
jsonAdapter.fromJson("{\"a\":null}") jsonAdapter.fromJson("{\"a\":null}")
fail() fail()
} catch (expected: JsonDataException) { } catch (expected: JsonDataException) {
assertThat(expected).hasMessage("Required property 'a' missing at \$") assertThat(expected).hasMessage("Unexpected null at \$.a")
} }
} }
@@ -526,6 +526,106 @@ class GeneratedAdaptersTest {
var b: Int = -1 var b: Int = -1
} }
@Test fun nonNullPropertySetToNullFailsWithJsonDataException() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(HasNonNullProperty::class.java)
try {
jsonAdapter.fromJson("{\"a\":null}")
fail()
} catch (expected: JsonDataException) {
assertThat(expected).hasMessage("Unexpected null at \$.a")
}
}
@JsonClass(generateAdapter = true)
class HasNonNullProperty {
var a: String = ""
}
@Test fun manyProperties32() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ManyProperties32::class.java)
val encoded = ManyProperties32(
101, 102, 103, 104, 105,
106, 107, 108, 109, 110,
111, 112, 113, 114, 115,
116, 117, 118, 119, 120,
121, 122, 123, 124, 125,
126, 127, 128, 129, 130,
131, 132)
val json = ("""
|{
|"v01":101,"v02":102,"v03":103,"v04":104,"v05":105,
|"v06":106,"v07":107,"v08":108,"v09":109,"v10":110,
|"v11":111,"v12":112,"v13":113,"v14":114,"v15":115,
|"v16":116,"v17":117,"v18":118,"v19":119,"v20":120,
|"v21":121,"v22":122,"v23":123,"v24":124,"v25":125,
|"v26":126,"v27":127,"v28":128,"v29":129,"v30":130,
|"v31":131,"v32":132
|}
|""").trimMargin().replace("\n", "")
assertThat(jsonAdapter.toJson(encoded)).isEqualTo(json)
val decoded = jsonAdapter.fromJson(json)!!
assertThat(decoded.v01).isEqualTo(101)
assertThat(decoded.v32).isEqualTo(132)
}
@JsonClass(generateAdapter = true)
class ManyProperties32(
var v01: Int, var v02: Int, var v03: Int, var v04: Int, var v05: Int,
var v06: Int, var v07: Int, var v08: Int, var v09: Int, var v10: Int,
var v11: Int, var v12: Int, var v13: Int, var v14: Int, var v15: Int,
var v16: Int, var v17: Int, var v18: Int, var v19: Int, var v20: Int,
var v21: Int, var v22: Int, var v23: Int, var v24: Int, var v25: Int,
var v26: Int, var v27: Int, var v28: Int, var v29: Int, var v30: Int,
var v31: Int, var v32: Int)
@Test fun manyProperties33() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ManyProperties33::class.java)
val encoded = ManyProperties33(
101, 102, 103, 104, 105,
106, 107, 108, 109, 110,
111, 112, 113, 114, 115,
116, 117, 118, 119, 120,
121, 122, 123, 124, 125,
126, 127, 128, 129, 130,
131, 132, 133)
val json = ("""
|{
|"v01":101,"v02":102,"v03":103,"v04":104,"v05":105,
|"v06":106,"v07":107,"v08":108,"v09":109,"v10":110,
|"v11":111,"v12":112,"v13":113,"v14":114,"v15":115,
|"v16":116,"v17":117,"v18":118,"v19":119,"v20":120,
|"v21":121,"v22":122,"v23":123,"v24":124,"v25":125,
|"v26":126,"v27":127,"v28":128,"v29":129,"v30":130,
|"v31":131,"v32":132,"v33":133
|}
|""").trimMargin().replace("\n", "")
assertThat(jsonAdapter.toJson(encoded)).isEqualTo(json)
val decoded = jsonAdapter.fromJson(json)!!
assertThat(decoded.v01).isEqualTo(101)
assertThat(decoded.v32).isEqualTo(132)
assertThat(decoded.v33).isEqualTo(133)
}
@JsonClass(generateAdapter = true)
class ManyProperties33(
var v01: Int, var v02: Int, var v03: Int, var v04: Int, var v05: Int,
var v06: Int, var v07: Int, var v08: Int, var v09: Int, var v10: Int,
var v11: Int, var v12: Int, var v13: Int, var v14: Int, var v15: Int,
var v16: Int, var v17: Int, var v18: Int, var v19: Int, var v20: Int,
var v21: Int, var v22: Int, var v23: Int, var v24: Int, var v25: 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)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@JsonQualifier @JsonQualifier
annotation class Uppercase annotation class Uppercase

View File

@@ -39,22 +39,6 @@ class KotlinCodeGenTest {
class DuplicateValue(var a: Int = -1, var b: Int = -2) class DuplicateValue(var a: Int = -1, var b: Int = -2)
@Ignore @Test fun nonNullPropertySetToNullFailsWithJsonDataException() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(HasNonNullProperty::class.java)
try {
jsonAdapter.fromJson("{\"a\":null}")
fail()
} catch (expected: JsonDataException) {
assertThat(expected).hasMessage("Non-null value a was null at \$")
}
}
class HasNonNullProperty {
var a: String = ""
}
@Ignore @Test fun repeatedValue() { @Ignore @Test fun repeatedValue() {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(RepeatedValue::class.java) val jsonAdapter = moshi.adapter(RepeatedValue::class.java)
@@ -323,167 +307,6 @@ class KotlinCodeGenTest {
A, B A, B
} }
@Ignore @Test fun interfacesNotSupported() {
val moshi = Moshi.Builder().build()
try {
moshi.adapter(Interface::class.java)
fail()
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessage("No JsonAdapter for interface " +
"com.squareup.moshi.KotlinJsonAdapterTest\$Interface (with no annotations)")
}
}
interface Interface
@Ignore @Test fun abstractClassesNotSupported() {
val moshi = Moshi.Builder().build()
try {
moshi.adapter(AbstractClass::class.java)
fail()
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessage(
"Cannot serialize abstract class com.squareup.moshi.KotlinJsonAdapterTest\$AbstractClass")
}
}
abstract class AbstractClass(val a: Int)
@Ignore @Test fun innerClassesNotSupported() {
val moshi = Moshi.Builder().build()
try {
moshi.adapter(InnerClass::class.java)
fail()
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessage(
"Cannot serialize non-static nested class com.squareup.moshi.KotlinCodeGenTest\$InnerClass")
}
}
inner class InnerClass(val a: Int)
@Ignore @Test fun localClassesNotSupported() {
class LocalClass(val a: Int)
val moshi = Moshi.Builder().build()
try {
moshi.adapter(LocalClass::class.java)
fail()
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessage("Cannot serialize local class or object expression " +
"com.squareup.moshi.KotlinJsonAdapterTest\$localClassesNotSupported\$LocalClass")
}
}
@Ignore @Test fun objectDeclarationsNotSupported() {
val moshi = Moshi.Builder().build()
try {
moshi.adapter(ObjectDeclaration.javaClass)
fail()
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessage("Cannot serialize object declaration " +
"com.squareup.moshi.KotlinJsonAdapterTest\$ObjectDeclaration")
}
}
object ObjectDeclaration {
var a = 5
}
@Ignore @Test fun objectExpressionsNotSupported() {
val expression = object : Any() {
var a = 5
}
val moshi = Moshi.Builder().build()
try {
moshi.adapter(expression.javaClass)
fail()
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessage("Cannot serialize local class or object expression " +
"com.squareup.moshi.KotlinJsonAdapterTest\$objectExpressionsNotSupported\$expression$1")
}
}
@Ignore @Test fun manyProperties32() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ManyProperties32::class.java)
val encoded = ManyProperties32(
101, 102, 103, 104, 105,
106, 107, 108, 109, 110,
111, 112, 113, 114, 115,
116, 117, 118, 119, 120,
121, 122, 123, 124, 125,
126, 127, 128, 129, 130,
131, 132)
val json = ("""
|{
|"v01":101,"v02":102,"v03":103,"v04":104,"v05":105,
|"v06":106,"v07":107,"v08":108,"v09":109,"v10":110,
|"v11":111,"v12":112,"v13":113,"v14":114,"v15":115,
|"v16":116,"v17":117,"v18":118,"v19":119,"v20":120,
|"v21":121,"v22":122,"v23":123,"v24":124,"v25":125,
|"v26":126,"v27":127,"v28":128,"v29":129,"v30":130,
|"v31":131,"v32":132
|}
|""").trimMargin().replace("\n", "")
assertThat(jsonAdapter.toJson(encoded)).isEqualTo(json)
val decoded = jsonAdapter.fromJson(json)!!
assertThat(decoded.v01).isEqualTo(101)
assertThat(decoded.v32).isEqualTo(132)
}
class ManyProperties32(
var v01: Int, var v02: Int, var v03: Int, var v04: Int, var v05: Int,
var v06: Int, var v07: Int, var v08: Int, var v09: Int, var v10: Int,
var v11: Int, var v12: Int, var v13: Int, var v14: Int, var v15: Int,
var v16: Int, var v17: Int, var v18: Int, var v19: Int, var v20: Int,
var v21: Int, var v22: Int, var v23: Int, var v24: Int, var v25: Int,
var v26: Int, var v27: Int, var v28: Int, var v29: Int, var v30: Int,
var v31: Int, var v32: Int)
@Ignore @Test fun manyProperties33() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(ManyProperties33::class.java)
val encoded = ManyProperties33(
101, 102, 103, 104, 105,
106, 107, 108, 109, 110,
111, 112, 113, 114, 115,
116, 117, 118, 119, 120,
121, 122, 123, 124, 125,
126, 127, 128, 129, 130,
131, 132, 133)
val json = ("""
|{
|"v01":101,"v02":102,"v03":103,"v04":104,"v05":105,
|"v06":106,"v07":107,"v08":108,"v09":109,"v10":110,
|"v11":111,"v12":112,"v13":113,"v14":114,"v15":115,
|"v16":116,"v17":117,"v18":118,"v19":119,"v20":120,
|"v21":121,"v22":122,"v23":123,"v24":124,"v25":125,
|"v26":126,"v27":127,"v28":128,"v29":129,"v30":130,
|"v31":131,"v32":132,"v33":133
|}
|""").trimMargin().replace("\n", "")
assertThat(jsonAdapter.toJson(encoded)).isEqualTo(json)
val decoded = jsonAdapter.fromJson(json)!!
assertThat(decoded.v01).isEqualTo(101)
assertThat(decoded.v32).isEqualTo(132)
assertThat(decoded.v33).isEqualTo(133)
}
class ManyProperties33(
var v01: Int, var v02: Int, var v03: Int, var v04: Int, var v05: Int,
var v06: Int, var v07: Int, var v08: Int, var v09: Int, var v10: Int,
var v11: Int, var v12: Int, var v13: Int, var v14: Int, var v15: Int,
var v16: Int, var v17: Int, var v18: Int, var v19: Int, var v20: Int,
var v21: Int, var v22: Int, var v23: Int, var v24: Int, var v25: 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)
// TODO(jwilson): resolve generic types? // TODO(jwilson): resolve generic types?
@Retention(RUNTIME) @Retention(RUNTIME)