Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to specify custom serializers in Services #89

Closed
Neitex opened this issue Jun 3, 2024 · 6 comments
Closed

Ability to specify custom serializers in Services #89

Neitex opened this issue Jun 3, 2024 · 6 comments
Milestone

Comments

@Neitex
Copy link

Neitex commented Jun 3, 2024

Hi! I'm currently trying to implement RPC for database access for an admin panel of my pet-project. Server-side database API utilizes Java's LocalDate and LocalDateTime classes, which do not have default serializers in kotlinx.serialization, but I've written custom serializers for them by hand.

I've tried to annotate all LocalDate fields with @Serializable() annotation specifying serializer to use, but that didn't help and build still failed with messages like: DatabaseAbsenceRPCClient.kt:58:58 Serializer has not been found for type 'LocalDate'. To use context serializer as fallback, explicitly annotate type or property with @Contextual. Not including these annotations doesn't affect build's failure to build too. :)

Declaration file:

interface DatabaseAbsenceRPC : RPC {
 // ...
 suspend fun getAbsences(date: @Serializable(with = LocalDateSerializer::class) LocalDate): List<AbsenceRecord>
}

So, my question is: would it be possible to somehow read these annotations in code-generation time and apply them to the generated code too? I think, I'm not the only one who uses custom serializers in kotlinx.serialization, so that would be a nice-to-have feature so we don't have to write boilerplate code to transform from-LocalDate-to-String and back :)

Awesome work on the library, btw! Looks promising!

@Mr3zee
Copy link
Collaborator

Mr3zee commented Jun 3, 2024

Hi! Thank for the kind words and your question!

Indeed, right now @Serializable(with = ...) is not supported.
Looking at it, I'm not sure this the way to support it, but the use case is valid still.
For the current version, you may use this workaround:

// todo update serializer to work with the value class
@Serializable(with = LocalDateSerializer::class) 
@JvmInline
value class LocalDateValue(val value: LocalDate)

interface DatabaseAbsenceRPC : RPC {
    suspend fun getAbsences(date: LocalDateValue): List<AbsenceRecord>
}

We actually support @Contextual annotation, so in general case you should be able to do this:

interface DatabaseAbsenceRPC : RPC {
    suspend fun getAbsences(@Contextual date: LocalDate): List<AbsenceRecord>
}

// and, for example, with kRPC protocol

rpcClientConfig {
    serialization {
        json { // for example, json
            serializersModule = SerializersModule {
                contextual(LocalDate::class) {
                    // do the thing
                }
            }
        }
    }
}

But our current codegen does not copy the parameter annotation, with is a problem on our side. (So it actually works with custom classes as arguments with contextual properties inside them, but not with the method arguments themselves)
I will look into how and which annotations we should support. The case with @contextual will definitely be fixed, and maybe we will add some others.

@martin-ncs
Copy link

Hi, we are currently implementing RPC and we'd appreciate having @Contextual support, any idea when it will released?

@Mr3zee
Copy link
Collaborator

Mr3zee commented Jun 27, 2024

Hi! Probably with or right after K2 support in 0.2.1

@Mr3zee Mr3zee added this to the 0.5.0 milestone Nov 5, 2024
@Mr3zee
Copy link
Collaborator

Mr3zee commented Nov 22, 2024

Ok, so funny thing

This (what is basically kotlinx-rpc is generating) is not working from the kotlinx-serialization side.

data class Some(
    val date: @Serializable(LocalDateSerializer::class) LocalDate,
)

fun main() {
    Json.encodeToString(Some(LocalDate.now()))
}

However, this does work

data class Some(
    val date: @Contextual LocalDate,
)

fun main() {
    Json {
        serializersModule = SerializersModule {
            contextual(LocalDate::class) { LocalDateSerializer }
        }
    }.encodeToString(Some(LocalDate.now()))
}

And this does work too!

@Rpc
interface Service : RemoteService {
	suspend fun nonSerializableClassContextual(localDate: @Contextual LocalDate): LocalDate
}

And apparently it did all this time 😄

@Mr3zee
Copy link
Collaborator

Mr3zee commented Nov 22, 2024

I was wrong about the first use case, actually, will check what is wrong

@Mr3zee Mr3zee reopened this Nov 22, 2024
@Mr3zee
Copy link
Collaborator

Mr3zee commented Nov 25, 2024

Ok, just a heads up:

@Rpc
interface Service : RemoteService {
	// works
	suspend fun func1(localDate: @Contextual LocalDate)

	// works
	suspend fun func1(localDate: @Contextual LocalDate): LocalDate

	// works
	suspend fun func1(localDate: @Serializable(with = ...) LocalDate)

	// doesn't work
	suspend fun func1(): @Serializable(with = ...) LocalDate
}

Immediately, there is no intention to fix the last case, as the @contextual serializers cover it. Maybe later

@Mr3zee Mr3zee closed this as completed Nov 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants