Serialization
Table of contents
Sqkon stores values as JSON inside SQLite (using SQLite’s JSONB representation) and queries against that JSON. The serializer therefore controls what is on disk and what you can query. This page explains the defaults Sqkon ships with, why they were chosen, and how to override them.
Default configuration
When you don’t pass a Json to the Sqkon(...) factory, Sqkon uses
SqkonJson { } — a tuned kotlinx.serialization.Json instance defined in
KotlinSqkonSerializer.kt.
Its defaults are:
fun SqkonJson(builder: JsonBuilder.() -> Unit) = Json {
ignoreUnknownKeys = true
encodeDefaults = true
useArrayPolymorphism = true
builder()
}
| Setting | Value | Why |
|---|---|---|
ignoreUnknownKeys |
true |
Forward compatibility. When you remove or rename a field, existing rows containing the old key still deserialize cleanly. |
encodeDefaults |
true |
Required to make queries on default-valued fields work. If a field is missing in JSON because it equals its default, where Type::field eq default would silently fail to match. |
useArrayPolymorphism |
true |
Allows polymorphic serialization of sealed hierarchies that contain value classes without custom descriptors — see the kotlinx.serialization issue for the full story. |
Changing any of these defaults can silently break querying or migration behavior. Read the rest of this page before turning them off.
Customizing
Pass a custom Json to the factory. The easiest path is to layer your tweaks
on top of SqkonJson { }:
import com.mercury.sqkon.db.serialization.SqkonJson
val json = SqkonJson {
prettyPrint = false
isLenient = true
}
val sqkon = Sqkon(context = this, scope = appScope, json = json)
You can also build a fully custom Json if you need to deviate from the
defaults — but doing so opts out of the guarantees above:
val json = Json {
encodeDefaults = true // keep — required for query correctness
ignoreUnknownKeys = false // your call; see "Migrations" below
serializersModule = myCustomModule
}
Why these defaults?
ignoreUnknownKeys = true is about schema evolution
Sqkon is a key-value store, not a managed-schema database. When you change a
data class — adding a field, removing a field, renaming with @SerialName —
existing rows in SQLite still hold the old JSON shape. Setting
ignoreUnknownKeys = false would cause every read of an old row to throw
after a refactor.
If you set this to false, you take responsibility for migrating values when
you change a class. See Migrations
for patterns.
encodeDefaults = true is about query correctness
Sqkon’s where DSL compiles down to JSON path queries. If a field is omitted
from the stored JSON because its value matched the Kotlin default,
Merchant::category eq "Food" will not match a row whose category defaulted
to "Food" — the JSON path simply isn’t there. Keeping encodeDefaults = true
ensures every property is materialized and queryable.
useArrayPolymorphism = true covers sealed hierarchies
If you store a sealed type and one of its branches contains a value class
without a custom serializer, the standard polymorphic encoder fails. Array
polymorphism sidesteps this. If you have your own polymorphic strategy
(classDiscriminator, custom serializers), feel free to override.
Custom SqkonSerializer
KotlinSqkonSerializer is the default implementation of the SqkonSerializer
interface. If you need to use a non-kotlinx serialization library — Moshi,
Jackson, protobuf — implement the interface yourself:
class MyMoshiSerializer(private val moshi: Moshi) : SqkonSerializer {
override fun <T : Any> serialize(type: KType, value: T?): String? =
value?.let { moshi.adapter<Any>(type.javaType).toJson(it) }
override fun <T : Any> deserialize(type: KType, value: String?): T? =
value?.let { moshi.adapter<Any>(type.javaType).fromJson(it) as? T }
}
Whatever serializer you plug in must produce valid JSON — Sqkon stores the result with SQLite’s JSONB and queries against it. Binary formats like protobuf or CBOR will break querying.
A custom SqkonSerializer is what the public top-level keyValueStorage(...)
factory accepts as its serializer parameter. The factory needs an
EntityQueries / MetadataQueries pair plus a driver, so wiring it up means
constructing your own SqlDriver rather than reusing the one inside Sqkon
(those internals are not part of the supported public surface).
For most use cases the easier option is to stick with kotlinx.serialization
and pass a custom Json to Sqkon(...) instead — see Customizing
above. If you genuinely need a non-kotlinx serializer (e.g. a Moshi
adapter for legacy data shapes), open an issue describing the use case and
we’ll add the public seam needed to wire one in.
Next
- Quickstart — try the defaults end-to-end.
- Serialization tips — sealed classes, value classes, and migration strategies.
- Querying guide — what JSON paths the
whereDSL generates and whyencodeDefaultsmatters.