I have this json.
{
"people":
[
{ "id":0, "person":true },
{ "id":1, "person":true }
]
}
Model
class Person(var id: Int, var person: Boolean)
My endpoint request
#GET("/posts")
fun getPeople(): Call<List<Person>>
And after request I have failure response and message
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
The issue is that you need to create a wrapper around the list itself that contains the people property.
So you should have something like
data class PeopleList(
var people: List<Person>
)
which should then be used in your service definition like
#GET("/posts")
fun getPeople(): Call<PeopleList>
The reason for this is that retrofit is expects a JSON list [...], but the data being sent is a JSON object {...} that contains a list. So you need to tell retrofit how to get to the list.
Related
I don't understand what is problem clearly. When I searched it in google, I don't decide my reponse model is problem or the json response is problem and should change. Which one? I can't find solution for Kotlin. How I should solve this?
response JSON:
"data":{
"productInfo":{
"data":{
"toBarcode":"2704439285463",
"productJson":{
"p_no":"28420000",
"p_name":"ASA"
}
}
},
"moves":{
"data":[
{
"fisAcik":"MALVERENDEN",
"toBarcode":"2704439285463",
"toJson":{
"to_Hks_Adi":"DAĞITIM MERKEZİ"
},
"movementJson":{
"isleme_Tarihi":"21/12/2022 02:19:30"
}
}
]
}
}
Data.kt
data class Data(
val productInfo: ProductInfo,
val moves: Moves
)
data class Moves (
val data: List<MovesItem>
)
data class MovesItem (
#SerializedName("fisAcik")
val receiptExplanation: String,
val toBarcode: String,
val toJson: ToJson,
val movementJson: MovementJson
)
data class MovementJson (
#SerializedName("isleme_Tarihi")
val processDate: String
)
data class ToJson (
#SerializedName("to_Hks_Adi")
val toUnitHksName: String
)
data class ProductInfo (
val data: ProductInfoItems
)
data class ProductInfoItems (
val toBarcode: String,
val productJson: ProductJson
)
data class ProductJson (
#SerializedName("p_No")
val migrosProductNo: String,
#SerializedName("p_Name")
val migrosProductName: String
)
method that using to call request.
suspend fun dataGetInfo(#Body request: DataRequest): NetworkResult<BaseResponse<Data>>
The framework you are using for this:
...fun dataGetInfo(#Body request: DataRequest)...
is implicitly taking a JSON request and deserializing.
The annotation #SerializedName is a from the Gson library, so I guessed that your framework must be using Gson. From that I was able to test using:
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
println(Gson().fromJson(src, Data::class.java))
which produces
Data(productInfo=ProductInfo(data=ProductInfoItems(toBarcode=2704439285463, productJson=ProductJson(migrosProductNo=null, migrosProductName=null))), moves=Moves(data=[MovesItem(receiptExplanation=MALVERENDEN, toBarcode=2704439285463, toJson=ToJson(toUnitHksName=DAĞITIM MERKEZİ), movementJson=MovementJson(processDate=21/12/2022 02:19:30))]))
So fundamentally your code is ok, but I think the problem is how the source JSON is "topped and tailed". To get that parse work, I was using
val src = """
{
"productInfo": {
"data": {
"toBarcode": "2704439285463",
"productJson": {
"p_no": "28420000",
"p_name": "ASA"
}
}
},
"moves": {
"data": [
{
"fisAcik": "MALVERENDEN",
"toBarcode": "2704439285463",
"toJson": {
"to_Hks_Adi": "DAĞITIM MERKEZİ"
},
"movementJson": {
"isleme_Tarihi": "21/12/2022 02:19:30"
}
}
]
}
}
"""
Notice how I removed, from your source, "data": since what you pasted is obviously not a JSON document. I guess, therefore, that this is where the problem occurs - something to do with the top or bottom of the JSON document or you need a container object around the JSON for Data
This error was from my wrong request. I saw Ios has same error also when request with wrong value. So, for who will look this quesiton, they should understand it's not from response or kotlin. Check your value it is clearly what request need.
I want to parse nested json using retrofit moshi. The json data i'm having is an array, inside array first element is string & second is again an array.
I don't want to parse first element in the array ("list"), just want to parse second element from the array (i.e. Inner array).
But i'm facing challenges exactly here with the data object to be use.
Obviously we can use Any type in kotlin with list, but again will loose type of the object inside the list.
Json Format:
{
"results": [
"list",
[
{
//jsonobj
},
{
//jsonobj
}
]
]
}
I want to parse json with this Data class that i have created without parsing first element & parse directly second element.
#JsonClass(generateAdapter = true)
data class ResponseModel(
#Json(name = "results")
val results: List<Results?>?,
) {
#JsonClass(generateAdapter = true)
data class Results(
#Json(name = "reference")
val reference: String?,
#Json(name = "enabled")
val enabled: Boolean,
)
}
fromJson overridden function
#Throws(IOException::class)
override fun fromJson(reader: JsonReader): C {
val result = newCollection()
reader.beginArray()
while (reader.hasNext()) {
result?.add(elementAdapter.fromJson(reader)!!)
}
reader.endArray()
return result
}
This is throwing an exception saying EXPECTED_OBJECT but it was STRING when trying to parse first element ("list").
i'm stuck here & not able to proceed further.
So can somebody plz help me to get out from here? any help will be appreciated.
I was trying to get list of students from the following json using Retrofit2
{
"students":[
{
"address":{
"city":"DETROIT",
"state":"MI",
"street":"4904 Yorkshire Circle",
"zip":"48228"
},
"school":"A B C D School",
"name":"Mani Nezhad"
},
{
"address":{
"city":"RED HOOK",
"state":"NY",
"street":"1641 Custer Street",
"zip":"12571"
},
"school":"X Y Z School",
"name":"Jane Lindberg"
}
]
}
Here is my Model Class
data class Student(
val name: String,
val school: String,
val address: Address
) {
data class Address(val street: String, val city: String, val state: String, val zip: String)
And here is the method written in Interface:
#GET("abcd")
fun getStudents(#Query("token") token: String): Call<List<Student>>
but whenever I run the app I get this error
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
I understand that I was trying to getch array from a json which starts with { curly bracket.
Now my question is how can I fetch this student array from this type of json which starts with the curly bracket?
Your response is object not Array . So you should create Wrapper Object which will hold the List<Student> . Something like this .
data class ApiResponse(val students: List<Student>)
#GET("abcd")
fun getStudents(#Query("token") token: String): Call<ApiResponse>
I am using Retrofit and Gson to communicate with server and parse responses.
I am receiving list of geometry objects list from server. But the coordinates field of the object might differ. For example, I am receving the following json from server:
{
"geometry":{
"type":"Point",
"coordinates": [
76.95210456848145,
43.2790799527603
]
}
}
Sometimes this object returns me in this format:
{
"geometry":{
"type":"Polygon",
"coordinates":[
[
[76.9478130340576,43.286265501840916],
[76.9482421875,43.276267985142056],
[76.95098876953125,43.27101863123778]
]
]
}
}
As you can see, sometimes coordinates field is just a list (in the first example). Sometimes, this field is 3-level deep list (in the second example).
As a result of this, I cannot correctly parse the list since format of the elements in the list is not the same.
How can I correctly parse this?
Currently, I am using this data class:
data class Geometry(
#SerializedName("coordinates")
val coordinates: List<List<List<Double>>>,
#SerializedName("type")
val type: String)
If you know that there are 2 versions of the file, create one inner class for each file. At time of parsing, identify the type of "coordinates" if it's the 1st type proceed with the 1st, if the second proceed with the second
inner class GeometryV2(
#SerializedName("coordinates")
val coordinates: List<List<List<Double>>>,
#SerializedName("type")
val type: String)
}
inner class GeometryV1(
#SerializedName("coordinates")
val coordinates: List<Double>,
#SerializedName("type")
val type: String)
}
Note: Newbie here, please let me know if i need to provide more information or clarify on anything.
To give you some context: I am practising building a Messenger-clone application with lots of Retrofit methods. For that purpose, i am using a small local JSON server, with which the application communicates.
When a user of the application creates an account, the application creates a profile object in the JSON server using the following method:
#FormUrlEncoded
#POST("profiles")
suspend fun createProfile(#Field("username") username: String?,
#Field("picture") picture: String?,
#Field(value = "nickname") nickname: String?,
#Field(value = "contacts") contacts: ArrayList<String?>,
#Field(value = "status") status: Int?): Response<Profile>
Initially, the contacts ArrayList is empty, because the user has not yet added any contacts. Creating a random profile with an empty ArrayList() for the contacts parameter, this is the result inside the JSON server:
{
"username": "username.example",
"picture": "picture's URL",
"nickname": "Nikola",
"status": 1,
"id": 4
}
The class that represents the Profile model inside the application is this:
class Profile(
val username: String? = "",
var picture: String? = "",
var nickname: String? = "",
var contacts: ArrayList<String?>? = ArrayList(),
var status: Int? = 1,
val id: Int? = 0
)
Once the profile is created, naturally the user can add new contacts, which happens using the following method:
#FormUrlEncoded
#PATCH("profiles/{id}")
suspend fun addContact(#Path("id") id: Int?,
#Field("contacts") contacts: ArrayList<String?>?): Response<Profile>
And here is where the problem occurs, on the very first contact added. The ArrayList, which is sent to server contains just one item and the result inside the JSON server looks like this:
{
"username": "username.example",
"picture": "picture's URL",
"nickname": "Nikola",
"status": 1,
"id": 4,
"contacts": "first.contact"
}
Basically, because the arraylist contains just one item, it saves it as a String. This creates all kinds of problems later on because, once the application uses a #GET method for that profile, it expects an ArrayList for the contacts attribute, but it receives a String.
What can i do to make the the JSON profile look like this:
{
"username": "username.example",
"picture": "picture's URL",
"nickname": "Nikola",
"status": 1,
"id": 4,
"contacts": ["first.contact"]
}
The contacts parameter needs to be an array, even when there is only one item in it.
Use #Body instead of #Form and #FormUrlEncoded:
data class ProfileContacts(val contacts: List<String>)
#PATCH("profiles/{id}")
suspend fun addContact(#Path("id") id: Int?, #Body contacts: ProfileContacts): Response<Profile>
and add a converter, if you haven't already had one, a Gson one for example:
// build.gradle
dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.6.1' // latest version
}
// Retrofit Builder
val retrofit = Retrofit.Builder()
... // other methods
.addConverterFactory(GsonConverterFactory.create())
.build()
#Body lets you define the request body as a Kotlin class, which will eventually get serialized using the provided Converter (in case of Gson, it will be converted to JSON). #Field on the other hand is used for sending data as application/x-www-form-urlencoded (as the required #FormUrlEncoded annotation also suggests). This means that the body of your request will be encoded into a list of key-value pairs, separated by '&', e.g. (based on the createProfile method):
username=username.example&picture=picture%27s%20URL&nickname=Nikola&status=1&id=4
You can POST an array as application/x-www-form-urlencoded by using the same key more than once. That's what basically happens when you annotate a list with the Retrofit #Field annotation - every element from the list is paired with the common key, e.g.:
#FormUrlEncoded
#PATCH("profiles/{id}")
suspend fun addContact(#Path("id") id: Int?,
#Field("contacts") contacts: ArrayList<String?>?): Response<Profile>
// ...
addContact(1, arrayListOf("first.contact", "second.contact"))
// request body:
contacts=first.contact&contacts=second.contact
So when you try to update the profile using only one element contacts list, a single "contacts" pair gets created (contacts=first.contact), and it's treated like a string value.