Moshi: [Kotlin Codegen] Issue with generated adapter for Kotlin generic-classes

Created on 13 Jan 2019  Â·  8Comments  Â·  Source: square/moshi

I spent a lot of time to make Moshi works with my generics classes, but it still throws java.lang.RuntimeException on every attempt to serialize object to the json.

There is minimal example:

import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi

interface ParamsRequest

@JsonClass(generateAdapter = true)
class RpcRequest<out T : ParamsRequest>(
    val method: String,
    val params: T? = null
) {
    val id: String = "vsdv"
    val jsonrpc: String = "2.0"
}

@JsonClass(generateAdapter = true)
class SeedRequest(val key_type: String = "mnemonic") : ParamsRequest

inline fun <reified T> T.toJson(): String = Moshi.Builder().build()
    .adapter(T::class.java)
    .toJson(this)

fun runTest() {
    val request = RpcRequest("method", SeedRequest())
    println("json: [${request.toJson()}]")
}

Used dependecies:

com.squareup.moshi:moshi:1.8.0
com.squareup.moshi:moshi-kotlin-codegen:1.8.0

Stacktrace:

    java.lang.RuntimeException: Failed to find the generated JsonAdapter constructor for class com.example.paulg.myapplication.RpcRequest
        at com.squareup.moshi.internal.Util.generatedAdapter(Util.java:484)
        at com.squareup.moshi.StandardJsonAdapters$1.create(StandardJsonAdapters.java:60)
        at com.squareup.moshi.Moshi.adapter(Moshi.java:137)
        at com.squareup.moshi.Moshi.adapter(Moshi.java:97)
        at com.squareup.moshi.Moshi.adapter(Moshi.java:71)
        at com.example.paulg.myapplication.TestKt.runTest(Test.kt:34)
        at com.example.paulg.myapplication.MainActivity$onCreate$1.onClick(MainActivity.kt:19)
        at android.view.View.performClick(View.java:6597)
        at android.view.View.performClickInternal(View.java:6574)
        at android.view.View.access$3100(View.java:778)
        at android.view.View$PerformClick.run(View.java:25885)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6680)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.NoSuchMethodException: <init> [class com.squareup.moshi.Moshi]
        at java.lang.Class.getConstructor0(Class.java:2327)
        at java.lang.Class.getDeclaredConstructor(Class.java:2166)
        at com.squareup.moshi.internal.Util.generatedAdapter(Util.java:476)
        at com.squareup.moshi.StandardJsonAdapters$1.create(StandardJsonAdapters.java:60) 
        at com.squareup.moshi.Moshi.adapter(Moshi.java:137) 
        at com.squareup.moshi.Moshi.adapter(Moshi.java:97) 
        at com.squareup.moshi.Moshi.adapter(Moshi.java:71) 
        at com.example.paulg.myapplication.TestKt.runTest(Test.kt:34) 
        at com.example.paulg.myapplication.MainActivity$onCreate$1.onClick(MainActivity.kt:19) 
        at android.view.View.performClick(View.java:6597) 
        at android.view.View.performClickInternal(View.java:6574) 
        at android.view.View.access$3100(View.java:778) 
        at android.view.View$PerformClick.run(View.java:25885) 
        at android.os.Handler.handleCallback(Handler.java:873) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:193) 

Most helpful comment

Am still new to Moshi, do you have an example showing this is done?

Examples:

@JsonClass(generateAdapter = true)
data class BaseBean<T>(val code: Int, val msg: T)

@JsonClass(generateAdapter = true)
data class InfoData(val name: String = "")

val moshi = Moshi.Builder().build()

val type = Types.newParameterizedType(BaseBean::class.java, InfoData::class.java)
val jsonAdapter: JsonAdapter<BaseBean<InfoData>> = moshi.adapter(type)
val baseBean = jsonAdapter.fromJson(jsonStr)!!

All 8 comments

There is also generated adapter:

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import java.lang.NullPointerException
import java.lang.reflect.Type
import kotlin.Array
import kotlin.Boolean
import kotlin.String

class RpcRequestJsonAdapter<T : ParamsRequest>(moshi: Moshi, types: Array<Type>) : JsonAdapter<RpcRequest<T>>() {
    private val options: JsonReader.Options = JsonReader.Options.of("method", "params")

    private val stringAdapter: JsonAdapter<String> =
            moshi.adapter<String>(String::class.java, kotlin.collections.emptySet(), "method")

    private val nullableTParamsRequestAdapter: JsonAdapter<T?> =
            moshi.adapter<T?>(types[0], kotlin.collections.emptySet(), "params")

    override fun toString(): String = "GeneratedJsonAdapter(RpcRequest)"

    override fun fromJson(reader: JsonReader): RpcRequest<T> {
        var method: String? = null
        var params: T? = null
        var paramsSet: Boolean = false
        reader.beginObject()
        while (reader.hasNext()) {
            when (reader.selectName(options)) {
                0 -> method = stringAdapter.fromJson(reader) ?: throw JsonDataException("Non-null value 'method' was null at ${reader.path}")
                1 ->  {
                    params = nullableTParamsRequestAdapter.fromJson(reader)
                    paramsSet = true
                }
                -1 -> {
                    // Unknown name, skip it.
                    reader.skipName()
                    reader.skipValue()
                }
            }
        }
        reader.endObject()
        var result = RpcRequest<T>(
                method = method ?: throw JsonDataException("Required property 'method' missing at ${reader.path}"))
        result = RpcRequest<T>(
                method = method,
                params = if (paramsSet) params else result.params)
        return result
    }

    override fun toJson(writer: JsonWriter, value: RpcRequest<T>?) {
        if (value == null) {
            throw NullPointerException("value was null! Wrap in .nullSafe() to write nullable values.")
        }
        writer.beginObject()
        writer.name("method")
        stringAdapter.toJson(writer, value.method)
        writer.name("params")
        nullableTParamsRequestAdapter.toJson(writer, value.params)
        writer.endObject()
    }
}

Unfortunately you're asking Moshi to give you the JsonAdapter for RpcRequest instead of RpcRequest<SeedRequest>. Use Types.newParameterizedType(...) to do that.

I see now, thanks a lot. This helps me

How do you use Types.newParameterizedType(...) to do that as it is not in the documentation?

Am still new to Moshi, do you have an example showing this is done?

Tests. For usage questions please ask on stackoverflow with the Moshi tag

On Wed, Sep 18, 2019 at 1:45 PM Zakayo Thuku notifications@github.com
wrote:

Am still new to Moshi, do you have an example showing this is done?

—
You are receiving this because you commented.

Reply to this email directly, view it on GitHub
https://github.com/square/moshi/issues/787?email_source=notifications&email_token=AAKMJPTANXULFVTCJBIKE23QKJSKFA5CNFSM4GPVVCZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7A4CBY#issuecomment-532791559,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAKMJPRTBBWWB4XFYXQURBDQKJSKFANCNFSM4GPVVCZQ
.

Am still new to Moshi, do you have an example showing this is done?

Examples:

@JsonClass(generateAdapter = true)
data class BaseBean<T>(val code: Int, val msg: T)

@JsonClass(generateAdapter = true)
data class InfoData(val name: String = "")

val moshi = Moshi.Builder().build()

val type = Types.newParameterizedType(BaseBean::class.java, InfoData::class.java)
val jsonAdapter: JsonAdapter<BaseBean<InfoData>> = moshi.adapter(type)
val baseBean = jsonAdapter.fromJson(jsonStr)!!
Was this page helpful?
0 / 5 - 0 ratings