Moshi expected BEGIN_OBJECT but was BEGIN_ARRAY at path $ - android

This is not retrofit but manual parsing in a Firebase message handler service. I am using KotlinJsonAdapterFactory() when building my Moshi instance.
For some reason it thinks one of the nodes is an array when I am asking for it to be parsed as an object. Here is the JSON:
[
{
"$": {
"updateOrigin": "CIS",
"requestSource": "at20",
"requestID": "0000000000004144"
},
"TS": [{
"$": {
"rid": "202008207681819",
"uid": "L81819",
"ssd": "2020-08-20"
},
"Location": [{
"$": {
"tpl": "PADTON",
"wtd": "17:28",
"ptd": "17:28"
},
"dep": [{
"$": {
"et": "17:28",
"src": "Darwin"
}
}
],
"plat": [{
"_": "2",
"$": {
"platsup": "true",
"cisPlatsup": "true",
"platsrc": "M"
}
}
]
}
]
}
]
}
]
And here are my Data classes:
package com.cniekirk.traintimes.model
import com.squareup.moshi.Json
data class PushPortMessage(
val pushPortMessageItems: List<PushPortMessageItem>?
)
data class PushPortMessageItem(
#Json(name = "TS")
val tS: List<TS>?,
#Json(name = "$")
val messageAttrs: MessageAttrs?
)
data class TS(
#Json(name = "LateReason")
val lateReason: List<String>?,
#Json(name = "Location")
val location: List<Location>?,
#Json(name = "$")
val tsAttrs: TSAttrs?
)
data class MessageAttrs(
#Json(name = "updateOrigin")
val updateOrigin: String?
)
data class Location(
#Json(name = "arr")
val arr: List<Arr>?,
#Json(name = "dep")
val dep: List<Dep>?,
#Json(name = "pass")
val pass: List<Pass>?,
#Json(name = "plat")
val plat: List<Plat>?,
#Json(name = "$")
val stationAttrs: StationAttrs?
)
data class TSAttrs(
#Json(name = "rid")
val rid: String?,
#Json(name = "ssd")
val ssd: String?,
#Json(name = "uid")
val uid: String?
)
data class Arr(
#Json(name = "$")
val arrPassAttrs: List<ArrPassAttrs>?
)
data class Dep(
#Json(name = "$")
var depAttrs: DepAttrs?
)
data class Pass(
#Json(name = "$")
val arrPassAttrs: ArrPassAttrs?
)
data class Plat(
#Json(name = "_")
val platform: Int?,
#Json(name = "$")
val platAttrs: PlatAttrs?
)
data class PlatAttrs(
#Json(name = "platsup")
val platsup: Boolean?,
#Json(name = "cisPlatsup")
val cisPlatsup: Boolean?
)
data class StationAttrs(
#Json(name = "pta")
val pta: String?,
#Json(name = "ptd")
val ptd: String?,
#Json(name = "tpl")
val tpl: String?,
#Json(name = "wta")
val wta: String?,
#Json(name = "wtd")
val wtd: String?,
#Json(name = "wtp")
val wtp: String?
)
data class ArrPassAttrs(
#Json(name = "delayed")
val delayed: String?,
#Json(name = "et")
val et: String?,
#Json(name = "src")
val src: String?
)
data class DepAttrs(
#Json(name = "delayed")
val delayed: String?,
#Json(name = "et")
val et: String?,
#Json(name = "etUnknown")
val etUnknown: String?,
#Json(name = "etmin")
val etmin: String?,
#Json(name = "src")
val src: String?
)
I may just be being stupid but I can't find the issue. Here is how I'm parsing the JSON:
val pushPortMessage = data["body"]
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val pushPortAdapter = moshi.adapter(PushPortMessage::class.java)
pushPortMessage?.let {
val msg = pushPortAdapter.fromJson(pushPortMessage)
println(msg)
}

you are parsing a list of objects not object so the parsing should be something like this
val pushPortMessage = data["body"]
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val listType = Types.newParameterizedType(List::class.java, PushPortMessageItem::class.java)
val adapter: JsonAdapter<List<PushPortMessageItem>> = moshi.adapter(listType)
val pushPortMessageList = adapter.fromJson(pushPortMessage )

Related

How to make post request with retrofit + moshi

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

Kotlin Using Genrics for Dynamic Response Parsing

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.

how can create TypeConverter for RoomDatabase

