Moshi and Retrofit2: Unable to read service response - android

I'm trying to read a json response from a webservice, but without success.
This is the json i receive:
{
"rsp": {
"#code": "0",
"#message": ""
},
"listOfStrings":[]
}
And this is relative data class where i parse response
data class Response(
val rsp : Rsp,
val listOfStrings : List<String>
)
data class Rsp(
#Json(name = "#code")
val code : String,
#Json(name = "#message")
val message : String
)
But it seems that moshi for some reason it's not able to parse json into object, because i always get Response object with all null fields.
So what's wrong? May the "#" character of json response fields cause problems?
UPDATE
Now i can parse correctly response by change #Json annotation into #field:Json:
data class Rsp(
#field:Json(name = "#code")
val code : String,
#field:Json(name = "#message")
val message : String
)
But i'm curious to know why it works.

#field:Json is required if you want moshi-kotlin to work with proguard according to the discussion here: https://github.com/square/moshi/issues/315

Try this model and let me know if it works:
#Parcelize
data class Response(
#Json(name = "rsp")
val rsp: Rsp,
#Json(name = "listOfStrings")
val listOfStrings: List<String>
) : Parcelable {
#Parcelize
data class Rsp(
#Json(name = "#code")
val code: String,
#Json(name = "#message")
val message: String
) : Parcelable
}
Edit:
If it didn't work, try to add back-slash behind those field names that have #.
Like: #Json(name = "\#code").
UPDATE AFTER QUESTION GOT UPDATE:
You need to add moshi-kotlin dependency and then using KotlinJsonAdapterFactory
val moshi = Moshi.Builder()
// ... add your own JsonAdapters and factories ...
.add(KotlinJsonAdapterFactory())
.build()
Then moshi couldn't ignore #Json.

Related

Body property ignored when generating json using Moshi

I have a simple class
#JsonClass(generateAdapter = true)
data class Person(
val name: String,
val age: Int
) {
val country: String = "None"
}
but when converting to json, Moshi skips country property.
How to include it in the resulting json?
Sadly, it's not possible.
More info and explanation here.

Moshi-JsonDataException: Required value '%s' missing at $

