I’m having a similar problem as mentioned in this ...
# serialization
s
I’m having a similar problem as mentioned in this thread, where a nested escaped json value interferes with the deserialization process. https://kotlinlang.slack.com/archives/C7A1U5PTM/p1674809577778819 Essentially I want to be able to decode an object like this (this is just the structure of the aws lambda proxy payload)
Copy code
@Serializable
sealed class LambdaProxyRequest<T>(
    val version: String? = null,
    val routeKey: String? = null,
    val rawPath: String? = null,
    val rawQueryString: String? = null,
    val cookies: List<String>? = null,
    val headers: Headers? = null,
    val queryStringParameters: QueryStringParameters? = null,
    val requestContext: RequestContext? = null,
    val body: T? = null,
    val pathParameters: PathParameters? = null,
    val isBase64Encoded: Boolean? = null,
    val stageVariables: StageVariables? = null,
)

object Request1: LambdaProxyRequest<CalculateRequest>()
object Request2: LambdaProxyRequest<DoOtherStuffRequest>()
where the
body
actually comes across the wire as an escaped JSON string, but I want to deserialize it as the generic object, rather than manually pull out the escaped string, then unescape it, then decode it again. I don’t exactly see how the Json primitive stuff y’all talked about above would solve this, but I do see how it could be done with a custom deserializer that simply searches each index until it finds the
body
and then unescapes it, then finally deserializes the whole object as if nothing was changed. But I’m having trouble implementing that. Am I off base here? Is it not possible to do this?
For example, I can do something like this
Copy code
object SomeProxyDeserializer: KSerializer<Some> {
    fun unescapeJson(escapedJson: String): String {
        return escapedJson.replace("""\"""", "\"")
    }
    override val descriptor: SerialDescriptor
        get() = PrimitiveSerialDescriptor("body", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): Some {
        val unescapedString = unescapeJson(decoder.decodeString())
        return Json.decodeFromString(Some.serializer(), unescapedString)
    }

    override fun serialize(encoder: Encoder, value: Some) {
        TODO("No need to ever serialize this, probably. XD")
    }
}
but it doesn’t use the original Json object that was used to start the deserialization process, thus immediately failing as some of the serialization modules aren’t available. and this doesn’t solve the generics problem at all, i have to manually set the serializer to a single instance, but it does get me slightly closer to a solution.
a
What are the possible types for
body
? The only example on the website has a string
"body": "Hello from Lambda",
oh I see, the content of body is an escaped JSON?
s
the type is always a string coming across the wire, but since I am controlling the api, I want to map it directly to a type I know. And yes, if it’s json it will be escaped.
so an example payload I would be trying to deserialize would look like this:
Copy code
val exampleLambdaProxyRequest = """
    {
        "body": "{\"idkanything\":\"json\"}"
    }
I think this is a MVE
Copy code
val exampleLambdaProxyRequest = """
    {
        "body": "{\"idkanything\":\"json\"}"
    }
""".trimIndent()

fun main() {
    val out = Json.decodeFromString<ProxyRequest<Some>>(exampleLambdaProxyRequest)
    println(out.some.idkanything == "json")
}

@Serializable
data class ProxyRequest<T>(val some: T) // but Some comes across as an Escaped JSON String 

@Serializable
data class Some(val idkanything: String)
a
super hacky example of what I think you have to do
Copy code
@Serializable
class LambdaProxyRequest<T>(
  @SerialName("body")
  val bodyContent: String? = null,
) {
  @Transient
  val body: T? = Json.decodeFromString(bodyContent?.unescapeJson())
}

fun String.unescapeJson(): String {
  TODO("")
}
s
ahh.. leave it as a string and just deserialize on a backing property… ok. thank you.
didn’t think of that…
a
you could write a custom serializer for
body
using JsonTransformingSerializer. You'd check that the incoming JsonElement was a JsonPrimitive.string, and then unescape and then
Json.decodeFromString()
s
a colleage literally just linked me this, which I somehow missed. https://github.com/Kotlin/kotlinx.serialization/blob/v1.5.0-RC/docs/json.md#json-transformations and it also mentioned the transforming serializer. I’ve been looking for this for like two hours now lol. smh. thank you.
🎯 1
a
haha
s
but your solution is more geared towards the generics so I think it will actually work better than a custom serializer.
let me try it! thank you 😄
a
instead of plain generics I'd also recommend using a sealed interface - replacing the generic T. It will help with encoding/decoding and working with the data in the code.
s
ah yes, instead of a sealed class over the proxy request, use a sealed interface for T. good idea.