Chris
Chris

Reputation: 11

Moshi Retrofit Android Json Parsing -Expected a string but was BEGIN_ARRAY at path $

I'm trying to parse some Json data into a kotlin data class but I'm getting the exception from the Api call: Expected a string but was BEGIN_ARRAY at path $.

I have no idea what is the cause of this issue. I have inspected my JSON data and I believe it matches the response that Moshi is expecting. For reference, I will post my JSON response as well as the model data classes and api methods below.

Please any help will be appreciated. I'm quite new to android and programming (6 months) in general so keep in mind. Thanks.

[
  {
    "record_id": "134227",
    "date": "2022-03-10T13:44:00",
    "total_amount": "4500.00",
    "amount_received": "3600.00",
    "discount": "900.00",
    "subtotal": "3600.00",
    "balance_due": "0.00",
    "description": "5 meat, 1 fish",
    "product_list": [
      {
        "id": "564",
        "product_name": "meat",
        "product_price": "500.00",
        "product_quantity": 5,
        "product_total_price": "2500.00"
      },
      {
        "id": "780",
        "product_name": "fish",
        "product_price": "1100.00",
        "product_quantity": 1,
        "product_total_price": "1100.00"
      }
    ],
    "customer": {
      "customer_name": "Emeka",
      "customer_phone": "07438938753"
    },
    "payment_list": [
      {
        "payment_amount": "1700.00",
        "payment_date": "2022-09-15T13:44:00",
        "payment_mode": "POS"
      },
      {
        "payment_amount": "1900.00",
        "payment_date": "2022-10-22T20:15:00",
        "payment_mode": "CASH"
      }
    ]
  },
  {
    "record_id": "495678",
    "date": "2022-04-13T22:30:00",
    "total_amount": "4500.00",
    "amount_received": "3600.00",
    "discount": "900.00",
    "subtotal": "3600.00",
    "balance_due": "0.00",
    "description": "2 tables, 3 chairs",
    "product_list": [
      {
        "id": "144",
        "product_name": "tables",
        "product_price": "500.00",
        "product_quantity": 2,
        "product_total_price": "2500.00"
      },
      {
        "id": "688",
        "product_name": "chairs",
        "product_price": "1100.00",
        "product_quantity": 3,
        "product_total_price": "1100.00"
      }
    ],
    "customer": {
      "customer_name": "Obinna",
      "customer_phone": "0744449853"
    },
    "payment_list": [
      {
        "payment_amount": "1700.00",
        "payment_date": "2022-07-18T13:17:00",
        "payment_mode": "POS"
      },
      {
        "payment_amount": "1900.00",
        "payment_date": "2022-11-12T10:35:00",
        "payment_mode": "CASH"
      }
    ]
  }
]
@JsonClass(generateAdapter = true)
data class NetworkIncome(
    @Json(name="record_id")
    val recordId: String,
    @Json(name="date")
    val date: LocalDateTime,
    @Json(name="total_amount")
    val totalAmount: BigDecimal,
    @Json(name="amount_received")
    val amountReceived: BigDecimal,
    @Json(name="discount")
    val discount: BigDecimal,
    @Json(name="subtotal")
    val subTotal: BigDecimal,
    @Json(name="balance_due")
    val balanceDue: BigDecimal,
    @Json(name="description")
    val description: String,
    @Json(name="product_list")
    val productList: List<Product>?,
    @Json(name="customer")
    val customer: Customer?,
    @Json(name="payment_list")
    val paymentList: List<Payment>
)

