square / moshi

A modern JSON library for Kotlin and Java.
https://square.github.io/moshi/1.x/
Apache License 2.0
9.79k stars 761 forks source link

Expected BEGIN_OBJECT but was BEGIN_ARRAY #1274

Closed franquicidad closed 3 years ago

franquicidad commented 4 years ago

Hello i am new in this of retrofit and im doing the whole app persistancy with retrofit and room and repository .Please help i got that error i am using the api https://docs.thecatapi.com/ to get a list of breeds . First i need to get the list of cat id which is a string and then iterate though the ids and make another call to another endpoint https://api.thecatapi.com/v1/images/search?breed_ids=beng (this is just one id)

so this is my this is my network package...

`

import com.franco.myconsultingcatapp.udacity.database.DatabaseBreed
import com.franco.myconsultingcatapp.udacity.domain.DomainImagesModelItem
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class NetworkBreedImagesModelContainer(val imagesDataItem:List<NetworkImagesModelItem>)

@JsonClass(generateAdapter = true)
data class NetworkImagesModelItem(
    val breeds: List<Breed>,
    val height: Int,
    val id: String,
    val url: String,
    val width: Int
)

fun NetworkBreedImagesModelContainer.asDomainModel():List<DomainImagesModelItem>{
    return imagesDataItem.map {
        DomainImagesModelItem(
            breeds = it.breeds,
            id = it.id,
            url = it.url
        )
    }
}
fun NetworkBreedImagesModelContainer.asDatabaseModel():Array<DatabaseBreed>{
    return imagesDataItem.map {
        DatabaseBreed(
                id = it.id,
                breeds = it.breeds,
                url = it.url
        )
    }.toTypedArray()
}`

This is my Service kotlin class

const val BASE_URL= "https://api.thecatapi.com/v1/"

this is the JSON for only one element with id=beng

[ { breeds: [ { weight: { imperial: "6 - 12", metric: "3 - 7" }, id: "beng", name: "Bengal", cfa_url: "http://cfa.org/Breeds/BreedsAB/Bengal.aspx", vetstreet_url: "http://www.vetstreet.com/cats/bengal", vcahospitals_url: "https://vcahospitals.com/know-your-pet/cat-breeds/bengal", temperament: "Alert, Agile, Energetic, Demanding, Intelligent", origin: "United States", country_codes: "US", country_code: "US", description: "Bengals are a lot of fun to live with, but they're definitely not the cat for everyone, or for first-time cat owners. Extremely intelligent, curious and active, they demand a lot of interaction and woe betide the owner who doesn't provide it.", life_span: "12 - 15", indoor: 0, lap: 0, adaptability: 5, affection_level: 5, child_friendly: 4, cat_friendly: 4, dog_friendly: 5, energy_level: 5, grooming: 1, health_issues: 3, intelligence: 5, shedding_level: 3, social_needs: 5, stranger_friendly: 3, vocalisation: 5, bidability: 3, experimental: 0, hairless: 0, natural: 0, rare: 0, rex: 0, suppressed_tail: 0, short_legs: 0, wikipedia_url: "https://en.wikipedia.org/wiki/Bengal_(cat)", hypoallergenic: 1 } ], id: "sPMOo3Jn2", url: "https://cdn2.thecatapi.com/images/sPMOo3Jn2.jpg", width: 880, height: 1100 } ]

`
import com.franco.myconsultingcatapp.BASE_URL
import com.franco.myconsultingcatapp.udacity.network.QueryByBreedId.NetworkBreedImagesModelContainer
import com.franco.myconsultingcatapp.udacity.network.getId.ServiceItemId
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kotlinx.coroutines.Deferred
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query

//https://api.thecatapi.com/v1/breeds
interface breedService{
    @GET("breeds")
    fun getBreedId():Deferred`<List<ServiceItemId>>`

   // https://api.thecatapi.com/v1/images/search?breed_ids=beng
    @GET("images/search")
   fun getImagesBreedId(@Query("breed_ids") id:String): Deferred
`<NetworkBreedImagesModelContainer>`

}

private val moshi =Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

object Network{
    private val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()

    val breedService = retrofit.create(breedService::class.java)
}`

this is my Breed data class... data class Breed( val description: String?, val id: String?, val name: String?, val origin: String?, val temperament: String?, @SerializedName("wikipedia_url") val wikipediaUrl: String? )

added BreedJsonConverter

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class Wrapped
class BreedJsonConverter {
    @Wrapped
    @FromJson
    fun fromJson(json: NetworkImagesModelItem): List<Breed> {
        return json.breeds
    }

    @ToJson
    fun toJson(@Wrapped value: List<Breed?>?): NetworkImagesModelItem? {
        throw UnsupportedOperationException()
    }

}

This is my repository class....

