Discussion: Non-intrusive serialization declaration - Reducing annotation pollution in KMP/JVM ecosystems
Problem Statement: The Annotation Intrusiveness Dilemma Currently, Kotlinx Serialization follows an intrusive approach where classes must be annotated with @Serializable to enable serialization. This creates several ecosystem challenges:
The Real-world Pain Point kotlin // Library author's code - they don't care about serialization data class ApiResponse( val data: String, val status: Int, val metadata: Map<String, Any> )
// Consumer wants to serialize this - BUT CAN'T! // They're forced to use workarounds: val response = ApiResponse("hello", 200, emptyMap())
// ❌ This doesn't work - no @Serializable annotation // Json.encodeToString(response)
// ✅ Consumers must create wrapper classes - boilerplate heaven! @Serializable data class SerializableApiResponse( val data: String, val status: Int, val metadata: Map<String, Any> ) { constructor(original: ApiResponse) : this(...) } Ecosystem Impact Library Authors: Forced to add Kotlinx Serialization as a dependency and annotate their models
KMP Compatibility: Libraries aiming for minimal dependencies can't use serialization without forcing it on consumers
Legacy Code: Impossible to serialize classes from Java libraries or older Kotlin code
Annotation Pollution: Every data class gets @Serializable whether it needs serialization or not
Proposed Solution: Consumer-side Declaration Enable serialization declaration at the consumption site rather than the declaration site.
Option A: File-level Declaration kotlin // In consumer module - no library modification needed @file:GenerateSerializersFor( com.some.library.ApiResponse::class, com.some.library.UserDTO::class )
fun main() { val response = ApiResponse("data", 200, mapOf()) val json = Json.encodeToString(response) // Just works! 🎉 } Option B: Configuration-based kotlin // serialization-config.kts externalClasses { serializable(ApiResponse::class) serializable(UserDTO::class) { rename("userId" to "user_id") } } Option C: Extension-based kotlin // Declare serializability via extensions val ApiResponse.serializer: KSerializer<ApiResponse> @ExperimentalSerializationApi get() = generatedSerializer() Benefits for KMP/JVM Ecosystem For Library Authors Zero dependency: No need to depend on kotlinx-serialization
Clean API: Models remain framework-agnostic
Better KMP support: Minimal dependencies for multiplatform libraries
For Consumers No wrappers: Direct serialization of any class
Flexibility: Choose serialization strategy per use case
Migration friendly: Gradually adopt serialization without refactoring
For Kotlin Ecosystem Interop: Serialize Java classes, third-party library models
Progressive: Works alongside existing @Serializable approach
Tooling: IDE support for external class configuration
Technical Considerations Compiler Plugin Extension: Extend KCP to process consumer declarations
Symbol Resolution: Handle external classes from binaries/dependencies
Module System: Ensure generated serializers are properly registered
Incremental Compilation: Maintain build performance
Community Impact This change would position Kotlinx Serialization as a more ecosystem-friendly solution, competing better with reflection-based alternatives like Jackson while maintaining compile-time safety.
Discussion Points Is consumer-side declaration feasible with current compiler architecture?
What's the optimal syntax for declaring external serializers?
How to handle complex cases (custom serializers, polymorphic hierarchies)?
Should this be a separate plugin or integrated into the main one?
I'm sorry I didn't notice that this feature was already in the documentation ;Deriving external serializer for another Kotlin class (experimental) by the way, ask if there is a way to scan the package, so it will be lazier.
I'm sorry I didn't notice that this feature was already in the documentation ;Deriving external serializer for another Kotlin class (experimental) by the way, ask if there is a way to scan the package, so it will be lazier.
As to declarative writing of serializers, it ends up being very similar to directly writing a custom serializer, but with more abstraction overheads. Providing serializer access through an extension function is mostly equivalent to having a file level annotation to have it used/picked up. Note that serializing your ApiResponse does not require a separate class, it just requires a custom serializer.
I need examples that are both custom serialized and consumer-defined, with generic parameters
As the error message says, your FormFieldSerializerConsumer would need to be a class (the same as the FormFieldSerializer) – in other words they would need to be identical in some way. You should however note that it is not possible to have an externally generated serializer for FormField as it is a sealed class (it would be "generated" as an instance of SealedSerializer anyway.
For the problem of having a library contain serializers, but those not being used by a consumer, this is where minification would come in. Looking at the way the plugin generates things, serialization is present in a number of functions/constructors and fields of the class and its companion (it appears that using the class without the serialization library will break even if serialization is not used).