How to create a Serializer for a List<Pair<String, Any>> - kotlin

I'm trying to create a Serializer for the List<Pair<String, Any>> type, I need this type for a project where I have to manipulate keys and values but user can change key names at any time and using a List of Pair is much better for what I want (and doesn't really work with a Map).
I have this code, but it produces a compiler error
class SnapshotListPairSerializer<K, V>(private val keySerializer: KSerializer<K>, private val valueSerializer: KSerializer<V>) :
KSerializer<SnapshotStateList<Pair<K, V>>> {
override val descriptor: SerialDescriptor = ListSerializer(PairSerializer(keySerializer, valueSerializer)).descriptor
override fun serialize(encoder: Encoder, value: SnapshotStateList<Pair<K, V>>) {
encoder.encodeSerializableValue(ListSerializer(PairSerializer(keySerializer, valueSerializer)), value as List<Pair<K, V>>)
}
override fun deserialize(decoder: Decoder): SnapshotStateList<Pair<K, V>> {
val list = mutableStateListOf<Pair<K, V>>()
val items = decoder.decodeSerializableValue(ListSerializer(PairSerializer(keySerializer, valueSerializer)))
list.addAll(items)
return list
}
}
Also, SnapshotStateList is a class that comes from Jetpack Compose and extends List.

The exception you get is:
Backend Internal error: Exception during IR lowering
Given that this is not providing you with meaningful information, but mentions compiler internals, this is not an error of your doing, but a bug: a cue to search for known bugs.
This seems very similar to the issue I filed on GitHub.
If it is the same cause, it should be fixed in version 1.6.10. This may explain why Philip can't repro.
P.s. the next problem you will run into is likely that Any is not registered for polymorphic serialization. Serializing Any is dodgy. If you are stuck and the documentation does not help you out, I suggest you post a new question with more information on the exact use case/expected types, and I will gladly help out.

Related

How can I circumvent Kotlin's generics type variance constraints

