Skip to content

Getting serializer from contextual given only KType #2967

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

Closed
eduard1abdulmanov123 opened this issue Apr 3, 2025 · 8 comments
Closed

Getting serializer from contextual given only KType #2967

eduard1abdulmanov123 opened this issue Apr 3, 2025 · 8 comments
Labels

Comments

@eduard1abdulmanov123
Copy link

eduard1abdulmanov123 commented Apr 3, 2025

I have the following method and I have my class which is generated. For the generated class I have a self-written serializer that is connected to the SerializersModule. I need both the Serializable annotation, i.e. the generated serializer and the custom serializer. How can I implement a function from Json to use the serializer from the module. Thanks in advance

public class JsonElementConverter(
    private val json: Json,
) {

    public fun <T : Any> fromJson(element: JsonElement, type: KType): T {
        return json.decodeFromJsonElement(json.serializersModule.serializer(type) as KSerializer<T>, element)
    }
}

@Serializable
public data class CustomObject(
    val field1: String,
    val field2: String,
)

public object CustomObjectSerializer : KSerializer<CustomObject> {

    override val descriptor: SerialDescriptor
        get() = TODO("Not yet implemented")

    override fun deserialize(decoder: Decoder): CustomObject {
        TODO("Not yet implemented")
    }

    override fun serialize(encoder: Encoder, value: CustomObject) {
        TODO("Not yet implemented")
    }
}

public fun main() {
    val json = Json {
        serializersModule = SerializersModule {
            contextual(CustomObject::class, CustomObjectSerializer)
        }
    }
    val jsonElementConverter = JsonElementConverter(json)
    val jsonElement = buildJsonArray {
        add(buildJsonArray {
            add(buildJsonObject {
                put("field1", JsonPrimitive("field1"))
                put("field2", JsonPrimitive("field2"))
            })
        })
    }

    val type = typeOf<List<List<CustomObject>>>()
    val result = jsonElementConverter.fromJson<List<List<CustomObject>>>(jsonElement, type)
    println(result)
}
@sandwwraith
Copy link
Member

@eduard1abdulmanov123
Copy link
Author

For annotation @KeepGeneratedSerializer , you need to pass a custom serializer to the annotation @Serializable, but we can't do that because models are generated

@pdvrieze
Copy link
Contributor

pdvrieze commented Apr 8, 2025

@eduard1abdulmanov123 If you are generating models, you would also have generated serializers (either by generating the proper Kotlin source, or just generating a serializer yourself). But note that kotlinx.serialization is a compile time system. It doesn't support reflection based serialization itself (if you create a way to get correct serializers then the library/formats would not know the difference)

@eduard1abdulmanov123
Copy link
Author

eduard1abdulmanov123 commented Apr 11, 2025

But if we consider the issue a little differently. For example, we have a CustomObject model that is used by many projects. For this model, there is a custom serializer, which is specified in the @Serializable annotation. But one of the projects needed to override the default behavior of the serializer, and this project writes its own serializer and adds it via contextual. How to achieve such behavior so that this code jsonElementConverter.fromJson<List<List<CustomObject>>>(jsonElement, type) first searches for the overridden serializer, and then the default one. This code is common for all projects jsonElementConverter.fromJson<List<List<CustomObject>>>(jsonElement, type)

public class JsonElementConverter(
    private val json: Json,
) {

    public fun <T : Any> fromJson(element: JsonElement, type: KType): T {
        return json.decodeFromJsonElement(json.serializersModule.serializer(type) as KSerializer<T>, element)
    }
}

@pdvrieze
Copy link
Contributor

Your code sample could directly ask the contextual serializer from the serializersModule, but you do need to keep in mind that this would not work for member types directly.

There is really only one way to reliably adjust the serializers used for specific types. That is to have a format that replaces the serializer used as appropriate. You would thus have to create a format that wraps Json (in this case) and replaces the serializer/deserializer as appropriate.

@eduard1abdulmanov123
Copy link
Author

that is, I need to implement this method manually, only changing the priority of the serializers, first the context one, then the generated one. And then pull the implemented method in the methods of my SerialFormat. Did I understand you correctly?

Image

@pdvrieze
Copy link
Contributor

@eduard1abdulmanov123 Effectively yes, in your encoder/decoders (with your format) you need to override encodeSerializableElement (and similar). They pass in a serializer, but what you want to do is first look for a contextual serializer (from the serializersModule), and only if not found use the hardcoded serializer.

@eduard1abdulmanov123
Copy link
Author

Thank you very much for your answers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants