Cannot Deserialize Generic Type T Moshi - android

Sorry if this is a basic question, but i'm new to Moshi.
So, I have a class with Generic Type Paramter as follows:
class BaseResponse<T> {
#Json(name = "message")
var message: String? = null
#Json(name = "data")
var data: T? = null
#Json(name = "meta")
var meta: JsonObject? = null
#Json(name = "error")
var error: ErrorResponse? = null
}
In GSON, this is done automatically and it works as long as i provide #SerializedName("data") and extends BaseResponse in my retrofit method. But it returns error with moshi as i could not deserialize BaseResponse. How can i fix this?

Both base class and class that replace generic type in code must be annotated with #JsonClass(generateAdapter = true). Please notice that the most important thing in order json to be converted correctly to a model class such as this, is to declare fields at base class as var and NOT as val. Please look the example below:
#JsonClass(generateAdapter = true)
class BaseResponse<T> {
#Json(name = "message")
var message: String? = null
#Json(name = "data")
var data: T? = null
#Json(name = "meta")
var meta: JsonObject? = null
#Json(name = "error")
var error: ErrorResponse? = null
}
Api example:
#GET("api/info")
suspend fun getInfo(): BaseResponse<Info>
Info:
#JsonClass(generateAdapter = true)
data class Info(
val language: String? = null,
val profile: Profile? = null
)

In Moshi you have to explicitly declare your annotations as field annotations, like #field:Json(name = "message")

Related

Get JSONObject as string with moshi/retrofit