I am relatively new Kotlin and Generics kind of give me a headache. I have the following architecture made out of:
A few data classes
A generic interface to process data
Implementations of that processing interface for each data type
A generic processing job class containing the data to be processed and it's appropriate processor
A global (singleton) processor which implements the processing interface, takes processing jobs and just delegates the processing to the job processor. It doesn't care about the data itself at all.
The simplified code looks like this
class DataOne
class DataTwo
interface DataProcessor<in T> {
fun process(o: T)
}
class DataOneProcessor: DataProcessor<DataOne> {
override fun process(o: DataOne) = println("Processing DataOne")
}
class DataTwoProcessor: DataProcessor<DataTwo> {
override fun process(o: DataTwo) = println("Processing DataTwo")
}
class ProcessingJob<T>(val data: T, val processor: DataProcessor<T>)
object GlobalProcessor: DataProcessor<ProcessingJob<Any>> {
override fun process(job: ProcessingJob<Any>) = job.processor.process(job.data)
}
fun main() {
GlobalProcessor.process(ProcessingJob(DataOne(), DataOneProcessor()))
}
In the main function I get a compiler error
Type mismatch.
Required: ProcessingJob<Any>
Found: ProcessingJob<DataOne>
I understand why this happens: A DataProcessor of DataOne, viewed as a DataProcessor of Any could be asked to process DataTwos and for type safety this is not allowed.
Can you give me any suggestions on how/what to change to make it compile and achieve the required result? Thanks for your time!
There are two problems here.
First, Any isn't actually the top-level type. Any implies not null, but T is unconstrained, which means it can be a nullable type. In this case you can use *, or you could also specify the type as Any?.
Change the signature of the GlobalProcessor to this:
object GlobalProcessor: DataProcessor<ProcessingJob<*>> {
override fun process(job: ProcessingJob<*>): ...
The second problem is that the implementation of process can't take advantage of the generic information from the job in order to know that the job.processor and the job.data are compatible. It just sees two objects of unknown type. To let it know they share a compatible type, you need to capture that type as a type variable. We can't add a generic type parameter to the existing method, because it has to match the signature of the interface method, but we can add a new private method that introduces the generic parameter.
Here's the GlobalProcessor with both the required changes.
object GlobalProcessor: DataProcessor<ProcessingJob<*>> {
override fun process(job: ProcessingJob<*>) = processGeneric(job)
private fun <T> processGeneric(job: ProcessingJob<T>) = job.processor.process(job.data)
}

When in Kotlin Either Hell

I am trying to use
Arrow Either results instead of try-catch, but have gone too deep down the rabbit hole. 🙄
I have been trying to use Either<Problem,Value> as my functional return types, where Problem is like
sealed interface Problem
data class Caught(val cause: Throwable): Problem
data class DataKeyDisabled(val uuid: UUID, val cause: String): Problem
data class SubscriberNotFound(val uuid: UUID, val cause: String): Problem
data class NotEncrypted(val field: String): Problem
where the use case looks like
when (val result = transform(...)) {
is Right -> {}
is Left -> when (val problem = result.value) {
is Caught -> {}
is DataKeyDisabled -> {}
is SubscriberNotFound -> {}
is NotEncrypted -> {}
// else -> {} not needed...
}
}
But, there are really three types of problems, and I don't want to have to exhaust all the choices all the time.
Problem -> Caught
KeyProblem -> Caught, DataKeyDisabled, SubscriberNotFound
DataProblem -> Caught, DataKeyDisabled, SubscriberNotFound, NotEncrypted
For example, I want to have something like
sealed interface Problem
sealed interface KeyProblem : Problem
sealed interface DataProblem : KeyProblem
data class NotHandled(val cause: Throwable): Problem
data class DataKeyDisabled(val uuid: UUID, val cause: String): KeyProblem
data class SubscriberNotFound(val uuid: UUID, val cause: String): KeyProblem
data class NotEncrypted(val cause: String) : DataProblem
And I want to be able to have some code like
fun bar(input: Either<Problem,String>) : Either<KeyProblem,String> {
val something = when (input) {
is Right -> {}
is Left -> {
when (val problem = input.value) {
is NotHandled -> {}
is DataKeyDisabled -> {}
is SubscriberNotFound -> {}
is NotEncrypted -> {}
}
}
}
}
But Kotlin complains about NotHandled, DataKeyDiabled, and SubscriberNotFound are not a DataProblem
In some cases, I want to return a KeyProblem so I can drop the NotEncrypted case from the when, and in some cases I want to return only a Problem such that the only case is NotHandled.
I do not know how to express this in Kotlin. I suspect it is not possible to express this in Kotlin, so if someone tells me it is impossible, that is a solution.
I am thinking it was a bad decision to replace try-catch with Arrow Either. If so, someone please tell me so.
I wanted to stick to Functional Reactive Programming paradigms, where try-catch does not work, but with Kotlin coroutines it sort of does work. 🤔
It seems to me, the problem with sealed things is that when using when you can only have one level of inheritance, and no more?
Maybe I am just looking at the whole problem the wrong way... help... please...
So my solution is to give up on trying to use Arrow Either and Kotlin sealed classes instead of using standard
try {
// return result
}
catch {
// handle or rethrow
}
finally {
// clean up
}
While I have been trying to practice Reactive and non-blocking programming for years, this was easy in Scala, but it's not easy in Kotlin.
After watching enough Java Project Loom videos, I am by far convinced this is the best way to go because exception handling just works... I could use Kotlin Coroutines because they also preserve correct exception handling, and may do that temporarily, but in the long run, Virtual Threads and Structured Concurrency are the way to go.
I hate using these words, but I am making a 'paradigm shift' back to cleaner code, retreating from this rabbit hole I have gone down.
It seems like you are going too far to re-use your error-types, when in fact your functions have different return-types and things that can go wrong. The simplest and cleanest solution in my opinion is to declare both the happy-case and error-case types per function. Then it should be very easy to only handle the cases than can actually go wrong per function.
For example if you have a function getPerson, you would declare the data class Person as the right value, and a GetPersonError as the left value, where the GetPersonError is an interface with only the relevant errors, like so:
private fun getPerson(identifier: String): Either<GetPersonError, Person> {...}
data class Person(name: String, ....)
sealed interface GetPersonError
sealed class PersonNotFoundError(): GetPersonError
sealed class InvalidIdentifierError(): GetPersonError
This does require you to write more code than reusing the same Problem-class for multiple functions, but the code becomes very readable and easy to change, which is much more difficult to achieve when reusing a lot of code.

How does Generic work if generic is Int in Kotlin?

I tried to make abstract class for testing because I found weird problem for using generics
abstract class Test<T> {
open fun hello(vararg data: T) {
print("Default function")
}
}
This very simple abstract class has one opened method with vararg keyword. Problem can be reproduced by making another class which extends Test class.
class Hello : Test<Int>() {
//Problem 1
override fun hello(vararg data: Int) {
super.hello(*data) //Problem 2
println("Override function")
}
}
About first problem, Kotlin says method doesn't override anything even though this method surely overrides something. Weirdly, this error happens randomly, so I can't tell exact way to reproduce this bug
This error got removed when I add some codes (like really simple code such as println(), etc), but when you compile, it causes same error again.
About second problem, super.hello(*data) causes problem because this requires Array<out Int>, but found parameter is IntArray. I think Kotlin is considering IntArray and Array<*> as different class, but it shouldn't act like this...
I'm using Kotlin 1.4.10 which seems the latest version according to this site.
I'm posting this to check if these 2 problems are bug or if I did something incorrectly because when I change generic to String, all problems get removed.
Are there any mistakes I made in these sample codes above?
Known issue: https://youtrack.jetbrains.com/issue/KT-9495
As a workaround, you can use the boxed java.lang.Integer.
class Hello : Test<Integer>() {
override fun hello(vararg data: Integer) {
super.hello(*data)
println("Override function")
}
}

#SerialInfo - How to manage user-defined serial annotations with Kotlinx serialization?

Kotlinx serialization documentation
According to Kotlinx.serialization user-defined annotations doc:
"Inside a process of serialization/deserialization, your own annotation class are available in SerialDescriptor object" :
override fun encodeElement(desc: SerialDescriptor, index: Int): Boolean {
val annotations = desc.getElementAnnotations(index)
...
}
What I want to do
I need a #Transient equivalent, but conditional:
classic way where : Json.stringify(serializer, myClass) works as usual.
custom way where : Json.stringify(customSerializer, myClass) would return usual json but exculding all #MyAnnotation-tagged values.
Here is my code
#SerialInfo
#Target(AnnotationTarget.PROPERTY)
annotation class CustomAnnotation
#Serializable
data class MyClass(val a: String, #CustomAnnotation val b: Int = -1)
And I would like to build a custom Serializer and achieve something like
override fun encodeElement(desc: SerialDescriptor, index: Int): Boolean {
val isTaggedAsCustomAnnotation = desc.getElementAnnotations(index).any{ it is CustomAnnotation }
val myCondition = mySerializer.getMyConditionBlablabla
if(myCondition && isTaggedAsCustomAnnotation) {
encode()
}
...
}
What I found
abstract class ElementValueEncoder : Encoder, CompositeEncoder {
...
open fun encodeElement(desc: SerialDescriptor, index: Int): Boolean = true
}
But I don't know how I can build a custom Serializer so that I can override that function Encoder.encodeElement. Where can I access to ElementValueEncoder in a custom Serializer ?
I also found this sample demo in kotlinx.serialization github repo. It's using TaggedEncoder & TaggedDecoder where I'm able to override encodeTaggedValue. But here again I don't know how I can use those encoder/decoder in a process of serialization/deserialization.
Finally
Where can I override fun encodeElement(desc: SerialDescriptor, index: Int): Boolean, and how I can handle my own-defined serialization annotation ?
Thanks !!
First of all, you need to grasp the difference between Serializer and Encoder. Serializer (represented by KSerializer) defines how your class looks like, and Encoder (represented by e.g. JsonOutput) defines how data will be recorded. You can find more info on that topic here: https://github.com/Kotlin/KEEP/blob/master/proposals/extensions/serialization.md#core-api-overview-and-mental-model .
So, custom annotations feature is mainly used for providing format-specific information to Encoder. Typical usage of such an annotation is ProtoId – property id, specific to protobuf format, that should be recognized by ProtobufEncoder. Such annotations are usually defined by format authors alongside their encoders.
What you want to do here, as I can see, is to use already existing encoder (JSON format), so overriding encodeElement is impossible since Json encoders can not be subclassed. I'd advise you to use custom json transofrming serializer to achieve your goal. Unfortunately, currently kotlinx.serialization does not have mechanism to generalize such a transformation, so you need to write such serializer for each class.

why there is 'by' for the extended class and reified in function define

coming across a sample with a class and a function and trying to understand the koltin syntax there,
what does this IMeta by dataItem do? looked at https://kotlinlang.org/docs/reference/classes.html#classes and dont see how to use by in the derived class
why the reified is required in the inline fun <reified T> getDataItem()? If someone could give a sample to explain the reified?
class DerivedStreamItem(private val dataItem: IMeta, private val dataType: String?) :
IMeta by dataItem {
override fun getType(): String = dataType ?: dataItem.getType()
fun getData(): DerivedData? = getDataItem()
private inline fun <reified T> getDataItem(): T? = if (dataItem is T) dataItem else null
}
for the reference, copied the related defines here:
interface IMeta {
fun getType() : String
fun getUUIDId() : String
fun getDataId(): String?
}
class DerivedData : IMeta {
override fun getType(): String {
return "" // stub
}
override fun getUUIDId(): String {
return "" // stub
}
override fun getDataId(): String? {
return "" // stub
}
}
why the reified is required in the inline fun <reified T> getDataItem()? If someone could give a sample to explain the reified?
There is some good documentation on reified type parameters, but I'll try to boil it down a bit.
The reified keyword in Kotlin is used to get around the fact that the JVM uses type erasure for generic. That means at runtime whenever you refer to a generic type, the JVM has no idea what the actual type is. It is a compile-time thing only. So that T in your example... the JVM has no idea what it means (without reification, which I'll explain).
You'll notice in your example that you are also using the inline keyword. That tells Kotlin that rather than call a function when you reference it, to just insert the body of the function inline. This can be more efficient in certain situations. So, if Kotlin is already going to be copying the body of our function at compile time, why not just copy the class that T represents as well? This is where reified is used. This tells Kotlin to refer to the actual concrete type of T, and only works with inline functions.
If you were to remove the reified keyword from your example, you would get an error: "Cannot check for instance of erased type: T". By reifying this, Kotlin knows what actual type T is, letting us do this comparison (and the resulting smart cast) safely.
(Since you are asking two questions, I'm going to answer them separately)
The by keyword in Kolin is used for delegation. There are two kinds of delegation:
1) Implementation by Delegation (sometimes called Class Delegation)
This allows you to implement an interface and delegate calls to that interface to a concrete object. This is helpful if you want to extend an interface but not implement every single part of it. For example, we can extend List by delegating to it, and allowing our caller to give us an implementation of List
class ExtendedList(someList: List) : List by someList {
// Override anything from List that you need
// All other calls that would resolve to the List interface are
// delegated to someList
}
2) Property Delegation
This allows you to do similar work, but with properties. My favorite example is lazy, which lets you lazily define a property. Nothing is created until you reference the property, and the result is cached for quicker access in the future.
From the Kotlin documentation:
val lazyValue: String by lazy {
println("computed!")
"Hello"
}