Moshi Date Adapter begin_object but was begin_array - android

I'm using the retrofit and moshi library in my project to help me connect to my backend. From there, I sent dates back but apparently, moshi can't handle dates. I've written my own JsonAdapter but now I get the error:
com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $
DateAdapter:
class DateAdapter: JsonAdapter<Date>() {
#FromJson
override fun fromJson(reader: JsonReader): Date? {
val value = reader.nextString()
return SimpleDateFormat("yyyy-MM-dd", Locale.FRENCH).parse(value)
//return getDateInstance(DateFormat.LONG, Locale.FRENCH).parse(value)
}
#ToJson
override fun toJson(writer: JsonWriter, value: Date?) {
writer.value(SimpleDateFormat("yyyy-MM-dd", Locale.FRENCH).format(value))
}
}
Network layer
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(DateAdapter())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(BASE_URL)
.build()
// the request that throws the error
#GET("getPartiesNearYou")
fun getPatiesNearYou(
#Query("distance") distance: Int,
#Query("lat") lat: Double,
#Query("long") long: Double,
#Query("userId") userId: String
): Deferred<NetworkPartyContainer>
Example response:
[
{
"location": {
"type": "Point",
"coordinates": [
50,
50
]
},
"participants": [
"5db76b7430957f0ef05e73fa"
],
"declines": [
null,
"5dc322e02c7171369e4c67fb"
],
"_id": "5dc322712c7171369e4c67fa",
"name": "Mout's Hartenjagen Party",
"date": "2019-11-28T23:00:00.000Z",
"maxSize": 4,
"gameId": "5db76b7430957f0ef05e73fa",
"createdAt": "2019-11-06T19:43:45.544Z",
"updatedAt": "2019-11-06T19:49:07.599Z",
"__v": 0
}
]
I've done some research and most talk about the fact that you get an array instead of a single object and that something needs to change but I don't know what or to add #Wrapped

your json is starting with array so you have to set your retrofit response in array like this in interface (This ans in JAVA) :- Call<List<ItemList>> getHomeContent();

Related

Moshi: Getting Null values to pass through Retrofit 2 from an API Response