I have an object that comes down from the API with another json object (with no named attributes) as one of its attributes:
"stickerData": {}
I would like this to be parsed into this object:
#JsonClass(generateAdapter = true)
class StickerDto(
#Json (name = "totalAnimatedStickers") val total: Int,
#Json(name = "pages") val pages: Int,
#Json(name = "data") val stickers: List<Sticker>
)
#JsonClass(generateAdapter = true)
class Sticker(
#Json(name = "name") val name: String,
#Json(name = "id") val id: String,
#Json(name = "stickerData") val stickerData: String,
)
The architecture of this app uses a single Retrofit instance for every API call:
private fun createNewUserApiClient(authRefreshClient: AuthRefreshClient,
preferencesInteractor: PreferencesInteractor): UserApiClient {
val moshi = Moshi.Builder()
.add(SkipBadElementsListAdapter.Factory)
return Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(Interactors.apiEndpoint)
.build()
.create(UserApiClient::class.java)
}
Which, uses this adapter that you can see getting attached above:
internal class SkipBadElementsListAdapter(private val elementAdapter: JsonAdapter<Any?>) : JsonAdapter<List<Any?>>() {
object Factory : JsonAdapter.Factory {
override fun fromJson(reader: JsonReader): List<Any?>? {
val result = mutableListOf<Any?>()
reader.beginArray()
while (reader.hasNext()) {
try {
val peeked = reader.peekJson()
result.add(elementAdapter.fromJson(peeked))
} catch (e: JsonDataException) {
Timber.w(e, "Item skipped while parsing:")
}
reader.skipValue()
}
reader.endArray()
return result
}
}
However, this adapter does not allow for the parsing of a JSON object as a string. If I try, it throws a
Gson: Expected a string but was BEGIN_OBJECT
error. Is there any way to get this adapter to parse attributes like this as raw strings, rather than looking for an object ?
The stickerData should be Object in POJO class, like this...
#JsonClass(generateAdapter = true)
class Sticker(
#Json(name = "name") val name: String,
#Json(name = "id") val id: String,
#Json(name = "stickerData") val stickerData: StickerData,
)

How to deserialize raw JSON objects with Moshi/Retrofit

I have a Sticker class and its wrapper:
#JsonClass(generateAdapter = true)
class StickerDto(
#Json (name = "totalAnimatedStickers") val total: Int,
#Json(name = "pages") val pages: Int,
#Json(name = "data") val stickers: List<Sticker>
)
#JsonClass(generateAdapter = true)
class Sticker(
#Json(name = "name") val name: String,
#Json(name = "id") val id: String,
#Json(name = "stickerData") val stickerData: JsonObject,
var isSelected:Boolean = false
)
The stickerData attribute comes from the api with a dynamic json object with unknown attributes
"stickerData": {}
How do I deserialize an object like that using Moshi?
My current retrofit client:
private fun createNewFriendsClient(authRefreshClient: AuthRefreshClient,
preferencesInteractor: PreferencesInteractor): FriendsApiClient {
val logger = run {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.apply {
httpLoggingInterceptor.level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
}
}
val okHttp = OkHttpClient.Builder().addInterceptor(logger).authenticator(RefreshUserAuthenticator(authRefreshClient, preferencesInteractor,
UnauthorizedNavigator(SDKInternal.appContext, Interactors.preferences))).build()
return Retrofit.Builder()
.client(okHttp)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(Interactors.apiEndpoint)
.build()
.create(FriendsApiClient::class.java)
}
Gives me an
"Unable to create converter for class StickerDto"
Caused by NoJsonAdapter for java.util.Comparator<? super java.lang.String>
error. What converter do I need to use if not that Moshi one? Trying to pull it down as a string also gives an error as it is expecting and object. I just need that string.
Edit, the Json string is very long but it begins like this:
{"tileId":"1264373a-24d8-4c10-ae90-d6e8f671410c","friendId":"2c50f187-039a-4f85-b12b-0c802396a611","name":"David Carey","message":"Joined WeAre8","animatedSticker":{"v":"5.5.7","fr":24,"ip":0,"op":48,"w":1024,"h":1024,"nm":"party_popper","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"C | Position","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[176,892,0],"to":[-6.667,6.667,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":7,"s":[136,932,0],"to":[0,0,0],"ti":[-6.667,6.667,0]},{"t":11,"s":[176,892,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[115,75,100]},{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":11,"s":[95,105,100]},{"t":20,"s":[100,100,100]}],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Controller","np":13,"mn":"Pseudo/DUIK controller","ix":1,"en":1,"ef":[{"ty":6,"nm":"Icon","mn":"Pseudo/DUIK controller-0001","ix":1,"v":0},{"ty":2,"nm":"Color","mn":"Pseudo/DUIK controller-0002","ix":2,"v":{"a":0,"k":[0.92549020052,0.0941176489,0.0941176489,1],"ix":2}},{"ty":3,"nm":"Position","mn":"Pseudo/DUIK controller-0003","ix":3,"v":{"a":0,"k":[0,0],"ix":3}},{"ty":0,"nm":"Size","mn":"Pseudo/DUIK controller-0004","ix":4,"v":{"a":0,"k":100,"ix":4}},{"ty":0,"nm":"Orientation","mn":"Pseudo/DUIK controller-0005
Note that JsonObject is a class from the gson package, so if you want to use Moshi you will need to switch to JSONObject which is the default class supported by Android.
To do this you will need to write your own JSONObject adapter.
First, write your adapter class:
import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.ToJson
import okio.Buffer
import org.json.JSONException
import org.json.JSONObject
class JSONObjectAdapter {
#FromJson
fun fromJson(reader: JsonReader): JSONObject? {
// Here we're expecting the JSON object, it is processed as Map<String, Any> by Moshi
return (reader.readJsonValue() as? Map<String, Any>)?.let { data ->
try {
JSONObject(data)
} catch (e: JSONException) {
// Handle exception
return null
}
}
}
#ToJson
fun toJson(writer: JsonWriter, value: JSONObject?) {
if (value != null) {
writer.value(Buffer().writeUtf8(value.toString()))
} else {
writer.value(null as String?)
}
}
}
Adjust your retrofit build to provide custom Moshi object when creating the MoshiConverterFactory:
.addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().add(JSONObjectAdapter()).build()))
and then you are good to go and use JSONObject
#Json(name = "stickerData") val stickerData: JSONObject
Good luck and I hope this helps!
Built-in Type Adapters for Moshi include Arrays, Collections, Lists, Sets, and Maps. A JsonObject type is not provided with Moshi itself, but it would be an enhanced Map<String, Any> anyhow, so just use the Map instead of an object.
#JsonClass(generateAdapter = true)
class Sticker(
#Json(name = "name") val name: String,
#Json(name = "id") val id: String,
#Json(name = "stickerData") val stickerData: Map<String, Any>,
var isSelected: Boolean = false
)
The values are automatically converted as well. Thus you'll find strings, lists or numbers there.
I made stickerData into a Map and used a GsonConverterFactory instead of Moshi.

