https://kotlinlang.org logo
#serialization
Title
# serialization
v

v79

01/22/2023, 8:08 AM
One last try, I've googled until I'm blue in the face. Given this class:
Copy code
data class ResponseEntity<T>(
    val statusCode: Int,
    val body: T? = null,
    val headers: Map<String, String> = emptyMap(),
) {
    companion object {
        fun <T> ok(body: T? = null, headers: Map<String, String> = emptyMap()) =
            ResponseEntity<T>(200, body, headers)
    }
}
I absolutely, definitely need the
KSerializationStrategy
for T (if it exists, could be null), so that much later in the code, in another class, another method, I can call
Json.encodeToString(entity.serializationStrategy, entity.body)
. I think that I need to add another property to the
ResponseEntity
class, which stores the
Class
of
T
, or better still the
KSerializer
for
T
, or the
KSerializationStrategy
for
T
. But whatever combination of things I try - inline reified functions,
typeOf(T)
(which doesn't even compile) - I get stuck.
If I explicitly supply the KClass, I can make it work:
Copy code
// call site
ResponseEntity.ok(body = SimpleClass("hello from simpleClass"), clazz = SimpleClass::class)

// ResponseEntity class
data class ResponseEntity<T : Any>(
    val statusCode: Int,
    val body: T? = null,
    val headers: Map<String, String> = emptyMap(),
    val clazz: KClass<T>? = null
) {
    companion object {
        fun <T : Any> ok(body: T? = null, headers: Map<String, String> = emptyMap(), clazz: KClass<T>? = null) =
            ResponseEntity<T>(200, body, headers, clazz)

    }
}

// eventually do the serialization in another class & method:
val kSerializer = responseEntity.clazz?.serializer()
        val body: String = when (mimeType) {
            MimeType.json -> {
                kSerializer?.let {
                    Json.encodeToString(kSerializer,responseEntity.body as T)
                } ?: "no-serializer"
            }
//... etc
}
This is working. But it does require the user to to provide that
clazz
value, which I had hoped to avoid.
Progress!
Copy code
data class ResponseEntity<T : Any>(
    val statusCode: Int,
    val body: T? = null,
    val headers: Map<String, String> = emptyMap(),
) {
    var clazz: KClass<T>? = null
    companion object {
        inline fun <reified T : Any> ok(body: T? = null, headers: Map<String, String> = emptyMap()): ResponseEntity<T> {
            val tt = T::class
            return ResponseEntity<T>(200, body, headers).apply { clazz = tt }
        }
    }
}
a

Adam S

01/22/2023, 8:57 AM
you could also make a fake inline constructor
Copy code
inline fun <reified T : Any> ResponseEntity(
  statusCode: Int,
  body: T? = null,
  headers: Map<String, String> = emptyMap(),
) = ResponseEntity(statusCode, body, headers, T::class)
e

ephemient

01/22/2023, 3:07 PM
Copy code
@Serializable
class Foo<T>(...)
creates a
Copy code
Foo.serializer(tSerializer: KSerializer<T>): KSerializer<Foo<T>>
function on the companion
this works with custom serializers too, e.g.
Copy code
class ResponseEntitySerializer<T>(
    private val tSerializer: KSerializer<T>
) : KSerializer<ResponseEntity<T>> {
    ...
}
@Serializable(with = ResponseEntitySerializer::class)
class ResponseEntity<T>(...)
has a
ResponseEntity.serializer(tSerializer)
just like the non-custom case
IMO you should not be using
KClass
for this purpose (type erasure) and you should not be carrying a serializer per instance either (that doesn't make much sense). instead, there should be some real call site that knows the real type that it is expecting
v

v79

01/22/2023, 3:17 PM
I'm now struggling with sealed classes with generics... but I think I might just handle those manually. Serialization is hard!
Finally got it working. Note to self - get the KType, it's better then the KClass. My json is a bit corrupted, I think I've double-escaped it, but that's a problem for another day. I'm off to bed. I've got a flight at 6am!
2 Views