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
)
Related
I'm trying to make a post with retrofit and moshi but keep getting the error mess
com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was STRING at path $
I can't seem to understand why this is so. This is a sample of the json tested on postman:
{
"customerName": "Name",
"customerPhoneNo": "090000000",
"customerAddress": "Address",
"note": "Please",
"items" : [{
"productUid": "5633e1f1-8b00-46de-b73e-43799245a4e8",
"quantity" : "3"
},{
"ProductUid": "fca3ffb1-0130-4e47-b499-721d046c1e32",
"Quantity" : "5"
},
{
"ProductUid": "6a7f3e24-03ff-408a-b67e-8530d411390c",
"Quantity" : "2"
}]
}
My data classes are set up like so:
#Parcelize
data class Order(
val items: List<Item>?,
val customerName: String,
val customerPhoneNo: String,
val customerAddress: String,
val note: String
) : Parcelable
and
#Parcelize
data class Item(
val productUid: String,
var quantity: Int
) : Parcelable
Service utils look like:
interface ProductService {
#Headers("Content-Type: application/json")
#POST("/api/order/saveorder")
suspend fun postProds(#Body order: Order
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
object Network {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)
.asLenient()
)
.build()
object ProdsApi {
val retrofitService: ProductService by lazy {
retrofit.create(ProductService::class.java)
}
}
}
The sendOrder function is set up like so:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(
order )
}
catch (e: Exception) {
Timber.e(e)
}
}
}
The GET request works perfectly.
Any help on this would be appreciated please.
In your Item Data Class you are using quantity as an Int but if you see the Postman JSON response it is a String.
So your class should be like:
data class Item(
#Json(name = "productUid")
val productUid: String?,
#Json(name = "quantity")
var quantity: String
)
Also as I see the key in your JSON response are written in two different ways.
For example your "Product ID" is written as "productUid" in one of the object and is written as "ProductUid" in another object.
So your complete Item Data Class should more look like this :
data class Item(
#Json(name = "productUid")
val productUid: String?,
#Json(name = "ProductUid")
val productUid: String?,
#Json(name = "quantity")
val quantity: String?,
#Json(name = "Quantity")
val quantity: String?
)
Add to app/build.gradle
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"
Refactor your data class
check the key in your item and replace with right one
if productUid or ProductUid
quantity or Quantity
#JsonClass(generateAdapter = true)
data class Item(
#Json(name = "productUid")
val productUid: String,
#Json(name = "quantity")
var quantity: String
)
#JsonClass(generateAdapter = true)
data class Order(
#Json(name = "items")
val items: List<Item>,
#Json(name = "customerName")
val customerName: String,
#Json(name = "customerPhoneNo")
val customerPhoneNo: String,
#Json(name = "customerAddress")
val customerAddress: String,
#Json(name = "note")
val note: String
)
and try it again
I had the response structure given below
#JsonClass(generateAdapter = true)
data class ResponseMessage(
#field:Json(name = "ClientID") val clientID: String,
#field:Json(name = "MessageID") val messageID: String,
#field:Json(name = "Payload") val payload: Payload,
#field:Json(name = "Timeout") val timeout: Int,
#field:Json(name = "Timestamp") val timestamp: Long,
#field:Json(name = "Type") val type: String
)
#JsonClass(generateAdapter = true)
data class Payload(
#field:Json(name = "Action") val action: String,
#field:Json(name = "Params") val params: Params?
)
#JsonClass(generateAdapter = true)
data class Params(
#field:Json(name = "room") val userDetails: UserDetails?,
#field:Json(name = "configuration") val configurationDetails: ConfigurationDetails?,
#field:Json(name = "category") val category: String?
)
Here Params could be changed in response as per the requested service. However in some cases params will not required at all. How this can be done by making Params as Generic Type so that when i need UserDetails i will pass that and likewise for other params.
[{"id":1,"first_name":"Lillis","last_name":"Hawgood"," cars":[ {"item":"Savana 1500"}, {"item":"Vibe"}, {"item":"Estate"} ]}]
data class MyData( val id: Int = 0, val first_name: String = "", val last_name: String = "", val cars: List
)
class Car { #Json(name = "item") var item: String? = null How to use Item in Data class the how ot print in main clas using moshi txtResult.text = "" for (myDataLst in myDataList ?: emptyList()) { txtResult.append("${myDataLst.first_name} - ${myDataLst.last_name} - ${myDataLst.cars} \n") }enter code here
I tried this way only first name and last showing but for cars showing some worng infor
Try this data classes i transform it using Json to Data Class plugin.
data class MyData(
val cars: List<Car>,
val first_name: String,
val id: Int,
val last_name: String
)
data class Car(
val item: String
)
class car : ArrayList<MyData>()
Firstly, please format your code, it's hard to read your code.
Secondly, you can use the Moshi like this:
#JsonClass(generateAdapter = true)
data class MyData(
#Json(name = "id") val id: String,
#Json(name = "first_name") val firstName: String,
#Json(name = "last_name") val lastName: String,
#Json(name = "cars") val cars: List<Car>
)
#JsonClass(generateAdapter = true)
data class Car(
#Json(name = "item") val item: String
)
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,
)
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")