Search code examples
androidgenericsgsonkotlin-reified-type-parameters

Why to use TypeToken for fromJson function?


I have a mapper like

inline fun <reified DTO_TYPE, reified TYPE> DTO_TYPE.toDomainLayer(): TYPE {
    val gson = Gson()
    val strJson = gson.toJson(this)
    val type = object: TypeToken<TYPE>() {}.type
    return gson.fromJson(strJson, type)
}

Now, here if I do it like

inline fun <reified DTO_TYPE, reified TYPE> DTO_TYPE.toDomainLayer(): TYPE {
    val gson = Gson()
    val strJson = gson.toJson(this)
    return gson.fromJson(strJson, TYPE::class.java)
}

Then is the second implementation good to use, or is there anycase where the second implementation will fail?


Solution

  • If you use TYPE::class.java you get the erased Class value. This is a problem for generic types. For example if TYPE is List<MyClass>, then TYPE::class.java will give you just List. This can then later lead to a ClassCastException when using the deserialized value because Gson does not know the arguments for the type parameters.

    Here is an example:

    inline fun <reified DTO_TYPE, reified TYPE> DTO_TYPE.toDomainLayer(): TYPE {
        val gson = Gson()
        val strJson = gson.toJson(this)
        return gson.fromJson(strJson, TYPE::class.java)
    }
    
    data class InputType(val i: Int)
    data class OutputType(val i: Int)
    
    val output: List<OutputType> = listOf(InputType(1)).toDomainLayer()
    // ClassCastException: LinkedTreeMap cannot be cast to class OutputType
    val first = output.first()
    

    So you have to use object: TypeToken<TYPE>() {} since that preserves the full parameterized type, such as List<OutputType> instead of just List, as mentioned in the question What is Type & TypeToken? (linked in the other answer).

    Even better is if you omit the TypeToken.type call, because this will then use Gson.fromJson(..., TypeToken<T>): T (requires Gson 2.10 or newer) which verifies that the return type actually matches the type of the TypeToken at compile time:

    val strJson = gson.toJson(this)
    val type = object: TypeToken<TYPE>() {}/* .type */
    return gson.fromJson(strJson, type)
    

    However, as mentioned by Pawel in the comments, it would be better to use a different JSON library because Gson does not support all Kotlin features. See the "Important" note on Gson's README or this pull request for details.