Promote Kotlin type-inferring APIs to the main Moshi package (round 2!) (#1202)

* Make moshi-root a kotlin project

* Move moshi kotlin extensions to moshi core

* Add appropriate experimental annotations

* Add nextAdapter helper

* Add explicit return type on addAdapter

* Expression body for adapter

* Use nextAdapter helper

* Opportunistically fix a couple Util warnings

* Add Types extensions

* Spotless

* Use extensions in more places for added coverage

* Apply java versions on any java plugin type

This way the kotlin projects get this too

* Fix circularAdapters test?

* Use java 8 in java for code gen too

* Fixup with CircularAdaptersTest

* Add coverage for remaining

* Remove nextAdapter

* Remove leftover function

* Use asserts

left checkNotNull for the contract

* boxIfPrimitive

* Fixup docs

* Copyright fixes

* Add parameterized addAdapter

* Switch to using native javaType API

* Spotless

* Back to 2019

* Spotless

* Use rawType extension

* Fix rebase issues
This commit is contained in:
Zac Sweers
2020-10-04 18:18:52 -04:00
committed by GitHub
parent f17e7c2584
commit 230c3d801f
17 changed files with 298 additions and 152 deletions

View File

@@ -17,6 +17,7 @@ package com.squareup.moshi.kotlin
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import org.junit.Test
class DefaultConstructorTest {
@@ -25,7 +26,7 @@ class DefaultConstructorTest {
val expected = TestClass("requiredClass")
val json =
"""{"required":"requiredClass"}"""
val instance = Moshi.Builder().build().adapter<TestClass>(TestClass::class.java)
val instance = Moshi.Builder().build().adapter<TestClass>()
.fromJson(json)!!
check(instance == expected) {
"No match:\nActual : $instance\nExpected: $expected"
@@ -36,7 +37,7 @@ class DefaultConstructorTest {
val expected = TestClass("requiredClass", "customOptional", 4, "setDynamic", 5, 6)
val json =
"""{"required":"requiredClass","optional":"customOptional","optional2":4,"dynamicSelfReferenceOptional":"setDynamic","dynamicOptional":5,"dynamicInlineOptional":6}"""
val instance = Moshi.Builder().build().adapter<TestClass>(TestClass::class.java)
val instance = Moshi.Builder().build().adapter<TestClass>()
.fromJson(json)!!
check(instance == expected) {
"No match:\nActual : $instance\nExpected: $expected"
@@ -47,7 +48,7 @@ class DefaultConstructorTest {
val expected = TestClass("requiredClass", "customOptional")
val json =
"""{"required":"requiredClass","optional":"customOptional"}"""
val instance = Moshi.Builder().build().adapter<TestClass>(TestClass::class.java)
val instance = Moshi.Builder().build().adapter<TestClass>()
.fromJson(json)!!
check(instance == expected) {
"No match:\nActual : $instance\nExpected: $expected"

View File

@@ -24,9 +24,10 @@ import com.squareup.moshi.JsonClass
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import com.squareup.moshi.Types
import com.squareup.moshi.adapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.squareup.moshi.kotlin.reflect.adapter
import com.squareup.moshi.rawType
import com.squareup.moshi.supertypeOf
import org.intellij.lang.annotations.Language
import org.junit.Assert.fail
import org.junit.Test
@@ -62,11 +63,11 @@ class DualKotlinTest(useReflection: Boolean) {
object : Factory {
override fun create(
type: Type,
annotations: MutableSet<out Annotation>,
annotations: Set<Annotation>,
moshi: Moshi
): JsonAdapter<*>? {
// Prevent falling back to generated adapter lookup
val rawType = Types.getRawType(type)
val rawType = type.rawType
val metadataClass = Class.forName("kotlin.Metadata") as Class<out Annotation>
check(rawType.isEnum || !rawType.isAnnotationPresent(metadataClass)) {
"Unhandled Kotlin type in reflective test! $rawType"
@@ -446,7 +447,7 @@ class DualKotlinTest(useReflection: Boolean) {
@Test fun typeAliasUnwrapping() {
val adapter = moshi
.newBuilder()
.add(Types.supertypeOf(Int::class.javaObjectType), moshi.adapter<Int>())
.add(supertypeOf<Int>(), moshi.adapter<Int>())
.build()
.adapter<TypeAliasUnwrapping>()

View File

@@ -20,7 +20,7 @@ package com.squareup.moshi.kotlin.codegen
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.adapter
import com.squareup.moshi.adapter
import org.intellij.lang.annotations.Language
import org.junit.Test

View File

@@ -26,9 +26,8 @@ import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import com.squareup.moshi.Types
import com.squareup.moshi.adapter
import com.squareup.moshi.internal.NullSafeJsonAdapter
import com.squareup.moshi.kotlin.reflect.adapter
import org.intellij.lang.annotations.Language
import org.junit.Assert.assertNull
import org.junit.Assert.fail
@@ -299,13 +298,7 @@ class GeneratedAdaptersTest {
@Test
fun nullableTypeParams() {
val adapter = moshi.adapter<NullableTypeParams<Int>>(
Types.newParameterizedTypeWithOwner(
GeneratedAdaptersTest::class.java,
NullableTypeParams::class.java,
Int::class.javaObjectType
)
)
val adapter = moshi.adapter<NullableTypeParams<Int>>()
val nullSerializing = adapter.serializeNulls()
val nullableTypeParams = NullableTypeParams(
@@ -597,7 +590,7 @@ class GeneratedAdaptersTest {
@Test fun multipleTransientConstructorParameters() {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java)
val jsonAdapter = moshi.adapter<MultipleTransientConstructorParameters>()
val encoded = MultipleTransientConstructorParameters(3, 5, 7)
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
@@ -1346,6 +1339,7 @@ class GeneratedAdaptersTest {
@Test fun typesSizeCheckMessages_noArgs() {
try {
// Note: This is impossible to do if you use the reified adapter extension!
moshi.adapter(MultipleGenerics::class.java)
fail("Should have failed to construct the adapter due to missing generics")
} catch (e: RuntimeException) {

View File

@@ -17,6 +17,7 @@ package com.squareup.moshi.kotlin.codegen
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import org.intellij.lang.annotations.Language
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -41,7 +42,7 @@ class MultipleMasksTest {
val json =
"""{"arg50":500,"arg3":34,"arg11":11,"arg65":67}"""
val instance = Moshi.Builder().build().adapter(MultipleMasks::class.java)
val instance = Moshi.Builder().build().adapter<MultipleMasks>()
.fromJson(json)!!
assertEquals(instance.arg2, 2)

View File

@@ -1,106 +0,0 @@
/*
* Copyright (C) 2019 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
*
* https://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.kotlin.reflect
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.internal.NonNullJsonAdapter
import com.squareup.moshi.internal.NullSafeJsonAdapter
import java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.typeOf
/**
* @return a [JsonAdapter] for [T], creating it if necessary. Note that while nullability of [T]
* itself is handled, nested types (such as in generics) are not resolved.
*/
inline fun <reified T> Moshi.adapter(): JsonAdapter<T> {
return adapter(typeOf<T>())
}
inline fun <reified T> Moshi.Builder.addAdapter(adapter: JsonAdapter<T>) = add(typeOf<T>().toType(), adapter)
/**
* @return a [JsonAdapter] for [ktype], creating it if necessary. Note that while nullability of
* [ktype] itself is handled, nested types (such as in generics) are not resolved.
*/
fun <T> Moshi.adapter(ktype: KType): JsonAdapter<T> {
val adapter = adapter<T>(ktype.toType())
return if (adapter is NullSafeJsonAdapter || adapter is NonNullJsonAdapter) {
// TODO CR - Assume that these know what they're doing? Or should we defensively avoid wrapping for matching nullability?
adapter
} else if (ktype.isMarkedNullable) {
adapter.nullSafe()
} else {
adapter.nonNull()
}
}
@PublishedApi
internal fun KType.toType(allowPrimitives: Boolean = true): Type {
classifier?.let {
when (it) {
is KTypeParameter -> throw IllegalArgumentException("Type parameters are not supported")
is KClass<*> -> {
val javaType = if (allowPrimitives) {
it.java
} else {
it.javaObjectType
}
if (javaType.isArray) {
return Types.arrayOf(javaType.componentType)
}
return if (arguments.isEmpty()) {
javaType
} else {
val typeArguments = arguments.toTypedArray { it.toType() }
val enclosingClass = javaType.enclosingClass
return if (enclosingClass != null) {
Types.newParameterizedTypeWithOwner(enclosingClass, javaType, *typeArguments)
} else {
Types.newParameterizedType(javaType, *typeArguments)
}
}
}
else -> throw IllegalArgumentException("Unsupported classifier: $this")
}
}
// Can happen for intersection types
throw IllegalArgumentException("Unrepresentable type: $this")
}
internal fun KTypeProjection.toType(): Type {
val javaType = type?.toType(allowPrimitives = false) ?: return Any::class.java
return when (variance) {
null -> Any::class.java
KVariance.INVARIANT -> javaType
KVariance.IN -> Types.subtypeOf(javaType)
KVariance.OUT -> Types.supertypeOf(javaType)
}
}
private inline fun <T, reified R> List<T>.toTypedArray(mapper: (T) -> R): Array<R> {
return Array(size) {
mapper.invoke(get(it))
}
}

View File

@@ -26,7 +26,7 @@ import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import com.squareup.moshi.Types
import com.squareup.moshi.adapter
import org.assertj.core.api.Assertions
import org.junit.Assert.fail
import org.junit.Test
@@ -448,7 +448,7 @@ class KotlinJsonAdapterTest {
fail()
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessageThat().isEqualTo(
"Platform class kotlin.Triple in kotlin.Triple<java.lang.Object, java.lang.Object, java.lang.Object> requires explicit JsonAdapter to be registered"
"Platform class kotlin.Triple in kotlin.Triple<?, ?, ?> requires explicit JsonAdapter to be registered"
)
}
}
@@ -857,13 +857,7 @@ class KotlinJsonAdapterTest {
@Test fun genericTypes() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val stringBoxAdapter = moshi.adapter<Box<String>>(
Types.newParameterizedTypeWithOwner(
KotlinJsonAdapterTest::class.java,
Box::class.java,
String::class.java
)
)
val stringBoxAdapter = moshi.adapter<Box<String>>()
assertThat(stringBoxAdapter.fromJson("""{"data":"hello"}""")).isEqualTo(Box("hello"))
assertThat(stringBoxAdapter.toJson(Box("hello"))).isEqualTo("""{"data":"hello"}""")
}
@@ -872,18 +866,7 @@ class KotlinJsonAdapterTest {
@Test fun nestedGenericTypes() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val type = Types.newParameterizedTypeWithOwner(
KotlinJsonAdapterTest::class.java,
NestedGenerics::class.java,
String::class.java,
Int::class.javaObjectType,
Types.newParameterizedTypeWithOwner(
KotlinJsonAdapterTest::class.java,
Box::class.java,
String::class.java
)
)
val adapter = moshi.adapter<NestedGenerics<String, Int, Box<String>>>(type).indent(" ")
val adapter = moshi.adapter<NestedGenerics<String, Int, Box<String>>>().indent(" ")
val json =
"""
|{