Предположим, у меня есть интерфейс IRunnable
и две реализации Cat
и Dog
:
interface IRunnable {
fun run()
}
class Cat : IRunnable {
override fun run() { println("cat running") }
}
class Dog : IRunnable {
override fun run() { println("dog running") }
}
И есть интерфейс, который преобразует IRunnable
в Map<String,String>
с ключом = runnable
interface IMapConverter<T> {
fun getMap(impl : T) : Map<String , String>
fun getImpl(map : Map<String , String>) : T?
}
class RunnableConverter : IMapConverter<IRunnable> {
private val key = "runnable"
override fun getMap(impl: IRunnable): Map<String, String> {
val value = when(impl) {
is Cat -> "C"
is Dog -> "D"
else -> throw RuntimeException("error")
}
return mapOf(key to value)
}
override fun getImpl(map: Map<String, String>): IRunnable? {
return map[key]?.let { value ->
when (value) {
"C" -> Cat()
"D" -> Dog()
else -> throw RuntimeException("error")
}
}
}
}
Затем с помощью moshi я хочу, чтобы IMapConverter
стал JsonAdapter
, поэтому я добавляю функцию getAdapter()
внутри RunnableConverter
:
fun getAdapter() : JsonAdapter<IRunnable> {
return object : JsonAdapter<IRunnable>() {
@ToJson
override fun toJson(writer: JsonWriter, runnable: IRunnable?) {
runnable?.also { impl ->
writer.beginObject()
getMap(impl).forEach { (key , value) ->
writer.name(key).value(value)
}
writer.endObject()
}
}
@FromJson
override fun fromJson(reader: JsonReader): IRunnable? {
reader.beginObject()
val map = mutableMapOf<String , String>().apply {
while (reader.hasNext()) {
put(reader.nextName() , reader.nextString())
}
}
val result = getImpl(map)
reader.endObject()
return result
}
}
}
Это работает хорошо :
@Test
fun testConverter1() {
val converter = RunnableConverter()
val moshi = Moshi.Builder()
.add(converter.getAdapter())
.build()
val adapter = moshi.adapter<IRunnable>(IRunnable::class.java)
adapter.toJson(Dog()).also { json ->
assertEquals("""{"runnable":"D"}""" , json)
adapter.fromJson(json).also { runnable ->
assertTrue(runnable is Dog)
}
}
}
Но когда я хочу экстернализовать адаптер для функции расширения:
fun <T> IMapConverter<T>.toJsonAdapter() : JsonAdapter<T> {
return object : JsonAdapter<T>() {
@ToJson
override fun toJson(writer: JsonWriter, value: T?) {
value?.also { impl ->
writer.beginObject()
getMap(impl).forEach { (key , value) ->
writer.name(key).value(value)
}
writer.endObject()
}
}
@FromJson
override fun fromJson(reader: JsonReader): T? {
reader.beginObject()
val map = mutableMapOf<String , String>().apply {
while (reader.hasNext()) {
put(reader.nextName() , reader.nextString())
}
}
val result = getImpl(map)
reader.endObject()
return result
}
}
}
И когда я хочу протестировать функцию:
@Test
fun testConverter2() {
val converter = RunnableConverter()
val moshi = Moshi.Builder()
.add(converter.toJsonAdapter()) // this is extension function
.build()
val adapter = moshi.adapter<IRunnable>(IRunnable::class.java)
adapter.toJson(Dog()).also { json ->
logger.info("json of dog = {}", json)
assertEquals("""{"runnable":"D"}""" , json)
adapter.fromJson(json).also { runnable ->
assertTrue(runnable is Dog)
}
}
}
Компилируется нормально, но сообщает об ошибке:
java.lang.IllegalArgumentException: No JsonAdapter for interface moshi.IRunnable (with no annotations)
at com.squareup.moshi.Moshi.adapter(Moshi.java:148)
at com.squareup.moshi.Moshi.adapter(Moshi.java:98)
at com.squareup.moshi.Moshi.adapter(Moshi.java:72)
at moshi.MoshiTest.testConverter2(MoshiTest.kt:39)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Я пытаюсь установить точку останова на строке 137 в Moshi.java https://github.com/square/moshi/blob/40a829ef181e7097087ec0e95cdcf3e3e3fbba3156/moshi/src/main/java/com/squareup/moshi/Moshi.java#
А это скриншот отладки:
ОК (определено в RunnableConverter
):
А это общая версия, созданная функцией расширения:
Вроде из-за стирания типа не может работать ... Есть ли способ обойти это?
Для всех, кому это интересно, полный код находится здесь: https://gist.github.com/smallufo/985db4719c5434a5f57f06b14011de78
среда :
<dependency>
<groupId>com.squareup.moshi</groupId>
<artifactId>moshi-kotlin</artifactId>
<version>1.9.2</version>
</dependency>
Спасибо.