I've read through multiple posts on StackOverflow, but haven't found a solution that fits my problem yet. Could you please help me fix this Retrofit error?
JSON response:
{
"products": [
{
"barcode_number": "8000040000802",
"barcode_type": "EAN",
"barcode_formats": "EAN 8000040000802",
"mpn": "",
"model": "",
"asin": "",
"product_name": "Campari Bitter 25% Vol. 1 L",
"title": "",
"category": "Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Bitters",
"manufacturer": "Campari",
"brand": "Campari",
"label": "",
"author": "",
"publisher": "",
"artist": "",
"actor": "",
"director": "",
"studio": "",
"genre": "",
"audience_rating": "",
"ingredients": "",
"nutrition_facts": "",
"color": "",
"format": "",
"package_quantity": "",
"size": "",
"length": "",
"width": "",
"height": "",
"weight": "",
"release_date": "",
"description": "",
"features": [],
"images": [
"https://images.barcodelookup.com/19631/196313718-1.jpg"
],
"stores": [
{
"store_name": "Rakuten Deutschland GmbH",
"store_price": "16.50",
"product_url": "https://www.rakuten.de/produkt/campari-bitter-25-vol-1-l-1826679605",
"currency_code": "EUR",
"currency_symbol": "€"
}
],
"reviews": []
}
]
}
Data classes that have been created by the JSON to Kotlin plugin:
data class BaseResponse(
val products: List<Product>
)
Product:
data class Product(
val actor: String,
val artist: String,
val asin: String,
val audience_rating: String,
...
)
Store:
data class Store(
val currency_code: String,
val currency_symbol: String,
val product_url: String,
val store_name: String,
val store_price: String
)
Service:
interface BarcodeLookupApiService {
#GET("products")
suspend fun getArticleData(#Query("barcode") barcode: String,
#Query("key") key: String): List<BaseResponse>
}
Retrofit Builder:
object RetrofitBuilder {
private const val BASE_URL = "https://api.barcodelookup.com/v2/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val apiService: BarcodeLookupApiService =
getRetrofit().create(BarcodeLookupApiService::class.java)
}
Are the data classes created by the plugin not right? Or should my service not return a list? I've tried returning a simple BaseResponse object but that doesn't work either.
Change your interface as shown below:
interface BarcodeLookupApiService {
#GET("products")
suspend fun getArticleData(#Query("barcode") barcode: String,
#Query("key") key: String): BaseResponse
}
and to get list of products do as shown below:
baseRespose.ProductsList
This will fix your issue. The response is an object with has an array inside object. Hence you are getting error.
In my case I was using Laravel framework to build my web service or Rest Api with Sanctum. Ok the problem was here:
{
"result": {
"message": "Registrado correctamente",
"success": true,
"status": 200,
"error": null
}
}
The GsonConverter queries of brackets yes look here be careful:
{
"result": [
{
"message": "Registrado correctamente",
"success": true,
"status": 200,
"error": null
}
]
I update my backend Api:
$result = [
'message' => 'Registrado correctamente',
'success' => true,
'status' => 200,
'error' => null
];
return response()->json([
'result' => $result,
], 200);
to
return response()->json([
'result' => [$result],
], 200);
See careful I have added brackets [ ] to 'result'. This fixed my problem.
My two data class were this:
#Parcelize
data class RegisterResponseObj(
val message: String,
val success: Boolean,
val status: Int = -1,
val error: String?,
) : Parcelable
data class RegisterResponse(val result: List<RegisterResponseObj>)
The web service:
interface WebService {
#FormUrlEncoded
#Headers("Accept: application/json")
#POST("register")
suspend fun registerUser(#Field("name") name: String,
#Field("email") email: String,
#Field("password")password: String): RegisterResponse
}
Cheers!
Related
Can anybody say where I am doing wrong. I have json like that
[
{
"id": "1",
"name": "ff",
"surname": "ggg",
"cap": "10000"
},
{
"id": "1",
"name": "aaa",
"surname": "hhh",
"cap": "22222"
},
{
"id": "1",
"name": "rrr",
"surname": "hhhhhdr",
"cap": "33333"
},
{
"id": "1",
"name": "hhh",
"surname": "qqqqq",
"cap": "44444"
}
]
And I parse to this class.
data class ResponseList(
val capList: List<Response>?
) {
data class Response(
#JsonProperty("id")
val id: String,
#JsonProperty("name")
val name: String,
#JsonProperty("surname")
val surname: String,
#JsonProperty("cap")
val cap: String
)
}
When I try to parse it the list is always null and if I try to test it I have this error:
Cannot deserialize value of type com.myapp.ResponseList from Array value (token JsonToken.START_ARRAY)
just class Response is needed, like following:
fun test(){
val jsonStr = "your json str"
val mapper = ObjectMapper()
val lendReco: List<Response> =
mapper.readValue(jsonStr, object : TypeReference<List<Response?>?>() {})
}
data class Response(
#JsonProperty("id")
val id: String,
#JsonProperty("name")
val name: String,
#JsonProperty("surname")
val surname: String,
#JsonProperty("cap")
val cap: String
)
I'm getting the JSON blow from an API with retrofit and I want to only select the production_companies array from it and convert it to a list of ProductionCompanie class, how I can do it with Moshi without using nested classes?
{
"backdrop_path": "/52AfXWuXCHn3UjD17rBruA9f5qb.jpg",
"belongs_to_collection": null,
"budget": 63000000,
"genres": [
{
"id": 18,
"name": "Drama"
}
],
"homepage": "http://www.foxmovies.com/movies/fight-club",
"id": 550,
"popularity": 40.054,
"poster_path": "/8kNruSfhk5IoE4eZOc4UpvDn6tq.jpg",
"production_companies": [
{
"id": 508,
"logo_path": "/7PzJdsLGlR7oW4J0J5Xcd0pHGRg.png",
"name": "Regency Enterprises",
"origin_country": "US"
},
{
"id": 711,
"logo_path": "/tEiIH5QesdheJmDAqQwvtN60727.png",
"name": "Fox 2000 Pictures",
"origin_country": "US"
},
{
"id": 20555,
"logo_path": "/hD8yEGUBlHOcfHYbujp71vD8gZp.png",
"name": "Taurus Film",
"origin_country": "DE"
},
{
"id": 54051,
"logo_path": null,
"name": "Atman Entertainment",
"origin_country": ""
}
],
"vote_count": 21181
}
this is my retrofit Apis interface:
interface Apis {
#Headers("Content-Type: application/json")
#GET("/3/movie/550")
fun getData(#Query("api_key") key: String = apiKey): Call<List<ProductionCompanie>>
}
and my model:
#JsonClass(generateAdapter = true)
data class ProductionCompanie(
#Json(name = "id")
val id: Int,
#Json(name = "logo_path")
val picture: String,
#Json(name = "name")
val name: String
)
I ended up using a custom adapter:
class ProductionCompanieListAdapter(private val moshi: Moshi) {
#FromJson
fun fromJson(value: JsonReader): List<ProductionCompanie>? {
val json = JSONObject(value.nextSource().readUtf8())
val jsonArray = json.getJSONArray("production_companies")
val type = Types.newParameterizedType(List::class.java, ProductionCompanie::class.java)
val adapter = moshi.adapter<List<ProductionCompanie>>(type)
return adapter.fromJson(jsonArray.toString())
}
#ToJson
fun toJson(value: List<ProductionCompanie>): String {
val type = Types.newParameterizedType(List::class.java, ProductionCompanie::class.java)
val adapter = moshi.adapter<List<ProductionCompanie>>(type)
return adapter.toJson(value)
}
}
I am developing an Android application.
I get the following error when receiving information from the server:
Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $.data
I will attach the following files to the attachment for more details.
Server Response:
{
"status": "Success",
"data": [
{
"reserve_type": "history",
"reserve_id": 163,
"payment_status": true,
"ship": {
"code": "#1",
"photo": "#",
"name": "",
"rate": null
},
"factor_detail": {
"trip": "526.00",
"transit": "356.40",
"extra_hour": {
"price": "1052.00",
"time": "2:0"
},
"items": [],
"reserve_dates": {
"start_date": "27 of July 01:30 PM",
"end_date": "27 of July 04:30 PM"
},
"product": {
"code": "#1",
"name": "",
"photo": "#"
},
"discount": "0.00",
"tax": "348.19",
"finalReservePriceWithoutPercent": "2282.59",
"finall_reserve_price": "2282.59"
},
"comment": null,
"scores": {
"items": [],
"final_rate": null
}
},
{
"reserve_type": "on_going",
"reserve_id": 168,
"payment_status": false,
"ship": {
"code": "#28",
"photo": "#",
"name": "",
"rate": null
},
"factor_detail": {
"trip": "3297.50",
"transit": "1087.50",
"extra_hour": {
"price": "17147.00",
"time": "13:0"
},
"items": [],
"reserve_dates": {
"start_date": "29 of July 07:30 AM",
"end_date": "29 of July 11:00 PM"
},
"product": {
"code": "#28",
"name": "",
"photo": "#"
},
"discount": "0.00",
"tax": "3875.76",
"finalReservePriceWithoutPercent": "25407.76",
"finall_reserve_price": "25407.76"
},
"cancel": false,
"nearest": true
}
],
"code": 200
}
ResponseBody.kt:
class ResponseBody<T>(
val status: String,
val data: T,
val code: Int
)
ResReserve.kt:
data class ResReserves(
val reserves: List<ApiReserve>
)
ApiReserve.kt:
#SuppressLint("ParcelCreator")
#Parcelize
data class ApiReserve(
#Json(name = "reserve_type")
val reserveType: String,
#Json(name = "reserve_id")
val reserveId: Int,
#Json(name = "payment_status")
val paymentStatus: Boolean,
#Json(name = "ship")
val ship: ApiShip,
#Json(name = "factor_detail")
val factorDetail: ApiFactorDetail,
#Json(name = "comment")
val comments: #RawValue Any? = null,
#Json(name = "scores")
val scores: ApiScores?,
#Json(name = "scores")
val cancel: Boolean?,
#Json(name = "nearest")
val nearest: Boolean?
) : Parcelable
MyApiInterface:
interface ReservesApi {
#GET("#")
suspend fun getReserves(
#Query("page[number]") pageNumber: Int? = null,
#Query("page[limit]") pageLimit: Int? = null
): ResponseBody<ResReserves>
}
Please guide me so that I can fix this problem
Thank you for your attention dear friends
I am consuming the Github API but I am facing the a problem "Expected BEGIN_ARRAY but was BEGIN_OBJECT when using GSON", I am using retrofit ato consume my API and kotlin for developing. I have create a model with the serialization, and a manager to handle API responses as you can see above.
This is the JSON I am consuming
[
{
"url": "https://api.github.com/gists/8ca806eb78108aa2fecef2ab33312444",
"forks_url": "https://api.github.com/gists/8ca806eb78108aa2fecef2ab33312444/forks",
"commits_url": "https://api.github.com/gists/8ca806eb78108aa2fecef2ab33312444/commits",
"id": "8ca806eb78108aa2fecef2ab33312444",
"node_id": "MDQ6R2lzdDhjYTgwNmViNzgxMDhhYTJmZWNlZjJhYjMzMzEyNDQ0",
"git_pull_url": "https://gist.github.com/8ca806eb78108aa2fecef2ab33312444.git",
"git_push_url": "https://gist.github.com/8ca806eb78108aa2fecef2ab33312444.git",
"html_url": "https://gist.github.com/8ca806eb78108aa2fecef2ab33312444",
"files": {
"ps-find-second-highest.js": {
"filename": "ps-find-second-highest.js",
"type": "application/javascript",
"language": "JavaScript",
"raw_url": "https://gist.githubusercontent.com/oyilmaztekin/8ca806eb78108aa2fecef2ab33312444/raw/dfb0d3e648ca39e9d21c55a77ca057d7175cf725/ps-find-second-highest.js",
"size": 478
}
},
"public": true,
"created_at": "2018-10-20T11:10:23Z",
"updated_at": "2018-10-20T11:10:23Z",
"description": "",
"comments": 0,
"user": null,
"comments_url": "https://api.github.com/gists/8ca806eb78108aa2fecef2ab33312444/comments",
"owner": {
"login": "oyilmaztekin",
"id": 863600,
"node_id": "MDQ6VXNlcjg2MzYwMA==",
"avatar_url": "https://avatars0.githubusercontent.com/u/863600?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/oyilmaztekin",
"html_url": "https://github.com/oyilmaztekin",
"followers_url": "https://api.github.com/users/oyilmaztekin/followers",
"following_url": "https://api.github.com/users/oyilmaztekin/following{/other_user}",
"gists_url": "https://api.github.com/users/oyilmaztekin/gists{/gist_id}",
"starred_url": "https://api.github.com/users/oyilmaztekin/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/oyilmaztekin/subscriptions",
"organizations_url": "https://api.github.com/users/oyilmaztekin/orgs",
"repos_url": "https://api.github.com/users/oyilmaztekin/repos",
"events_url": "https://api.github.com/users/oyilmaztekin/events{/privacy}",
"received_events_url": "https://api.github.com/users/oyilmaztekin/received_events",
"type": "User",
"site_admin": false
},
"truncated": false
}
This is my GistModel(with only the parts I want)
#Entity(tableName = "gists_table")
data class Gist(
#SerializedName("id") #PrimaryKey(autoGenerate = true) val gistId:Long?,
#SerializedName("description") val description:String?,
#SerializedName("comments_url") val commentsUrl:String?,
#SerializedName("owner") #Embedded val owner:Owner?)
data class Owner(
#SerializedName("avatar_url") var avatar:String,
#SerializedName("followers_url") var followers_url:String,
#SerializedName("following_url") var following_url:String)
This is my Guist manager
fun getGists(
gistsEndpoint: GitEndpoint,
page:Int,
itemsPerPage:Int,
onSuccess:(repos:List<Gist>) -> Unit,
onError: (error:String) -> Unit) {
gistsEndpoint.getGists(page,itemsPerPage).enqueue(
object : Callback<GistResponse> {
override fun onFailure(call: Call<GistResponse>?, t: Throwable?) {
Log.d(ContentValues.TAG, "fail to get data")
onError(t?.message ?: "unknown error")
}
override fun onResponse(
call: Call<GistResponse>?,
response: Response<GistResponse>
) {
Log.d(ContentValues.TAG, "got a response $response")
if (response.isSuccessful) {
val repos = response.body()?.data ?: emptyList()
onSuccess(repos)
} else {
onError(response.errorBody()?.string() ?: "Unknown error")
}
}
}
)
}
This is my GistResponse
/**
* It will represent the list of gists
*/
data class GistResponse(
#SerializedName("") val data: List<Gist>)
I also have a boundary call to handle recycler view scrollings and API request timings but I dont think thats relevant. I wanted to know if I am serializing well because I never had a JSON with an empty array name(normally starts with a data[{something}] and I call the "data" value in the List of the model like #SerializedName("data") var data:List. Can someone help with this problem?
I have a JSON response, which looks like this:
{
"equipment_layer": [
{
"id": 2,
"name": "Gateway",
"detail": "All gateways"
},
{
"id": 3,
"name": "Node",
"detail": "All Nodes"
},
{
"id": 1,
"name": "Miscellaneous",
"detail": "All miscellaneous assets"
},
{
"id": 4,
"name": "Sensors",
"detail": "All Sensors"
},
{
"id": 5,
"name": "IRM",
"detail": "Installation required material"
},
{
"id": 6,
"name": "Communication",
"detail": "All communication devices such as Cellular Router, ETU etc. which are purely communication"
}
],
"data": {
"1": [
{
"equipment_id": 353,
"item_quantity": 1,
"name": "DC Current Transformer (20mm) (Old)",
"shortcode": "SNS-DCI-CT20m-R1A",
"part_number": "718,804,805,",
"equipment_layer_id": 1,
"equipment_layer_name": "Miscellaneous"
},
{
"equipment_id": 357,
"item_quantity": 1,
"name": "Fuel Sensor - B4 (Old)",
"shortcode": "SNS-FUL-PSR-R1A",
"part_number": "718,810,811",
"equipment_layer_id": 1,
"equipment_layer_name": "Miscellaneous"
}
],
"2": [
{
"equipment_id": 345,
"item_quantity": 1,
"name": "RTU (Old)",
"shortcode": "RAN-RTU-PMCL-R1A",
"part_number": "787,788,789",
"equipment_layer_id": 2,
"equipment_layer_name": "Gateway"
}
],
"3": [
{
"equipment_id": 356,
"item_quantity": 1,
"name": "Battery Analyzer (Product) (Old)",
"shortcode": "RAN-BAM-PMCL-R1A",
"part_number": "787,808,809",
"equipment_layer_id": 3,
"equipment_layer_name": "Node"
}
],
"4": [
{
"equipment_id": 346,
"item_quantity": 1,
"name": "DC Current Transformer (30mm) (Old)",
"shortcode": "SNS-CT-DCI-R1A",
"part_number": "718,792,793",
"equipment_layer_id": 4,
"equipment_layer_name": "Sensors"
},
{
"equipment_id": 350,
"item_quantity": 1,
"name": "AC Block CT (Old)",
"shortcode": "SNS-ACI-BLK-R1A",
"part_number": "718,790,791",
"equipment_layer_id": 4,
"equipment_layer_name": "Sensors"
}
]
}
}
Now the part after the "data" label is dynamic, in a response I can have subarrays of "1", "2" but not of "3" or "4".The POJO of the data inside is same as you can see. So how can I parse this data? I'm using Rerofit2 with Gson.converterfactory. I've tried jsonchema2pojo as well but the data inside the "data" object is not showing up.
I've tried to follow this method:
Parsing Retrofit2 result using Gson with different JSON structures but I can't seem to trigger the UnrwapConverter.
This is my converterfactory implementation:
internal class UnwrappingGsonConverterFactory private constructor(private val gson: Gson) : Converter.Factory() {
override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *> ?{
if (!needsUnwrapping(annotations)) {
return super.responseBodyConverter(type, annotations, retrofit)
}
val typeAdapter = gson.getAdapter(TypeToken.get(type))
return UnwrappingResponseConverter(typeAdapter)
}
private class UnwrappingResponseConverter (private val typeAdapter: TypeAdapter<*>) : Converter<ResponseBody, Any> , AnkoLogger{
#Throws(IOException::class)
override fun convert(responseBody: ResponseBody): Any? {
responseBody.use { responseBody ->
JsonReader(responseBody.charStream()).use({ jsonReader ->
// Checking if the JSON document current value is null
val token = jsonReader.peek()
if (token === JsonToken.NULL) {
return null
}
// If it's an object, expect `{`
jsonReader.beginObject()
var value: Any? = null
// And iterate over all properties
while (jsonReader.hasNext()) {
val data = jsonReader.nextName()
debug("Unwrap Stuff: $data")
when (data) {
"1", "2", "3", "4", "5", "6" -> value = typeAdapter.read(jsonReader)
else ->jsonReader.skipValue()
}
}
// Consume the object end `}`
jsonReader.endObject()
return value
})
}
}
}
companion object {
fun create(gson: Gson): Converter.Factory {
return UnwrappingGsonConverterFactory(gson)
}
private fun needsUnwrapping(annotations: Array<Annotation>): Boolean {
for (annotation in annotations) {
if (annotation is Unwrap) {
return true
}
}
return false
}
}
}
And the interface:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class Unwrap
My data classes are these:
data class ActivityNodes(#SerializedName("equipment_layer") val equipmentLayer: List<EquipmentLayer>,
#SerializedName("data") val data: nodeData)
data class nodeData (#SerializedName("1") val nodeList: List<dataItem>) <-- this is where I need someway to tell SerializedName that the value can be anything from 1 to 6
data class dataItem(#SerializedName("equipment_id") val equipmentId: Int,
#SerializedName("item_quantity") val itemQuantity: Int,
#SerializedName("name") val name: String,
#SerializedName("shortcode") val shortCode: String,
#SerializedName("part_number") val partNumber: String,
#SerializedName("equipment_layer_id") val equipmentLayerId: Int,
#SerializedName("equipment_layer_name") val equipmentLayerName: String,
var isScanned: Boolean = false )
data class EquipmentLayer(#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("detail") val details: String)
For the dynamic JSON, you have to parse the JSON string manually. To get JSON string from retrofit you have to use ScalarsConverterFactory instead of GsonConverterFactory.
Add this dependency:
compile 'com.squareup.retrofit2:converter-scalars:2.3.0'
Create Adapter like this:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://echo.jsontest.com")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
Create request method with ResponseBody
public interface MyService {
#GET("/key/value/one/two")
Call<ResponseBody> getData();
}
You can get Json String like this:
MyService service = retrofit.create(MyService.class);
Call<ResponseBody> result = service.getData();
result.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response) {
try {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Throwable t) {
e.printStackTrace();
}
});
Now you have to parse the JSON string manually to get your data from JSON.
Hope it helps:)
use below for the part of "data" of json:
Type mapType = new TypeToken<Map<String, List<EqupmentDetail.class>>>() {}.getType(); // define generic type
Map<String, List<EqupmentDetail.class>> result= gson.fromJson(new InputStreamReader(source), mapType);
here define EqipmentDetails class same as your refence
this will definitely work