What kind of issue is this?
[ ] Question. This issue tracker is not the place for questions. If you want to ask how to do
something, or to understand why something isn't working the way you expect it to, use Stack
Overflow. https://stackoverflow.com/questions/tagged/retrofit
[ ] Bug report. If you鈥檝e found a bug, spend the time to write a failing test. Bugs with tests
get fixed. Here鈥檚 an example: https://gist.github.com/swankjesse/6608b4713ad80988cdc9
[X] Feature Request. Start by telling us what problem you鈥檙e trying to solve. Often a solution
already exists! Don鈥檛 send pull requests to implement new features without first getting our
support. Sometimes we leave features out on purpose to keep the project small.
Description of the issue:
I'm trying to achieve a scenario that is very common, but with Moshi isn't so trivial as I thought (I'm coming from Gson, that is simpler to use, but I'm aware of its many disadvantages).
I have endpoint definitions that use generic types, like this:
@GET("api.json")
suspend fun <T> test(): Result<T>
This is used because Result<T> contains a standard structure, basically as defined below:
@JsonClass(generateAdapter = true)
data class Result<T> (
var data: T? = null,
var timestamp: OffsetDateTime? = null
)
As you may see, it is used to track common information (like the server timestamp and, eventually, in the future, may have the ID of the request to match logs or something like that).
But the problem is that Moshi adapter for this class isn't called how it should by Retrofit (I explain below), which results in this error:
2020-01-18 15:38:59.913 7097-7202/com.example.myapplication E/MainActivity: Error handling JSON data:
java.lang.IllegalArgumentException: Unable to create converter for com.example.myapplication.api.Result<T>
for method RemoteService.test
at retrofit2.Utils.methodError(Utils.java:53)
at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:115)
at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:82)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:37)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:192)
at retrofit2.Retrofit$1.invoke(Retrofit.java:149)
at java.lang.reflect.Proxy.invoke(Proxy.java:813)
at $Proxy4.test(Unknown Source)
at com.example.myapplication.MainActivity.handleWithMoshiRetrofitConverter(MainActivity.kt:72)
at com.example.myapplication.MainActivity$onCreate$1$1.invokeSuspend(MainActivity.kt:48)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
Caused by: java.lang.IllegalArgumentException: No JsonAdapter for T (with no annotations)
for T data
for com.example.myapplication.api.Result<T>
at com.squareup.moshi.Moshi$LookupChain.exceptionWithLookupStack(Moshi.java:349)
at com.squareup.moshi.Moshi.adapter(Moshi.java:150)
at com.example.myapplication.api.ResultJsonAdapter.<init>(ResultJsonAdapter.kt:29)
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
at com.squareup.moshi.internal.Util.generatedAdapter(Util.java:540)
at com.squareup.moshi.StandardJsonAdapters$1.create(StandardJsonAdapters.java:60)
at com.squareup.moshi.Moshi.adapter(Moshi.java:138)
at com.squareup.moshi.Moshi.adapter(Moshi.java:98)
at retrofit2.converter.moshi.MoshiConverterFactory.responseBodyConverter(MoshiConverterFactory.java:91)
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:352)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:335)
at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:113)
... 14 more
Caused by: java.lang.IllegalArgumentException: No JsonAdapter for T (with no annotations)
at com.squareup.moshi.Moshi.adapter(Moshi.java:148)
... 25 more
Firstly, I thought the problem was with Moshi generated adapter. But then I made a simple test code that uses Moshi directly to parse the text and then try the same with a Retrofit call. The Moshi part of handling it is a little bit confusing (maybe the generated adapter code could be improved for this kind of scenarios), but it works very well.
So I ended up thinking that this may be the way Retrofit call Moshi converter. Any thoughts or ideas?
PS: I know I could create a response model for each of my response possibilities, but I really don't think that this would be a solution. I don't see, at least right now, why Retrofit + Moshi couldn't handle this kind of use case (except for the fact that it isn't implemented yet, which seems to be the case).
T is not resolvable at runtime therefore this will never work (with any serialization mechanism). The fact that Retrofit didn't reject your function is a bug. It never should have even gotten far enough to invoke Moshi.
Hello, @JakeWharton. Thanks for your soon reply!
There is no way to implement this, then? If there is no way to figure out T to call Moshi (or any other library), I can't see a way to achieve it from Retrofit helpers, indeed.
Thanks for the attention!
Hello, @JakeWharton. Thanks for your soon reply!
There is no way to implement this, then? If there is no way to figure out T to call Moshi (or any other library), I can't see a way to achieve it from Retrofit helpers, indeed.
- Any suggestions on how to achieve something like that without using generics in Retrofit definitions?
- Maybe Retrofit could warn somehow, in compilation time, that this is not a valid interface construction to map a remote service (if possible and not difficult to implement).
Thanks for the attention!
Have you tried creating an instance of the generic class with the specific T type in mind and then passing the class of that ? This way you have a new class where T is defined.
Also another option is to replace T with Any/Object and convert it after the matter
Thanks for the suggestion, @20ali20.
I ended up, for now, using Gson as parser. I read all over that it isn't a good library for many reasons, but it is more flexible than Moshi in some use cases.
Will try to replace Gson at some point and your suggestion will be taken into account!
I will close the issue, as it is answered and there is no point to keep it open (I think).
Thanks for the attention of you all.
Exactly same problem here!!
Faced same issue...
Faced similar problem, I have decided to use gson despite its shortcomings
No luck, gotta use Gson for a while now.
Really there is not an option for this ?
Most helpful comment
Thanks for the suggestion, @20ali20.
I ended up, for now, using Gson as parser. I read all over that it isn't a good library for many reasons, but it is more flexible than Moshi in some use cases.
Will try to replace Gson at some point and your suggestion will be taken into account!
I will close the issue, as it is answered and there is no point to keep it open (I think).
Thanks for the attention of you all.