9

I am receiving a JSON data model that has a map wrapper Table. I'm trying to use generics to pass in the type that is beyond the wrapper but it's not translating well at runtime. Here's an example of my JSON file:

{
"Table": [
 {
   "paymentmethod_id": 1,
   "paymentmethod_description": "Cash",
   "paymentmethod_code": "Cash",
   "paymentmethod_is_ach_onfile": false,
   "paymentmethod_is_element": false,
   "paymentmethod_is_reward": false,
   "paymentmethod_is_openedgeswipe": false,
   "paymentmethod_update_user_id": 1,
   "paymentmethod_insert_user_id": 1,
   "paymentmethod_insertdate": "2014-10-07 14:53:16",
   "paymentmethod_deleted": false,
   "paymentmethod_is_mobile_visible": true
   }
  ]
}

The wrapper class I'm using is called Table.

data class Table<T>(
    @SerializedName("Table") val models : Array<T>
)

The actual model class is PaymentMethod.

data class PaymentMethod(
    @SerializedName("paymentmethod_id") val idNumber : Int = -1
)

I have created a generic data manager class that takes < T > type. I think use subclasses of the data manager to localize the input and results (such as declaring the model class PaymentMethod.

open class NXDataManager<T>(manager: NXNetworkManager? = null, rpc : String?, parameters: List<Pair<String, String>>? = null, method : String = "get")
{
   ...


open fun sendRequest(completionHandler: (models:Array<T>) -> Unit, errorHandler: (error:FuelError) -> Unit) {

    val request = NXNetworkRequest(rpc, parameters, method)

    request.send(manager, completionHandler = { s: String ->

        val table: Table<T> = Gson().fromJson(s)

        completionHandler(table.models)

    }, errorHandler = errorHandler)
}

inline fun <reified T> Gson.fromJson(json: String) = this.fromJson<T>(json, object: TypeToken<T>() {}.type)

}

My subclassed data manager specifies the model to parse into.

final public class PaymentMethodsDataManager : NXDataManager<PaymentMethod>
{
   constructor () : super("genGetPaymentMethods")

}

When I run the code as:

val table: Table<T> = Gson().fromJson(s)

I get an error message java.lang.ClassCastException: java.lang.Object[] cannot be cast to Networking.PaymentMethod[]. However, when I pass in an explicit type it works as expected--parsing the array into PaymentMethod models:

val table: Table<PaymentMethod> = Gson().fromJson(s)

Any ideas of how I can still use the generic type T?

Jitesh Mohite
  • 31,138
  • 12
  • 157
  • 147
Hobbes the Tige
  • 3,753
  • 2
  • 22
  • 21

3 Answers3

12

Data Class :

data class Table<T>(
    @SerializedName("Table") val models : Array<T>
)

to JSON:

val gson = Gson()
val json = gson.toJson(table)

from JSON:

val json = getJson()
val table = gson.fromJson(json, Table::class.java)
chandrakant sharma
  • 1,334
  • 9
  • 15
  • 1
    I was not able to use Table::class.java, because Table needs to specify its generic type Table::class.java. But that throws an error because I'm guessing the compiler can't tell what T is at compile time (isn't that kind of the point of generics in the first place?). – Hobbes the Tige Jan 02 '18 at 14:38
4

Method fromJson is generic, so when you call it for Table<T> variable it creates Array<Any> as most suitable. You need to notice that PaymentMethod class extends T generic, but I don't know is it even possible. If you find out how to make it, use something like following:

val table: Table<T> = Gson().fromJson<Table<PaymentMethod>>(s)

In your case I'm using gson adapters. Following function creates object with specified type parameter:

fun getObjectFromString(type: Type, string: String) =
    Gson().getAdapter(TypeToken.get(type)).fromJson(string)

To use it write something following:

val table: Table<T> = getObjectFromString(Table<PaymentMethod>::class.java, s) as Table<PaymentMethod>

Update

To avoid spare class cast you can use reified generic function:

inline fun <reified T> getObjectFromString(string: String): T =
            getGsonConverter().getAdapter(TypeToken.get(T::class.java)).fromJson(string)!!

In that case using would be easier:

val table: Table<T> = getObjectFromString<Table<PaymentMethod>>(s)

I used first solution in cases where I don't know what type the object would be - I've got only Type variable with information about that object.

Ircover
  • 2,406
  • 2
  • 22
  • 42
2

java.lang.ClassCastException: java.lang.Object[] cannot be cast to Networking.PaymentMethod[]

Your JSON is

{
"Table": [
 {
   "paymentmethod_id": 1,
   "paymentmethod_description": "Cash",
   "paymentmethod_code": "Cash",
   "paymentmethod_is_ach_onfile": false,
   "paymentmethod_is_element": false,
   "paymentmethod_is_reward": false,
   "paymentmethod_is_openedgeswipe": false,
   "paymentmethod_update_user_id": 1,
   "paymentmethod_insert_user_id": 1,
   "paymentmethod_insertdate": "2014-10-07 14:53:16",
   "paymentmethod_deleted": false,
   "paymentmethod_is_mobile_visible": true
   }
  ]
}

Create a data class, PaymentMethod.

We frequently create classes whose main purpose is to hold data. In such a class some standard functionality and utility functions are often mechanically derivable from the data.

data class PaymentMethod(@SerializedName("Table") val table:ArrayList<PaymentData> )
data class PaymentData

                      (
                       @SerializedName("paymentmethod_id") val paymentmethod_id: Int,
                       @SerializedName("paymentmethod_description") val paymentmethod_description: String,
                       @SerializedName("paymentmethod_code") val paymentmethod_code:String,
                       @SerializedName("paymentmethod_is_ach_onfile") val paidStatus:Boolean,
                       @SerializedName("paymentmethod_is_element") val paymentmethod_is_element:Boolean,
                       @SerializedName("paymentmethod_is_reward") val paymentmethod_is_reward:Boolean,
                       @SerializedName("paymentmethod_is_openedgeswipe") val paymentmethod_is_openedgeswipe:Boolean,
                       @SerializedName("paymentmethod_update_user_id") val paymentmethod_update_user_id:Int,
                       @SerializedName("paymentmethod_insert_user_id") val paymentmethod_insert_user_id:Int,
                       @SerializedName("paymentmethod_insertdate") val paymentmethod_insertdate:String,
                       @SerializedName("paymentmethod_deleted") val paymentmethod_deleted:Boolean),
                       @SerializedName("paymentmethod_is_mobile_visible") val paymentmethod_is_mobile_visible:Boolean
                       )

You can call this way

  val paymentDATA = Gson().fromJson<PaymentMethod>("JSON_RESPONSE", PaymentMethod::class.java)
  val _adapterPaymentHistory = paymentDATA.table
IntelliJ Amiya
  • 74,896
  • 15
  • 165
  • 198