@JsonClass(generateAdapter = true)
data class Product(
    @Json(name="id")
    val id: String,

    @Json(name="product_name")
    var productName: String,

    @Json(name="product_price")
    var productPrice: BigDecimal = BigDecimal.ZERO,

    @Json(name="product_quantity")
    var productQuantity: Int = 1,

    @Json(name="product_total_price")
    var productTotalPrice: BigDecimal = BigDecimal.ZERO
)
@JsonClass(generateAdapter = true)
data class Customer(
    @Json(name="customer_name")
    var customerName: String,

    @Json(name="customer_phone")
    var customerPhone: String
)
@JsonClass(generateAdapter = true)
data class Payment(

    @Json(name="payment_amount")
    var paymentAmount: BigDecimal = BigDecimal.ZERO,

    @Json(name="payment_date")
    var paymentDate: LocalDateTime,

    @Json(name="payment_mode")
    var paymentMode: PaymentMode

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

object Api {
    val retrofitService: ApiService by lazy {
        Retrofit.Builder()
            .addConverterFactory(MoshiConverterFactory.create(moshi).asLenient())
            .baseUrl(BASE_URL)
            .build()
            .create(ApiService::class.java)
    }
}
interface ApiService {
@GET("get-all-income")
suspend fun getAllIncome(): Response<List<NetworkIncome>>
}
//api call
val result = Api.retrofitService.getAllIncome()
if (result.isSuccessful) {
    val incomeList = result.body()
    Timber.d("get income api call was successful")
    Result.Success(incomeList)
} else {
    Timber.d("get income api call was not successful")
    Result.Error(Exception("server side error"))
}
EDIT: I have added the error stacktrace to provide more details

W/System.err: com.squareup.moshi.JsonDataException: Expected a string but was BEGIN_ARRAY at path $
W/System.err:     at com.squareup.moshi.JsonUtf8Reader.nextString(JsonUtf8Reader.java:674)
W/System.err:     at com.squareup.moshi.StandardJsonAdapters$10.fromJson(StandardJsonAdapters.java:250)
W/System.err:     at com.squareup.moshi.StandardJsonAdapters$10.fromJson(StandardJsonAdapters.java:247)
W/System.err:     at com.squareup.moshi.internal.NullSafeJsonAdapter.fromJson(NullSafeJsonAdapter.java:41)
W/System.err:     at com.squareup.moshi.AdapterMethodsFactory$5.fromJson(AdapterMethodsFactory.java:295)
W/System.err:     at com.squareup.moshi.AdapterMethodsFactory$1.fromJson(AdapterMethodsFactory.java:97)
W/System.err:     at com.squareup.moshi.JsonAdapter$2.fromJson(JsonAdapter.java:205)
W/System.err:     at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:46)
W/System.err:     at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:27)
W/System.err:     at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
W/System.err:     at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
W/System.err:     at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)

UPDATE:

I removed all the Big decimal and LocalDateTime fields from the NetworkIncome class and converted everything to string and Moshi parsed the response successfully. So this means that the problem is not that I wrapped List with Response<>. I believe the problem is that I haven't figured out how to use Moshi Custom adapters for parsing big decimal and localdatetime. I previously wrote an adapter class (with ToJson and FromJson) which I added to the Moshi builder but for some reason, Moshi is not making use of it. So now, I have written functions to transform the string fields in NetworkIncome class to their respective big decimal and date formats after receiving the api response. TGhis method is not really elegant.

I would appreciate it if anyone can tell me how to write custom Moshi adapters/converters for big decimal and localdatetime class and more importantly, how to make sure that Moshi uses them when parsing my Json into Kotlin data classes.

FINAL UPDATE:

I got it to work! The problem was not Response<> rather it was the Moshi custom json adapter. Because I had Big decimal and date fields in my model class, I needed to define the adapters properly, I was doing it wrong all these while. I'm going to post the adapter below just in case some one else has this issue in the future.

class BigDecimalAdapter {
    @FromJson
    fun stringToBigDecimal(value: String): BigDecimal = BigDecimal(value)

    @ToJson
    fun bigDecimalToString(value: BigDecimal): String = value.toString()
}
class OffsetDateTimeAdapter {
    @FromJson
    fun toDateTime(value: String) = OffsetDateTime.parse(value)

    @ToJson
    fun fromDateTime(value: OffsetDateTime) = value.toString()
}
Moshi.Builder()
        .add(BigDecimalAdapter())
        .add(OffsetDateTimeAdapter())
        .addLast(KotlinJsonAdapterFactory())
        .build()

Upvotes: 1

Views: 979

Answers (1)

Tonnie
Tonnie

Reputation: 8182

I would suggest you wrap the NetworkIncome into a list of items - you can call it NetworkIncomeResponse data class for instance

data class NetworkIncomeResponse (val items:List<NetworkIncome>)

Thereafter, make this API query using the wrapper class:

@GET("get-all-income")
suspend fun getAllIncome(): NetworkIncomeResponse

The error you are having should now go away.

UPDATE/CORRECTION

Actually, the above approach gives the same error!

You don't need to wrap the NetworkIncome data class. In your API's @GET function you just need return List<NetworkIncome> just like you had done before but without wrapping it with<Response> .

@GET("get-income")
 suspend fun getAllIncome(): List<NetworkIncome>

I see in your comments that you had tried the same in thing unsuccessfully. To take it further in your repository, you can use try-catch as shown below:

override fun getIncome(): Flow<Resource<List<NetworkIncome>>> = 
   flow {

  
val response = try {
        api.getAllIncome()
    } catch (e: Exception) {

        e.printStackTrace()
        emit()
        null
    }
    response?.let {
     //do something with the response
    }
}

You can ignore the Flow/Resource as this is just for demo purposes.This works perfectly on my end; let me know if it works on your side.

PS. I didn't know you can create an Endpoint on PostMan as a temporary server, I learnt something

Upvotes: 1

Related Questions