Serialization

Table of contents
  1. Default configuration
  2. Customizing
  3. Why these defaults?
    1. ignoreUnknownKeys = true is about schema evolution
    2. encodeDefaults = true is about query correctness
    3. useArrayPolymorphism = true covers sealed hierarchies
  4. Custom SqkonSerializer
  5. Next

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 where DSL generates and why encodeDefaults matters.