Moshi map nested JSON value to field - android

Is there any way to map nested JSON value to field without additional classes? I have a JSON response
{
"title": "Warriors",
"artist": "Imagine Dragons",
"apple_music": {
"url": "https://music.apple.com/us/album/warriors/1440831203?app=music&at=1000l33QU&i=1440831624&mt=1",
"discNumber": 1,
"genreNames": [
"Alternative",
"Music"
],
}
}
But from apple_music I need only url value. So I decided to create Kotlin data class and tried option with #Json annotation
data class Song(
val title: String,
val artist: String,
#Json(name = "apple_music.url")
val appleMusicUrl: String
)
However, this doesn't work.
It throws an exception at runtime
Required value 'appleMusicUrl' (JSON name 'apple_music.url') missing at $
The code below is working
data class Song(
val title: String,
val artist: String,
#Json(name = "apple_music")
val appleMusic: AppleMusic
)
data class AppleMusic(val url: String)
I have several nested values and creating extra classes for them is quite overblowing. Is there any better ways than creating nested class for apple_music node?

One way you can do this is with alternate type adapters using #JsonQualifier. For example:
#Retention(RUNTIME)
#JsonQualifier
annotation class AppleMusicUrl
data class Song(
val title: String,
val artist: String,
#AppleMusicUrl
val appleMusicUrl: String
)
#FromJson
#AppleMusicUrl
fun fromJson(json: Map<String, Any?>): String {
return json.getValue("url") as String
}

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/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

Embedded annotation can't save a list of data class into room database

I have a list for embedded data class and inside that, I have again another data class and when use #Embeded and #ColumnInfo for embedded column got this error:
I read this question and answer and try it but got below error.
#Entity(tableName = "venues_table")
data class Venue(
#Embedded
val categories: List<Category>,
#PrimaryKey
val id: String,
#Embedded
val location: Location,
val name: String
)
data class Category(
#Embedded
val icon: Icon,
#ColumnInfo(name = "id_category")
val id: String,
#ColumnInfo(name = "name_category")
val name: String,
#ColumnInfo(name = "plural_name")
val pluralName: String,
#ColumnInfo(name = "primary")
val primary: Boolean,
#ColumnInfo(name = "short_name")
val shortName: String
)
e: error: Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - java.util.List
may please help me to fix this
you can't use "List" type in entities without type converter(see the official document https://developer.android.com/training/data-storage/room/referencing-data) you must use type converter for "List", "bitmap" etc. I wish It's useful for you.

Moshi and Retrofit2: Unable to read service response

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.

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