Search code examples
androidkotlinretrofit

Kotlin reflection


With Retrofit, I am fetching data and getting JSONObject with the structure like this for example:

{
    "token": "some-token",
    "tiles": [
        {
            "id": "id-1",
            "type": "RedTile",
            "title": "This red title",
            "stuff": "hello world"
        },
        {
            "id": "id-2",
            "type": "BlueTile",
            "title": "This blue title",
            "value": 200
        }
    ]
}

Now, when I get this response, I need to make a parser that will actually take the type from every tile and match it with the class that already exists inside application. I need to basically from this response to create new one with token and tiles list but in this case tiles will reflect to actual class in app.

Any idea what is proper way to do it?!


Solution

  • Because you mention Reflection in your question's title:

    import kotlin.reflect.full.declaredMemberProperties
    
    data class TileResponse(val id: String, val type: String, val title: String, val stuff: String? = null, val value: Int? = null)
    data class Response(val token: String, val tiles: List<Tile>)
    
    sealed interface Tile
    data class RedTile(val title: String, val stuff: String) : Tile
    data class BlueTile(val title: String, val value: Int) : Tile
    
    fun getTiles(): List<Tile> {
      return tileResponses
        .filter { tileResponse -> mapTypeToClass.containsKey(tileResponse.type) }
        .map { tileResponse ->
          val clazz = mapTypeToClass[tileResponse.type]!!
          val constructor = clazz.constructors.first()
          val declaredMemberProperties = tileResponse::class.declaredMemberProperties
          val fields = constructor.parameters.map { parameter ->
            parameter to declaredMemberProperties.first { it.name == parameter.name }
          }
          val arguments = fields.associate { (parameter, property) ->
            parameter to property.call(tileResponse)
          }
          constructor.callBy(arguments)
        }
    }
    
    val tileResponses = listOf(
      TileResponse("id-1", "RedTile", "This red title", "hello world", null),
      TileResponse("id-2", "BlueTile", "This blue title", "null", 200)
    )
    
    val mapTypeToClass = mapOf(
      "RedTile" to RedTile::class,
      "BlueTile" to BlueTile::class,
    )
    
    val response = Response("some-token", getTiles())