Skip to content

Commit

Permalink
add common customizable serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
InsanusMokrassar committed Aug 21, 2024
1 parent d7e9c6a commit c03440c
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dev.inmo.tgbotapi.types.updateIdField
import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.decodeDataAndJson
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
import dev.inmo.tgbotapi.utils.serializers.CallbackCustomizableDeserializationStrategy
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
Expand Down Expand Up @@ -47,52 +48,21 @@ object UpdateSerializerWithoutSerialization : KSerializer<Update> {
* @see StringFormat.parse
* @see kotlinx.serialization.json.Json.parse
*/
object UpdateDeserializationStrategy : DeserializationStrategy<Update> {
private val _customDeserializationStrategies = LinkedHashSet<JsonDeserializerStrategy>()

/**
* Contains [JsonDeserializerStrategy] which will be used in [deserialize] method when standard
* [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well)
*/
val customDeserializationStrategies: Set<JsonDeserializerStrategy>
get() = _customDeserializationStrategies.toSet()
fun interface JsonDeserializerStrategy {
fun deserializeOrNull(json: JsonElement): Update?
}

override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor

override fun deserialize(decoder: Decoder): Update {
val asJson = JsonElement.serializer().deserialize(decoder)
return runCatching {
nonstrictJsonFormat.decodeFromJsonElement(
RawUpdate.serializer(),
asJson
).asUpdate(
asJson
)
}.getOrElse {
customDeserializationStrategies.firstNotNullOfOrNull {
it.deserializeOrNull(asJson)
} ?: UnknownUpdate(
UpdateId((asJson as? JsonObject) ?.get(updateIdField) ?.jsonPrimitive ?.longOrNull ?: -1L),
asJson,
it
)
}
object UpdateDeserializationStrategy : CallbackCustomizableDeserializationStrategy<Update>(
JsonElement.serializer().descriptor,
{ _, jsonElement ->
nonstrictJsonFormat.decodeFromJsonElement(
RawUpdate.serializer(),
jsonElement!!
).asUpdate(
jsonElement
)
},
{ it, _, jsonElement ->
UnknownUpdate(
UpdateId((jsonElement as? JsonObject) ?.get(updateIdField) ?.jsonPrimitive ?.longOrNull ?: -1L),
jsonElement!!,
it
)
}

/**
* Adding [deserializationStrategy] into [customDeserializationStrategies] for using in case of unknown update
*/
fun addUpdateDeserializationStrategy(
deserializationStrategy: JsonDeserializerStrategy
) = _customDeserializationStrategies.add(deserializationStrategy)

/**
* Removing [deserializationStrategy] from [customDeserializationStrategies]
*/
fun removeUpdateDeserializationStrategy(
deserializationStrategy: JsonDeserializerStrategy
) = _customDeserializationStrategies.remove(deserializationStrategy)
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dev.inmo.tgbotapi.utils.serializers

import dev.inmo.tgbotapi.types.update.RawUpdate
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy.deserialize
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement

interface CustomizableDeserializationStrategy<T> : DeserializationStrategy<T> {
fun interface JsonDeserializerStrategy<T> {
fun deserializeOrNull(json: JsonElement): T?
}
/**
* Contains [JsonDeserializerStrategy] which will be used in [deserialize] method when standard
* [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well)
*/
val customDeserializationStrategies: Set<JsonDeserializerStrategy<T>>

/**
* Adding [deserializationStrategy] into [customDeserializationStrategies] for using in case of unknown update
*/
fun addUpdateDeserializationStrategy(
deserializationStrategy: JsonDeserializerStrategy<T>
): Boolean

/**
* Removing [deserializationStrategy] from [customDeserializationStrategies]
*/
fun removeUpdateDeserializationStrategy(
deserializationStrategy: JsonDeserializerStrategy<T>
): Boolean
}

open class CallbackCustomizableDeserializationStrategy<T>(
override val descriptor: SerialDescriptor,
private val defaultDeserializeCallback: (decoder: Decoder, jsonElement: JsonElement?) -> T,
private val fallbackDeserialization: (initialException: Throwable, decoder: Decoder, jsonElement: JsonElement?) -> T = { initialException, _, _ -> throw initialException }
) : CustomizableDeserializationStrategy<T> {
protected val _customDeserializationStrategies = LinkedHashSet<CustomizableDeserializationStrategy.JsonDeserializerStrategy<T>>()

/**
* Contains [JsonDeserializerStrategy] which will be used in [deserialize] method when standard
* [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well)
*/
override val customDeserializationStrategies: Set<CustomizableDeserializationStrategy.JsonDeserializerStrategy<T>>
get() = _customDeserializationStrategies.toSet()

override fun deserialize(decoder: Decoder): T {
val jsonDecoder = decoder as? JsonDecoder
val jsonElement = jsonDecoder ?.decodeJsonElement()
return runCatching {
defaultDeserializeCallback(decoder, jsonElement)
}.onFailure {
return (jsonElement ?.let {
customDeserializationStrategies.firstNotNullOfOrNull {
it.deserializeOrNull(jsonElement)
}
}) ?: fallbackDeserialization(it, decoder, jsonElement)
}.getOrThrow()
}

/**
* Adding [deserializationStrategy] into [customDeserializationStrategies] for using in case of unknown update
*/
override fun addUpdateDeserializationStrategy(
deserializationStrategy: CustomizableDeserializationStrategy.JsonDeserializerStrategy<T>
): Boolean = _customDeserializationStrategies.add(deserializationStrategy)

/**
* Removing [deserializationStrategy] from [customDeserializationStrategies]
*/
override fun removeUpdateDeserializationStrategy(
deserializationStrategy: CustomizableDeserializationStrategy.JsonDeserializerStrategy<T>
): Boolean = _customDeserializationStrategies.remove(deserializationStrategy)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package dev.inmo.tgbotapi.utils.serializers

import dev.inmo.tgbotapi.types.update.RawUpdate
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy.deserialize
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonElement

interface CustomizableSerializationStrategy<T> : SerializationStrategy<T> {
fun interface CustomSerializerStrategy<T> {
fun optionallySerialize(encoder: Encoder, value: T): Boolean
}
/**
* Contains [CustomSerializerStrategy] which will be used in [Serialize] method when standard
* [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well)
*/
val customSerializationStrategies: Set<CustomSerializerStrategy<T>>

/**
* Adding [deserializationStrategy] into [customSerializationStrategies] for using in case of unknown update
*/
fun addUpdateSerializationStrategy(
deserializationStrategy: CustomSerializerStrategy<T>
): Boolean

/**
* Removing [deserializationStrategy] from [customSerializationStrategies]
*/
fun removeUpdateSerializationStrategy(
deserializationStrategy: CustomSerializerStrategy<T>
): Boolean
}

open class CallbackCustomizableSerializationStrategy<T>(
override val descriptor: SerialDescriptor,
private val defaultSerializeCallback: (encoder: Encoder, value: T) -> Unit,
private val fallbackSerialization: (initialException: Throwable, encoder: Encoder, value: T) -> T = { initialException, _, _ -> throw initialException }
) : CustomizableSerializationStrategy<T> {
protected val _customSerializationStrategies = LinkedHashSet<CustomizableSerializationStrategy.CustomSerializerStrategy<T>>()

/**
* Contains [JsonSerializerStrategy] which will be used in [deserialize] method when standard
* [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well)
*/
override val customSerializationStrategies: Set<CustomizableSerializationStrategy.CustomSerializerStrategy<T>>
get() = _customSerializationStrategies.toSet()

override fun serialize(encoder: Encoder, value: T) {
runCatching {
defaultSerializeCallback(encoder, value)
}.onFailure {
customSerializationStrategies.firstOrNull() {
it.optionallySerialize(encoder, value)
} ?: fallbackSerialization(it, encoder, value)
}
}

/**
* Adding [deserializationStrategy] into [customSerializationStrategies] for using in case of unknown update
*/
override fun addUpdateSerializationStrategy(
deserializationStrategy: CustomizableSerializationStrategy.CustomSerializerStrategy<T>
): Boolean = _customSerializationStrategies.add(deserializationStrategy)

/**
* Removing [deserializationStrategy] from [customSerializationStrategies]
*/
override fun removeUpdateSerializationStrategy(
deserializationStrategy: CustomizableSerializationStrategy.CustomSerializerStrategy<T>
): Boolean = _customSerializationStrategies.remove(deserializationStrategy)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.inmo.tgbotapi.utils.serializers

import dev.inmo.tgbotapi.types.update.RawUpdate
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy.deserialize
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement

interface CustomizableSerializer<T> : KSerializer<T>, CustomizableSerializationStrategy<T>, CustomizableDeserializationStrategy<T> {
}

open class CallbacksCustomizableDeserializationStrategy<T>(
override val descriptor: SerialDescriptor,
defaultDeserializeCallback: (decoder: Decoder, jsonElement: JsonElement?) -> T,
defaultSerializeCallback: (encoder: Encoder, value: T) -> Unit,
fallbackDeserialization: (initialException: Throwable, decoder: Decoder, jsonElement: JsonElement?) -> T = { initialException, _, _ -> throw initialException },
fallbackSerialization: (initialException: Throwable, encoder: Encoder, value: T) -> T = { initialException, _, _ -> throw initialException }
) : CustomizableSerializer<T>,
CustomizableSerializationStrategy<T> by CallbackCustomizableSerializationStrategy(descriptor, defaultSerializeCallback, fallbackSerialization),
CustomizableDeserializationStrategy<T> by CallbackCustomizableDeserializationStrategy(descriptor, defaultDeserializeCallback, fallbackDeserialization){
}

0 comments on commit c03440c

Please sign in to comment.