@@ -9,6 +9,9 @@ import io.ktor.http.*
99import io.ktor.serialization.kotlinx.json.*
1010import kotlinx.coroutines.*
1111import kotlinx.serialization.json.*
12+ import kotlinx.serialization.*
13+ import kotlinx.serialization.descriptors.*
14+ import kotlinx.serialization.encoding.*
1215import java.time.Instant
1316import java.time.format.DateTimeFormatter
1417import java.util.*
@@ -92,6 +95,7 @@ internal actual fun platformCapture(event: String, properties: Map<String, Any?>
9295 scope.launch { flushEvents() }
9396 }
9497 }
98+ println (" PosthogAnalyticsManager platformCapture: Sent event $event $eventProperties " )
9599}
96100
97101internal actual fun platformScreen (screenName : String , properties : Map <String , Any ?>? ) {
@@ -302,14 +306,16 @@ private suspend fun flushEvents() {
302306 " batch" to batch
303307 )
304308
309+ val requestBody = json.encodeToString(mapSerializer, body)
310+
305311 try {
306312 val response = client.post(" ${cfg.host} /batch" ) {
307313 contentType(ContentType .Application .Json )
308- setBody(json.encodeToString(mapSerializer, body) )
314+ setBody(requestBody )
309315 }
310316
311317 if (cfg.debug) {
312- println (" [PostHog] Flushed ${eventsToSend.size} events: ${response.status} " )
318+ println (" [PostHog] Flushed ${eventsToSend.size} events: ${response.status} \n ${ if ( ! response.status.isSuccess()) requestBody + " \n " + response else " " } " )
313319 }
314320 } catch (e: Exception ) {
315321 if (cfg.debug) {
@@ -376,5 +382,58 @@ private fun getCurrentISOTimestamp(): String {
376382 return DateTimeFormatter .ISO_INSTANT .format(Instant .now())
377383}
378384
379- // Simple map serializer for the body
380- private val mapSerializer = kotlinx.serialization.serializer<Map <String , Any ?>>()
385+ // Custom serializer for Map<String, Any?>
386+ @OptIn(ExperimentalSerializationApi ::class )
387+ private object MapStringAnySerializer : KSerializer<Map<String, Any?>> {
388+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor(" MapStringAny" )
389+
390+ private fun Any?.toJsonElement (): JsonElement = when (this ) {
391+ null -> JsonNull
392+ is String -> JsonPrimitive (this )
393+ is Number -> JsonPrimitive (this )
394+ is Boolean -> JsonPrimitive (this )
395+ is List <* > -> buildJsonArray {
396+ this @toJsonElement.forEach { item ->
397+ add(item.toJsonElement())
398+ }
399+ }
400+ is Map <* , * > -> buildJsonObject {
401+ this @toJsonElement.forEach { (key, value) ->
402+ put(key.toString(), value.toJsonElement())
403+ }
404+ }
405+ else -> JsonPrimitive (this .toString())
406+ }
407+
408+ override fun serialize (encoder : Encoder , value : Map <String , Any ?>) {
409+ val jsonObject = buildJsonObject {
410+ value.forEach { (key, v) ->
411+ put(key, v.toJsonElement())
412+ }
413+ }
414+ encoder.encodeSerializableValue(JsonObject .serializer(), jsonObject)
415+ }
416+
417+ override fun deserialize (decoder : Decoder ): Map <String , Any ?> {
418+ // For PostHog we mainly need serialization, but provide basic deserialization
419+ val jsonObject = decoder.decodeSerializableValue(JsonObject .serializer())
420+ return jsonObject.mapValues { (_, v) ->
421+ when (v) {
422+ is JsonPrimitive -> when {
423+ v.isString -> v.content
424+ v.booleanOrNull != null -> v.boolean
425+ v.intOrNull != null -> v.int
426+ v.longOrNull != null -> v.long
427+ v.floatOrNull != null -> v.float
428+ v.doubleOrNull != null -> v.double
429+ else -> v.content
430+ }
431+ is JsonNull -> null
432+ else -> v.toString()
433+ }
434+ }
435+ }
436+ }
437+
438+ // Custom map serializer for the body
439+ private val mapSerializer = MapStringAnySerializer
0 commit comments