in my project , I have two Data Model class
first :
#Entity(tableName = TABLE_TASK)
data class TaskEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "task_id") val taskId: Int,
#ColumnInfo(name = "task_title") val taskTitle: String?,
#ColumnInfo(name = "task_desc") val taskDesc: String?,
#ColumnInfo(name = "task_priority") val taskPriority: Int?,
#ColumnInfo(name = "task_status") val taskStatus: String?,
#ColumnInfo(name = "task_time") val taskTime: String?,
#ColumnInfo(name = "task_notify") val taskNotify: String?
)
and second :
data class SubtaskModel(
val subTasktitle : String?,
val subTaskStatus : Boolean?
)
and I wanted to add a filed in my first Data Model class as the code below (the last field ) :
#Entity(tableName = TABLE_TASK)
data class TaskEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "task_id") val taskId: Int,
#ColumnInfo(name = "task_title") val taskTitle: String?,
#ColumnInfo(name = "task_desc") val taskDesc: String?,
#ColumnInfo(name = "task_priority") val taskPriority: Int?,
#ColumnInfo(name = "task_status") val taskStatus: String?,
#ColumnInfo(name = "task_time") val taskTime: String?,
#ColumnInfo(name = "task_notify") val taskNotify: String?,
#ColumnInfo(name = "task_subtask") val taskSubTask: MutableList<SubtaskModel>?
)
the error that i have got :
Cannot figure out how to save this field into database. You can
consider adding a type converter for it.
so I read(developer.android.com) and I did search about this but I couldn't figure out how can write Type Converter.
thank you
Follow below steps :
Step1. : create a convertor class -
class MyConverter {
#TypeConverter
fun listToJson(value: List< SubtaskModel >?): String = Gson().toJson(value)
#TypeConverter
fun jsonToList(value: String) = Gson().fromJson(value, Array<SubtaskModel>::class.java).toList()
}
Step 2 -> Add converter to your dataBase class
#Database(entities = [TaskEntity::class], version = 1)
#TypeConverters(MyConverter::class)
abstract class myDataBase : RoomDatabase() {
abstract fun myDao(): MyDao
}
That's it.

How to use json array in Kotlin Using Moshi

[{"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
)

android kotlin: storing retrieved data from url to room database

method i made:
private fun saveToRoom(albums: Array<Data>) {
doAsync {
/*val currentDBPath = getDatabasePath("albums_database").absolutePath
println("DBPath is " + currentDBPath)*/
var items = ArrayList<ProductList_Data>()
for (album in albums) {
val item = ProductList_Data()
item.name = album.name
item.price = album.price
items.add(item)
}
db?.productListDao()?.insert(items)
/*val musicAlbums = db?.productListDao()?.getAll()
activityUiThread {
longToast("Data Got saved")
refreshUIWith(musicAlbums!!)
}*/
}
}
this is data class names Data
data class Data(
val _links: Links?,
val attributes: List<Attribute>?,
val average_rating: String?,
val backordered: Boolean?,
val backorders: String?,
val backorders_allowed: Boolean?,
val button_text: String?,
val catalog_visibility: String?,
val categories: List<Category>?,
val cross_sell_ids: List<Any>?,
val custom_fields: CustomFields?,
val date_created: String?,
val date_created_gmt: String?,
val date_modified: String?,
val date_modified_gmt: String?,
val date_on_sale_from: Any?,
val date_on_sale_from_gmt: Any?,
val date_on_sale_to: Any?,
val date_on_sale_to_gmt: Any?,
val default_attributes: List<Any>?,
val description: String?,
val dimensions: Dimensions?,
val download_expiry: Int?,
val download_limit: Int?,
val downloadable: Boolean?,
val downloads: List<Any>?,
val external_url: String?,
val featured: Boolean?,
val grouped_products: List<Any>?,
val id: Int?,
val images: List<Image>?,
val manage_stock: Boolean?,
val menu_order: Int?,
val meta_data: List<MetaData>?,
val name: String?,
val on_sale: Boolean?,
val parent_id: Int?,
val permalink: String?,
val price: String?,
val price_html: String?,
val purchasable: Boolean?,
val purchase_note: String?,
val rating_count: Int?,
val regular_price: String?,
val related_ids: List<Int>?,
val reviews_allowed: Boolean?,
val sale_price: String?,
val shipping_class: String?,
val shipping_class_id: Int?,
val shipping_required: Boolean?,
val shipping_taxable: Boolean?,
val short_description: String?,
val sku: String?,
val slug: String?,
val sold_individually: Boolean?,
val status: String?,
val stock_quantity: Any?,
val stock_status: String?,
val tags: List<Any>?,
val tax_class: String?,
val tax_status: String?,
val total_sales: Int?,
val type: String?,
val upsell_ids: List<Any>?,
val variations: List<Int>?,
val virtual: Boolean?,
val weight: String?
) i want to get only name and price to be stored in room database
entity class that i am using :
#Entity(tableName = "productlisttable")
data class ProductList_Data(
#PrimaryKey
val uid: Int = 0,
#ColumnInfo(name = "_name")
var name: String? = "",
#ColumnInfo(name = "_price")
var price: String? = ""
)
calling method in main activity and getting red underline error that no values passed for init and all that
saveToRoom(Array<Data>))
i think the way i am calling the method is wrong or what
Use this function to convert and save array in database
#TypeConverter
fun fromStringList(stringList: List<String?>?): String? {
if (stringList == null) return null
val type = object : TypeToken<List<String?>?>() {}.type
return Gson().toJson(stringList, type)
}

Categories

Resources