The API I am calling has a response that looks like this...
[
{
"id": 755,
"listId": 2,
"name": ""
},
{
"id": 203,
"listId": 2,
"name": ""
},
{
"id": 684,
"listId": 1,
"name": "Item 684"
},
{
"id": 276,
"listId": 1,
"name": "Item 276"
},
{
"id": 736,
"listId": 3,
"name": null
},
{
"id": 926,
"listId": 4,
"name": null
}
]
There are null values inside the response, but I can't get the API call to work because of it. I tried having the 'name' field be to accept a null value, but I got an error. I heard that Moshi (the converter I am using) could have a way to serialize nulls, so that they pass, but not sure how to go about it.
Here is some more code for a better understanding
Network File
interface InfoCollections {
#GET(ENDPOINT)
suspend fun getInfoService(): Response<List<GetInfoJsonResponse>>
}
object NetworkingObject {
val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val networkingService: InfoCollections by lazy {
retrofit.create(InfoCollections::class.java)
}
val apiClient = ApiClient(networkingService)
The Repository
class InfoRepository(private val infoDao: InfoDao) {
private val allInfoFeeds: LiveData<List<GetInfoJsonResponse>> = infoDao.getAllInfo()
private val _infoFeeds: MediatorLiveData<List<GetInfoJsonResponse>> = MediatorLiveData()
val feeds: LiveData<List<GetInfoJsonResponse>>
get() = _infoFeeds
init {
_infoFeeds.addSource(allInfoFeeds){
_infoFeeds.value = it
}
}
suspend fun fetchInfo(): List<GetInfoJsonResponse>? {
val request = NetworkingObject.apiClient.fetchInfoJsonResponse()
if(request.isSuccessful){
infoDao.insertAll(*request.body()!!.toTypedArray())
return request.body()
}
return null
}
}
The DAO
#Dao
interface InfoDao {
#Query("SELECT * FROM info_response")
fun getAllInfo(): LiveData<List<GetInfoJsonResponse>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(vararg info: GetInfoJsonResponse)
}
The Data Class
#Parcelize
#Entity(tableName = "info_response")
data class GetInfoJsonResponse(
#PrimaryKey
val id: Int,
val listId: Int,
val name: String
): Parcelable
Here is the error message
Appreciate the help guys, thank you.

How to use Retrofit2 to GET list of addresses with the postcode JSON body

I'm using Retrofit2 for the first time, so I'm confused how to proceed. I have the following code
fun getAddressFromPostCode(postCode: String): List<PXAddress>{
val trimmedPostCode = postCode.replace("\\s".toRegex(),"").trim()
val dataBody = JSONObject("""{"postCode":"$trimmedPostCode"}""").toString()
val hmac = HMAC()
val hmacResult = hmac.sign(RequestConstants.CSSecretKey, dataBody)
val body = JSONObject("""{"data":"$dataBody", "data_signature":"$hmacResult"}""").toString()
val url = RequestConstants.getAddress
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
val address: PXAddress = retrofit.create(PXAddress::class.java)
}
with the idea that body needs to look like this:
"data":{
"postcode": "WA1 1LD"
},
"data_signature": "{{getSignature}}"
}
and the response should be
"success": 1,
"addresses": [
{
"address1": "47 Museum Street",
"address2": null,
"address3": null,
"town": "WARRINGTON",
"county": "",
"postcode": "WA1 1LD"
},
{
"address1": "49a Museum Street",
"address2": null,
"address3": null,
"town": "WARRINGTON",
"county": "",
"postcode": "WA1 1LD"
},
{
"address1": "Leda Recruitment",
"address2": "49 Museum Street",
"address3": null,
"town": "WARRINGTON",
"county": "",
"postcode": "WA1 1LD"
}
]
}
And I need to convert that response into a list of PXAddress which is
open class PXAddress : RealmObject() {
var addressLine1: String? = null
var addressLine2: String? = null
var addressLine3: String? = null
var town: String? = null
var county: String? = null
var postcode: String? = null
}
Your implementation is wrong for some reasons:
Use an interface to define the web service request, you must define a class like this:
interface ApiService {
#POST("your/webservice/path")
fun getPXAddress(#Body dataBody: YourBodyModel): Call<List<PXAddress>>
}
You must call your webservice with a data class as body, the gson converter will convert your models in json, in your main code you must do that:
fun getAddressFromPostCode(postCode: String): List<PXAddress>{
val trimmedPostCode = postCode.replace("\\s".toRegex(),"").trim()
val dataBody = DataBodyObject(postCode = trimmedPostCode)
val hmac = HMAC()
val hmacResult = hmac.sign(RequestConstants.CSSecretKey, dataBody)
val yourBodyModel = YourBodyModel(data = dataBody, data_signature = hmacResult)
val url = RequestConstants.getUrl() // This address must be only the host, the path is specified in ApiService interface
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api: ApiService = retrofit.create(ApiService::class.java) // With this you create a instance of your apiservice
val myCall: Call<List<PXAddress>> = api.getPXAddress(yourBodyModel) //with this you can call your service synchronous
}
One last thing, you must call your method asynchronous mode with rxjava, livedata or coroutines. All of them offers converters to retrofit. By default, retrofit has a call method like the example that I show you, you can complete your code doing this:
myCall.enqueue(object : Callback<List<PXAddress>> {
override fun onFailure(call: Call<List<PXAddress>>?, t: Throwable?) {
// Error response
}
override fun onResponse(call: Call<List<PXAddress>>?, response: Response<List<PXAddress>>?) {
// Success response
val myList : List<PXAddress> = response?.body
}
})
Best regards!

Error in parsing json with class Kotlin Android

hi i am trying to parse JSON with kotlin
below is my json code
[{
"module":"1",
"books":[{"name":"bookname1","authors":"author1, author 2"},
{"name":"bookname2","authors":"author1, author 2"},
{"name":"bookname3","authors":"author1, author 2"}]
},
{
"module":"2",
"books":[{"name":"bookname1","authors":"author1, author 2"},
{"name":"bookname2","authors":"author1, author 2"},
{"name":"bookname3","authors":"author1, author 2"}]
},
{
"module":"3",
"books":[{"name":"bookname1","authors":"author1, author 2"},
{"name":"bookname2","authors":"author1, author 2"},
{"name":"bookname3","authors":"author1, author 2"}]
},
{
"module":"4",
"books":[{"name":"bookname1","authors":"author1, author 2"},
{"name":"bookname2","authors":"author1, author 2"},
{"name":"bookname3","authors":"author1, author 2"}]
},
{
"module":"5",
"books":[{"name":"bookname1","authors":"author1, author 2"},
{"name":"bookname2","authors":"author1, author 2"},
{"name":"bookname3","authors":"author1, author 2"}]
}]
please note that this json response starts with array
here is my class to parse it
class SemdetailsPArser {
#SerializedName("module")
#Expose
var module: String? = null
#SerializedName("books")
#Expose
var books: List<Book>? = null
}
class Book {
#SerializedName("name")
#Expose
var name: String? = null
#SerializedName("authors")
#Expose
var authors: String? = null
}
And here is my code
//interface
interface SemdetailsFetcher {
#GET("test/json/sub1.json")
fun getCurrentSemData(): Call<SemdetailsPArser>
}
here is my code in activity
fun getCurrentData() {
val retrofit = Retrofit.Builder()
.baseUrl(BaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(SemdetailsFetcher::class.java)
val call = service.getCurrentSemData()
call.enqueue(object : Callback, retrofit2.Callback<SemdetailsPArser> {
override fun onResponse(
call: retrofit2.Call<SemdetailsPArser>?,
response: retrofit2.Response<SemdetailsPArser>?
) {
// val thisthig = response?.body();
println("here 1 ${response?.body().toString()}")
}
override fun onFailure(call: Call?, e: IOException?) {
println("here 2")
}
override fun onFailure(call: retrofit2.Call<SemdetailsPArser>?, t: Throwable?) {
println("here 3 $t")
}
override fun onResponse(call: Call, response: Response) {
if (response.code() == 200) {
println("secodn success")
val sampleResp = response.body()!!
println(sampleResp)
}
}
})
}
and i am getting this error
here 3 com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
I understood that this might be related to my parsing class
here i am getting an array of json info, i tried the same code with another json
{
"person": {
"name": "Don",
"age": 35
},
"books": [{
"id": 800,
"name": "book 1",
"description": "clear sky",
"icon": "01n"
},
{
"id": 801,
"name": "book 2",
"description": "clear sky 1",
"icon": "01N"
}
],
"city": "bgvnslsl",
"id": 1851632,
"bname": "abcd",
"code": 200
}
this was working perfectly when i changed the parsing class and interface
My problem is that i dont know how to write a class to parse a json response starting with an array
You are expecting list of SemdetailsPArser , so you should define return type as List of SemdetailsPArser
This should fix problem.
interface SemdetailsFetcher {
#GET("test/json/sub1.json")
fun getCurrentSemData(): Call<List<SemdetailsPArser>>
}
You also need to change it in other parts of code.
The error you are getting means that you're trying to parse JSON array, thinking it should be JSON object. JSON array is the thing between these [], while JSON object is in curly brackets like these {}. So your first JSON corresponds to something like List<Module>, it's not an object, but a list of them. Each module has a list of books in it.
So all said, it should be like this
interface SemdetailsFetcher {
#GET("test/json/sub1.json")
fun getCurrentSemData(): Call<List<SemdetailsPArser>>
}
By the way, if you define your POJOs right, you won't need all the annotations.
Create SemdetailsPArser class
data class SemdetailsPArser(
val books: List<Book>,
val module: String
)
Next create Book class
data class Book(
val authors: String,
val name: String
)
next in the interface (SemdetailsFetcher)
interface SemdetailsFetcher {
#GET("test/json/sub1.json")
fun getCurrentSemData(): Call<List<SemdetailsPArser>>
}

Parse list with Moshi without custom adapter

I use an API which returns data as array, like this JSON string (let's call it "tasks")
[
{"id": "5d9478a643a7520041b367c7",
"name": "Task 1",
"value": 2
},
{"id": "5d9478a243a7520041b367c6",
"name": "Task 2",
"value": 10
},
]
I want to parse this response to a list. So I have created corresponding data class with generated adapter
#JsonClass(generateAdapter = true)
data class Task(
var id: String,
var name: String,
var value: Int
)
My retrofit service looks like this
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface NetworkService {
#GET("tasks")
suspend fun tasks(): Response<List<Task>>
}
But I want to user a container type, e.g.:
data class TaskList(val tasks: List<Task>)
as network response result. Is this possible without creating custom adpter or I will need to create a custom adapter and pass it to moshi builder?
You have to make a specific adapter in order to parse a JSON like this
class YourAdapter {
#FromJson
fun fromJson(reader: JsonReader, jsonAdapter: JsonAdapter<YourResponse>): List<YourResponse>? {
val list = ArrayList<YourLResponse>()
if (reader.hasNext()) {
val token = reader.peek()
if (token == JsonReader.Token.BEGIN_ARRAY) {
reader.beginArray()
while (reader.hasNext()) {
val yourResponse = jsonAdapter.fromJsonValue(reader.readJsonValue())
YoutResponse?.let {
list.add(yourResponse)
}
}
reader.endArray()
}
}
return list.toList()
}}

Kotlin Json Question Expected a string but was BEGIN_OBJECT at path

Trying some different methods to parse nested Json that is less than user friendly. With the logger I can see the result coming in correctly but the log shows error
com.squareup.moshi.JsonDataException: Expected a string but was BEGIN_OBJECT at path $.capabilities[1]
I cannot for the life of me figure out how to parse the Attribute array. I have tried doing <List<Attribute>> and Attribute and it does not change the result. Is there a way to convert the Attribute array into a list?
Very new at coding in Android so looking for some help.
JSON to parse
{
"id": "65",
"name": "Switch - Kitchen",
"label": "Switch - Kitchen",
"attributes": [
{
"name": "switch",
"currentValue": "off",
"dataType": "ENUM",
"values": [
"on",
"off"
]
}
],
"capabilities": [
"Switch",
{
"attributes": [
{
"name": "switch",
"dataType": null
}
]
},
"Configuration",
"Refresh",
"Actuator"
],
"commands": [
"configure",
"flash",
"off",
"on",
"refresh",
"refresh"
]
}
DeviceDetails
data class DeviceDetails(
#Json(name="CapabilitiesList")
var attributeList: Attribute,
#Json(name="CapabilitiesList")
val capabilities: List<String>,
#Json(name="CommandsList")
val commands: List<String>,
var id: String = "",
var label: String = "",
var name: String = ""
)
data class Attribute(
val currentValue: String,
val dataType: String,
val name: String,
#Json(name="AttributesValues")
val values: List<String>
)
DeviceDetailsAPI
interface DeviceDetailsAPI {
#GET("devices/65")
fun getDeviceDetails(#Query("access_token") access_token: String):
Deferred<DeviceDetails>
companion object{
operator fun invoke(): DeviceDetailsAPI {
//Debugging URL//
val interceptor : HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY }
val client : OkHttpClient = OkHttpClient.Builder().apply {
this.addInterceptor(interceptor)}.build()
//Debugging URL//
val okHttpClient = OkHttpClient.Builder()
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("http://xxx.xxx.xxx.xxx/apps/api/109/")
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create())
.client(client)
.build()
.create(DeviceDetailsAPI::class.java)
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val API_KEY = "xxxxxxxx"
val testapiService = DeviceListAPI()
val testapiDetails = DeviceDetailsAPI()
//GlobalScope.launch (Dispatchers.Main) {
//val DeviceListAPI = testapiService.getAllDevices(access_token = API_KEY).await()
//textViewID.text = DeviceListAPI.toString()
//}
GlobalScope.launch (Dispatchers.Main) {
val DeviceDetailsAPI = testapiDetails.getDeviceDetails(access_token = API_KEY).await()
textViewID.text = DeviceDetailsAPI.toString()
}
}
}
The apparent problem is that the "capabilities": ... in the JSON block is a mixed type list, but you declare it as val capabilities: List<String>. Hence it fails when it hits the
{
"attributes": [
{
"name": "switch",
"dataType": null
}
]
},
item. It's hard to guess how this item relates to the capabilities, but as it currently stands it looks like this will require a pretty complicated custom Moshi adapter to be able to parse this into a meaningful data structure.

Categories

Resources