javajsonkotlingsongeneralization

General Gson Serialization of API types


I am working with an API that can have different types for it's attributes

The attributes can either be Ids or Objects

I want to build a generalized type that handles this for me with Gson serialization.

Example:

"platforms": [
        6
    ]

"platforms": [
    {
        "id": 6,
        "name": "PC (Microsoft Windows)",
        "slug": "win",
        "url": "https://www.igdb.com/platforms/win",
        "created_at": 1297639288000,
        "updated_at": 1470063140518,
        "website": "http://windows.microsoft.com/",
        "alternative_name": "mswin"
    }
]

I am working with Kotlin and have started building my Generalizable class

data class ObjectType<T>(
        var Id: Long? = null,
        var expand: T? = null
) 

I am currently stuck in constructing my JsonDeserializer, as it needs a return of type T which in my case can be both an Int or an Object. I have tried to replace the T with ObjectType which works 'better' but cannot handle the cases when the JSON is an array.

I am currently trying to make it work with just the Generalized Type T as I can set the type as List> instead.

Current Implementation:

class ObjectDeserializer<T> : JsonDeserializer<T> {

    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
        if (json != null) {

            if (json.isJsonArray) {

                val struct: T = Gson().fromJson(json, T::class.java) as T
                return struct
            } else {
                val id = Gson().fromJson(json, Long::class.java)
                //return ObjectType(id, null)
            }

        }

        return T as T
    }
}

I would love some input on how to solve this.


Solution

  • Your implementation has some issues and inconsistencies. First you have to make sure to deserialize ObjectType<T>. Thus you have to declare the class as:

    class ObjectDeserializer<T> : JsonDeserializer<ObjectType<T>>
    

    It would also be easier to assume that all parameters are non-null:

    override fun deserialize(json: JsonElement, typeOfT: Type,
                             context: JsonDeserializationContext): ObjectType<T>
    

    Now you can use typeOfT which is actually the type of T in JsonDeserializer, not in ObjectDeserializer. Therefore it's the type of ObjectType<T> you need to deserialize. To move to the next step you need to find the actual type of T:

    val objectTypeType = typeOfT as ParameterizedType
    val actualTypeOfT = objectTypeType.getActualTypeArguments()[0]
    

    As the next step you need to figure out the contents of json. In your case you won't ever find an array, but an object or a long:

    return if (json.isJsonObject()) {
        val struct: T = context.deserialize(json, actualTypeOfT)
        ObjectType(expand = struct)
    } else {
        val id = json.getAsLong()
        ObjectType(Id = id)
    }
    

    Here you return the ObjectType instances without any error handling, which you might need to add as well.

    Then you should provide this deserializer to Gson by:

    registerTypeAdapter(ObjectType::class.java, ObjectDeserializer<Any>())
    

    Whenever Gson needs to deserialize an ObjectType<TheType>, it finds the instance of ObjectDeserializer and provides ObjectType<TheType> as typeOfT to deserialize.