`
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.franco.myconsultingcatapp.udacity.database.BreedDatabase
import com.franco.myconsultingcatapp.udacity.database.asDomainModel
import com.franco.myconsultingcatapp.udacity.domain.DomainImagesModelItem
import com.franco.myconsultingcatapp.udacity.network.Network
import com.franco.myconsultingcatapp.udacity.network.QueryByBreedId.asDatabaseModel
import com.franco.myconsultingcatapp.udacity.network.getId.ServiceItemId
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class BreedRepository(private val databaseBreed: BreedDatabase) {

    private lateinit var breedList:List<ServiceItemId>

    val breeds :LiveData<List<DomainImagesModelItem>> =
        Transformations.map(databaseBreed.breedDao.getBreeds()){
            it.asDomainModel()
        }

    suspend fun getListId():List<ServiceItemId>{
        withContext(Dispatchers.IO){
           breedList =
                    Network.breedService.getBreedId().await()

        }
        return breedList
    }

    init {

    }

    suspend fun getListBreedsFromNetworkThenLoadToDatabase(){

        withContext(Dispatchers.IO){
            val listOfIds =getListId()
            for ((index, value) in listOfIds.withIndex()) {
                val contentListBreed = Network.breedService.getImagesBreedId(value.id).await()
                databaseBreed.breedDao.insertAll(*contentListBreed.asDatabaseModel())
            }

        }
    }
}`

When i put the debugg point at
val contentListBreed = Network.breedService.getImagesBreedId(value.id).await() i am also following this responce https://stackoverflow.com/questions/49212323/moshi-expected-begin-object-but-was-begin-array-custom-converter-ignored but not sure is its the right solution donde really understand whats happening. and skip the line it crashed with the error . Expected BEGIN_OBJECT but was BEGIN_ARRAY When i added the BreedJsonConverter i got another error that says:

java.lang.IllegalArgumentException: Unable to create converter for class com.franco.myconsultingcatapp.udacity.network.QueryByBreedId.NetworkBreedImagesModelContainer for method breedService.getImagesBreedId

if i do private val moshi =Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() and replace it for private val moshi =Moshi.Builder() .add(BreedJsonConverter()) .build() it gives me : Cannot serialize Kotlin type com.franco.myconsultingcatapp.udacity.network.getId.ServiceItemId. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact.

So i leaved it with KotlinJsonAdapterFactory()

cant find a solution to this please help me.

@JakeWharton

ZacSweers commented 4 years ago

Usage questions should go on stackoverflow

franquicidad commented 4 years ago

@ZacSweers Hello Zac i cant make questions in stack overflow because I didn't know how to edit them the way they wanted i am relatively new in programming and didnt know i could get banned.

franquicidad commented 3 years ago

@ZacSweers please help!

DaveBoy commented 3 years ago

it's too much code ,i don't know what you want to do....Simple Description?

@ZacSweers please help!

DaveBoy commented 3 years ago

@ZacSweers please help!

the first error: Expected BEGIN_OBJECT but was BEGIN_ARRAY maybe because of the code:

// https://api.thecatapi.com/v1/images/search?breed_ids=beng
    @GET("images/search")
   fun getImagesBreedId(@Query("breed_ids") id:String): Deferred
`<NetworkBreedImagesModelContainer>`

the json you give is a array ,but you give the NetworkBreedImagesModelContainer to receive,it's an object,

try to change it like this:

// https://api.thecatapi.com/v1/images/search?breed_ids=beng
    @GET("images/search")
   fun getImagesBreedId(@Query("breed_ids") id:String): Deferred
`<List<NetworkImagesModelItem>>`

good luck

ZacSweers commented 3 years ago

Again - usage questions should go on stackoverflow and use the moshi tag

sawphyowai commented 2 years ago

I got an error like you but I solved it out.First you parse Json Array using and you try to convert using Moshi And then you insert into DataBase that type Array. If Jason Object you can do that, but being Jason Array you should try this. You should try parsed data like list into Database.

You try to change like this // https://api.thecatapi.com/v1/images/search?breed_ids=beng @GET("images/search") fun getImagesBreedId(@Query("breed_ids") id:String): Deferred <List<NetworkImagesModelItem>>

And then

@JsonClass(generateAdapter = true) data class NetworkImagesModelItem( val breeds: List, val height: Int, val id: String, val url: String, val width: Int )

fun List.asDomainModel():List{ return map { DomainImagesModelItem( breeds = it.breeds, id = it.id, url = it.url ) } } fun List.asDatabaseModel():List{ return map { DatabaseBreed( id = it.id, breeds = it.breeds, url = it.url ) } }`

In Dao You Must Change Array type into List @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(val dataBasename: List< dataBasename >)

suspend fun getListBreedsFromNetworkThenLoadToDatabase(){

    withContext(Dispatchers.IO){
        val listOfIds =getListId()
        for ((index, value) in listOfIds.withIndex()) {
            val contentListBreed = Network.breedService.getImagesBreedId(value.id).await()
            databaseBreed.breedDao.insertAll(contentListBreed.asDatabaseModel())
        }

    }
}