Generate empty json object using moshi json library

What should my data class be so that when I convert it to json using moshi it would come out like this?
{"id":"abcdef""formValues":{}}
At the moment my class looks like this.
#JsonClass(generateAdapter = true)
class MyDataClass(
#Json(name = "id")
val id: String
) {
#Json(name = "formValues")
val formValues = FormValues()
#JsonClass(generateAdapter = true)
class FormValues
}
But you see the statement
Moshi.Builder().build().adapter(MyDataClass::class.java).toJson(MyDataClass("abcdef"))
produces this
{"id": "abcdef"}
and I want this
{"id":"abcdef""formValues":{}}
It turns out you have to use var instead of val for your class members. I change my class to this
#JsonClass(generateAdapter = true)
class MyDataClass(
#Json(name = "id")
var id: String
) {
#Json(name = "formValues")
var formValues = FormValues()
#JsonClass(generateAdapter = true)
class FormValues
}
and moshi generates json like this
{"id":"abcdef""formValues":{}}

ObjectBox Always return null for inner data classes in Kotlin

I am using Retrofit for API Calls.
I was able to cache data class which contains only strings and int.
But now I want to cache another model class which has nested model classes like
below.
#Entity data class ConsumptionDashboardResponse(
#Id(assignable = true)
var id: Long? = null,
#Transient
#SerializedName("data")
#NameInDb("data_consumption")
var `data`: DataConsumption,
#SerializedName("responseCode")
val responseCode: Int,
#SerializedName("responseDesc")
val responseDesc: String,
#SerializedName("sessionLang")
val sessionLang: String )
DataConsumption Class:
#Entity
data class DataConsumption(
#Id(assignable = true)
var id: Long? = null,
#Backlink
#Transient
#SerializedName("listDivisions")
var listDivisions: List<Divisions>
)
And Divisions Class
#Entity
data class Divisions(
#Id(assignable = true)
var id: Long? = null,
#SerializedName("aggregateBalance")
val aggregateBalance: String,
#SerializedName("aggregateConsumption")
val aggregateConsumption: String,
#SerializedName("division")
val division: String,
#Backlink
#Transient
#SerializedName("listConsumption")
var listConsumption: List<Consumption>,
#SerializedName("unit")
val unit: String
)
Divison class contains list of Consumption
#Entity
data class Consumption(
#Id(assignable = true)
var id: Long? = null,
#SerializedName("aggregateConsumption")
val aggregateConsumption: String,
#SerializedName("billingPeriod")
val billingPeriod: String
)
I am unable to find if I need any type of custom converters or what else I need to do.
If I try to read saved data from ObjectBox this is what I get:
ConsumptionDashboardResponse(id=4, data=null, responseCode=200, responseDesc=SUCCESS, sessionLang=AR)
A Transient field is not persisted in the database. If you have to store properties of non-primitive type either use relationships or create type converters.

Moshi deserializing values as null

I've recently switched from Gson to Moshi and am having trouble parsing some Json.
{
"access_token": "-LNe2LQ7DQH5Y2zs_W5iUumKuaUE",
"token_type": "bearer",
"device_id": "461f-837e-af5050c92fe9",
"expires_in": 3600,
"scope": "*"
}
And here's the model class:
data class AuthToken(
#Json(name = "access_token") val accessToken: String,
#Json(name = "token_type") val tokenType: String,
#Json(name = "device_id") val deviceId: String,
#Json(name = "expires_in") val expiresIn: Int,
#Json(name = "scope") val scope: String
)
Whenever I switch to using Moshi in my retrofit client, I receive the following error:
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull
I have made the field nullable, however it is always deserialized as null. I've checked my retrofit response and it's (obviously) the same when using either Gson or Moshi. What am I doing wrong?
For some reason, when I explicitly tell the AuthToken class to generate an adapter - I receive no null values.
#JsonClass(generateAdapter = true)
data class AuthToken(
#Json(name = "access_token") val accessToken: String,
#Json(name = "token_type") val tokenType: String,
#Json(name = "device_id") val deviceId: String,
#Json(name = "expires_in") val expiresIn: Int,
#Json(name = "scope") val scope: String
)

Categories

Resources