Slumber

A serialization framework built for Kotlin's type system. Nullable types, default values, sealed classes — handled without annotations.

Kotlin has a rich type system — serialization should use it

Kotlin distinguishes String from String? at the type level. Data classes have default parameters. Sealed classes define closed hierarchies. These are powerful features for modeling data correctly. But most serialization frameworks predate Kotlin or target multiple languages — they don't see these distinctions. So you annotate, configure, and work around.

We hit this exact problem while building Karango. We needed something that understood Kotlin types natively. So we wrote one. Whether that was wise is a separate question.

What Slumber does differently

You have a Kotlin data class. You want to serialize it to a Map (for a database, an API, a cache). Slumber uses Kotlin reflection to understand your types — nullable fields, default values, generics, sealed classes — and handles them without annotations or compiler plugins.

When deserialization fails, it tells you exactly which nested field caused it, with the full path.

The Solution

data class User(val name: String, val age: Int, val email: String? = null)

val codec = Codec.default

// Serialize ("slumber")
val map = codec.slumber(User("Alice", 30))
// → { "name": "Alice", "age": 30, "email": null }

// Deserialize ("awake")
val user = codec.awake<User>(mapOf("name" to "Bob", "age" to 25))
// → User(name="Bob", age=25, email=null)

That's it. No annotations needed. No compiler plugin. Slumber uses Kotlin reflection to understand your data classes — nullable fields, default values, generics, and all.

What Makes It Different

Kotlin-First

Understands String vs String?, default parameters, data classes, sealed classes — at the type level.

Graceful Degradation

Nullable field receives bad data? Returns null instead of crashing. Required field fails? Detailed path in the error.

Two-Pass Error Handling

Fast pass first, no overhead. Only builds full diagnostic path on failure — performance when it works, clarity when it doesn't.

Format-Agnostic

Works with Maps and Lists, not JSON strings. Use it for databases, REST APIs, caches, or any storage backend.

Pluggable Modules

Custom type handling via SlumberModule. No annotation processors — just implement an interface.

Polymorphism Built-In

Sealed classes auto-detected. Custom discriminators, migration aliases, default fallback types — all supported.

The Naming

Slumber uses a playful metaphor:

  • Slumber = serialize. Put your objects to sleep as raw data.
  • Awake = deserialize. Wake your data back up as typed objects.

Quick Taste

Polymorphic sealed classes — zero configuration:

sealed class Event {
    data class UserCreated(val userId: String) : Event()
    data class UserDeleted(val userId: String, val reason: String?) : Event()
}

val codec = Codec.default

// Slumber a list of mixed types
val data = codec.slumber(listOf(
    Event.UserCreated("alice"),
    Event.UserDeleted("bob", reason = "inactive"),
))
// → [
//   { "_type": "...UserCreated", "userId": "alice" },
//   { "_type": "...UserDeleted", "userId": "bob", "reason": "inactive" }
// ]

// Awake it back — types are restored automatically
val events = codec.awake<List<Event>>(data)