I was working with Moshi to pull JSON data from an API and map it to my DTO Data classes when I encountered this error:
FATAL EXCEPTION: main Process: com.plcoding.weatherapp, PID: 9706
com.squareup.moshi.JsonDataException: Required value 'weatherData'
missing at $ at
com.squareup.moshi.internal.Util.missingProperty(Util.java:649) at
com.squareup.moshi.kotlin.reflect.KotlinJsonAdapter.fromJson(KotlinJsonAdapter.kt:103)
at
com.squareup.moshi.internal.NullSafeJsonAdapter.fromJson(NullSafeJsonAdapter.java:41)
My DTO are annotated with #field:Json(name = "xyz")
#JsonClass(generateAdapter = true)
data class WeatherDataDTO(
#field:Json(name = "time")
val times: List<String>,
#field:Json(name = "temperature_2m")
val temperatures: List<Double>)
I have enclosed the above DTO in another DTO.
JsonClass(generateAdapter = true)
data class WeatherDTO(
#field:Json(name = "hourly")
val weatherData: WeatherDataDTO
)
I am using the latest Retrofit and Moshi Libs on Kotlin 1.6.10
// Retrofit, Core Moshi JSON Library and Moshi's Kotlin support and converter factory
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.moshi:moshi:1.12.0"
implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
My Json endpoint looks this:
{
"latitude": 42,
"longitude": 36,
"generationtime_ms": 0.3489255905151367,
"hourly": {
"time": [],
"temperature_2m": []
.....
}
Have struggled with this error for days but cannot find a solution.
Removing #field:Json() in favor of just #Json(name = "xyz") did the trick for me.
My DTO now looks like this and is working fine!
#JsonClass(generateAdapter = true)
data class WeatherDataDTO(
#Json(name = "time")
val times: List<String>,
#Json(name = "temperature_2m")
val temperatures: List<Double>)
For more info on #field:Json(name = "xyz") vs #Json(name = "xyz") you can review this StackOverflow question.
I also found this article to be helpful in case the Moshi-JSON Exception persists.
Happy Coding ...
In my case I was migrating from gson to moshi and first I got the exception:
java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.xxx.Spot. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact.
Then I resolved it with adding #JsonClass(generateAdapter = true) on data class like your WeatherDataDTO that you did and right after that came the exception you mentioned in this post.
I have done what you posted in your answer, added the #Json(name) but because my data class fields that was getting the errors were null, I was getting that same error.
In my data class I had somewhat like this:
#JsonClass(generateAdapter = true)
data class WeatherDataDTO(
#Json(name = "time")
val times: List<OtherDataClass>
)
And the OtherDataClass:
#JsonClass(generateAdapter = true)
data class OtherDataClass(
#Json(name = "time")
val problematicString: String, //This was causing the problem if it was null
//and the String was non-nullable(without a "?" questionmark beside of String)
)
What I did in OtherDataClass was making problematicString nullable:
#JsonClass(generateAdapter = true)
data class OtherDataClass(
#Json(name = "problemString")
val problematicString: String? = null, // Made it nullable and initialized it as a null.
//...other variables...
)
Also I got the information from this stack overflow question
I hope this helps someone with the same exception.

Moshi/retrofit error with one single Int field after enabling R8 minify

Having trouble with parsing one single field from JSON response after enabling minify, with minify disabled all works correctly:
retrofit API call:
#FormUrlEncoded
#POST("/api/test")
fun test(#Field <some String fields>
): Observable<Response<TestListing>>
wrapped in repo
override fun test(<some String fields>): Observable<Response<TestListing>> {
return api.test(<some String fields>)
.subscribeOn(schedulers.io())
}
model:
data class TestListing (
#Json(name = "success") val success:Int,
#Json(name = "user") val user: TestUser?
)
TestUser class
data class TestUser(
#Json(name = "id") val id: Int,
#Json(name = "email") val email: String,
#Json(name = "name") val name: String,
#Json(name = "key") val remix_userkey: String,
#Json(name = "downloads_limit") val downloads_limit: Int?,
<some other fields>
)
and finally calling it in a viewModel
fun test(<some String fields>){
compositeDisposable.add(testRepo.test(<some String variables>)
.subscribeOn(schedulers.io())
.observeOn(schedulers.main())
.subscribe ({ testList ->
testListDebug.postValue(testList)
if (testList.isSuccessful) user.postValue(userList.body()?.user)
else {<some error posting>}
})
{ throwable -> <some actions>})
}
So without minifyEnabled it parses this JSON
{"success":1,"user":{"id":"123456","email":"test#test.com","name":"Test","remix_userkey":"abcd123abcd","downloads_limit":15}}
correctly, after I enable minify - id field is always 0.
Same JSON, but somehow it wraps in retrofit Response already with id=0 in the body(all other fields are parsed correctly)
example of testListDebug value from debugger after API call
Tried adding all library rules in proguard-rules.pro file, but with no effect; also tried adding #Keep annotation to TestUser class and renaming id field
Where I can dig from here? Is it something regarding Moshi or Retrofit/Okhttp?
Figured it out - needed to keep a custom moshi annotation class, which was used for parsing some field(which sometimes Int and sometimes Boolean) in other API calls and which was not used here. After adding keep annotation to it id is parsed fine
Very strange behavior since this annotation was not used here

POSTING a custom object with Retrofit2 issue

I'm currently making my first custom Android app for a project and I can't resolve the following issue. (I made a lot of research but didn't find anything like this)
2020-02-15 11:41:30.075 10337-10337/com.example.quickmatch I/SigninFragmentViewModel: Required value 'surname' missing at $post
I'm trying to post this custom object to my backend server online :
#JsonClass(generateAdapter = true)
data class PlayerObject(
val id : Int?,
#Json(name = "surname") val surname : String,
#Json(name = "first_name") val firstName : String,
val pseudo : String,
#Json(name = "mdp") val password : String,
#Json(name = "mail_address") val mailAddress : String,
#Json(name = "phone_number") val phoneNumber : String?,
#Json(name = "scored_goals") val scoredGoals : Int,
#Json(name = "conceded_goals") val concededGoals : Int,
#Json(name = "matches_played") val matchesPlayed : Int,
val victories : Int,
val avatar : String?,
val bio : String?
)
I'm using Retrofit2 and Moshi with coroutines :
implementation "com.squareup.retrofit2:retrofit:2.5.0"
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"
implementation "com.squareup.retrofit2:converter-moshi:2.5.0"
Here is my Retrofit instance :
/* Create Moshi object which will parse the responses */
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/* Retrofit builder with converter for response and base url */
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(BASE_URL)
.build()
To send my POST request I made this method (It should send me back the player created if successful) :
#POST("...")
fun addPlayer(#Body player: PlayerObject) : Deferred<PlayerObject>
And I call it passing this Object :
var newPlayerObject = PlayerObject(null, name, firstName, pseudo, password, mailAddress, phoneNumber, 0, 0, 0, 0, null, null )
I am getting the non-null values from EditTexts with a basic binding.editText.text.toString()
The big problem is that I logged every value (name, surname, etc...) both in my ViewModel before the request and in my UI in the button onClickListener that triggers the ViewModel and everything looks fine and matches what I type in the Edits but when the app posts it seems that every attribute of the object is set to null. Since surname is not nullable I get the previous error. I tried to make it nullable and it is the same issue with the next attributes.
Also note that passing an object with every attribute null works.
I've found specifying use-site targets helpful for fields in a Kotlin data class.
What happens if you try modifying your annotations to the following format?
#field:Json(name = "surname") val surname : String
I finally fixed this issue. The problem was created by the phone_number field which I wanted to be optional. Indeed, when the corresponding EditText was empty when posting, the value wasn't put to null but something "empty" (not just ""), that explain the parsing error. So I just added a condition to set this field at null if the EditText isn't filled.

Retrofit - Android - Handling multiple type keys and values with gson

So I've been having some trouble with an api that I want to consume from my application when dealing with multiple types from the keys and values of the response json.
Let me show you the json response:
{
"error":[
],
"result":{
"field1":[
[
1544258160,
"57.15",
"57.15",
"57.15",
"57.15",
"0.00",
"0.00000000",
0
],
[
1544258220,
"56.89",
"56.89",
"56.89",
"56.89",
"56.89",
"2.94406281",
1
]
],
"field2":1544301240
}
}
and here is the representation of the pojo class:
data class Response(val error: List<String>, val result: LinkedTreeMap<String, List<List<Result>>>)
data class Result(
val time: Double,
val open: String,
val high: String,
val low: String,
val close: String,
val vwap: String,
val volume: String,
val count: Double
)
I know that the current structure fails to represent the json format. but I have run out of ideas.
btw the stack error is saying this:
Expected BEGIN_OBJECT but was NUMBER
edit: adding a bit more context
I'm using Gsonconverter for the retrofit builder.
val retrofit = Retrofit.Builder().baseUrl(API_URL).client(client)
.addConverterFactory(GsonConverterFactory.create()).build()
You could build a Gson JsonDeserializer for the filed1, filed2 types.
It is more manually written code, but this way you can check the type of the filed and call the right deserializer.
yeah,what make the error is your response field(result),it's different type.however.you just use the first type Response(val error: List<String>, val result:LinkedTreeMap<String, List<List<Result>>> receive all the json.
in my view,you can use below ways to solve it.
First,redefine you receive model,maybe you can use the Gson JsonObject and when deal with value remember check the type.
Second,discuss with your background server engineer,agree on the every type response.may be the "field2" can be "field2:[[]]"
Next,change your model.may be you define below it
#Serialized("field1")
LinkedTreeMap<String, List<List<Result>>> field;
#Serialized("field2")
String fields;
hope to help you.
Your problem is here:
"field2":1544301240
You defined like below:
val result: LinkedTreeMap<String, List<List<Result>>>
but instead of that you get number!!!
EDIT
Just try this:
data class Response(val error: List<String>, val result: YourModel)
data class YourModel(val field1: LinkedTreeMap<String, List<List<Result>>>, val field2: Double)
data class Result(
val time: Double,
val open: String,
val high: String,
val low: String,
val close: String,
val vwap: String,
val volume: String,
val count: Double
)
it has been a while but still i already came to a conclusion of what i need.
data class OHLCResponse(
private val error: List<String>,
val result: LinkedTreeMap<String, Any>)
turns out that on the LinkedTreeMap class i just needed to pass the second class type parameter as Any and it can be down casted to anything that you need.
also i leave here a nice tool that can help you map your json responses into kotlin plain objects/DTOs:
https://app.quicktype.io/
thanks all for your responses.
I solved the problem with such a solution in adapter class .
override fun getItemCount(): Int = leagueTableList[0].size
override fun onBindViewHolder(holder: LeagueTableViewHolder, position: Int) {
val gs = Gson()
val js = gs.toJson(leagueTableList[0][position])
val standing = gs.fromJson(js, Standing::class.java)
holder.view.table = standing
holder.bind(standing,onItemClick)
}

Categories

Resources