Write a custom KSerializer for a value type using PrimitiveSerialDescriptor. Why must the descriptor's serialName be unique, and what determines the PrimitiveKind?
answer
- PrimitiveSerialDescriptor(name, PrimitiveKind)
- serialName unique — often the FQ class name
- kind must match encodeString/encodeLong call
- wrong name => bad polymorphic dispatch
- scalar => primitive descriptor, not buildClassSerialDescriptor
basics
~20 sUse PrimitiveSerialDescriptor with a unique name and the basic kind (string, long, etc.) your wire value has. The name must be unique so the format can tell types apart; the kind matches the encode/decode call you actually use.
solid answer
~40 sFor a type that maps to a single scalar, use `PrimitiveSerialDescriptor(serialName, kind)`. The `serialName` is a globally identifying string — it must be unique across all descriptors because formats (and polymorphism/registries) key on it; reusing a name can cause collisions or wrong dispatch. The `PrimitiveKind` (STRING, LONG, INT, BOOLEAN, DOUBLE, etc.) must match the encoder call you make: if `serialize` calls `encoder.encodeString(...)`, the kind must be `STRING`; if it calls `encodeLong`, use `LONG`. Mismatches lead to format errors or incorrect schemas (e.g. ProtoBuf field types). The descriptor is created once (often as a property) and reused; it should never be a full `buildClassSerialDescriptor` for a scalar — that would model it as an object.
code
kotlin · 8 linesobject UserIdSerializer : KSerializer<UserId> {
override val descriptor =
PrimitiveSerialDescriptor("com.acme.UserId", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: UserId) =
encoder.encodeLong(value.raw)
override fun deserialize(decoder: Decoder): UserId =
UserId(decoder.decodeLong())
}go deeper
Can call PrimitiveSerialDescriptor with a name and kind and a matching encode call.
Explains why the name must be unique and why kind must match the encoder method.
Ties serialName uniqueness to polymorphic dispatch and self-describing formats; uses FQ-name convention.
Treats the descriptor as part of the public wire schema and reasons about its impact on ProtoBuf/CBOR tooling and evolution.
## PrimitiveSerialDescriptor When your type is conceptually a single scalar (a wrapped String, an ID that's really a Long), its `SerialDescriptor` should be **primitive**. You build it with: ```kotlin PrimitiveSerialDescriptor(serialName: String, kind: PrimitiveKind) ``` `PrimitiveKind` is one of `BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, STRING`. ## serialName must be unique The `serialName` is the descriptor's identity. It matters because: - **Polymorphism & registries** use it as the type discriminator key. Two distinct serializers sharing a name can collide in a `SerializersModule` or sealed-class registry. - **Self-describing formats** (e.g. ProtoBuf schema generation, debugging) print it. - A common convention is the fully-qualified class name (e.g. `"com.acme.Color"`) to guarantee uniqueness. Reusing a name across two unrelated types is a latent bug — most often it surfaces as wrong polymorphic dispatch, not an immediate crash. ## PrimitiveKind must match the encode/decode calls The descriptor declares the *shape*; the `serialize`/`deserialize` bodies must agree: ```kotlin object ColorAsHexSerializer : KSerializer<Color> { override val descriptor = PrimitiveSerialDescriptor("com.acme.Color", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: Color) { encoder.encodeString("#%06x".format(value.rgb)) // STRING -> encodeString } override fun deserialize(decoder: Decoder): Color { return Color(decoder.decodeString().removePrefix("#").toInt(16)) } } ``` If the kind were `LONG` but you called `encodeString`, formats that rely on the descriptor (ProtoBuf, CBOR) would build a wrong schema or fail. JSON is more forgiving at runtime but the descriptor still drives things like ProtoBuf field types and tooling. ## Don't model a scalar as a class Using `buildClassSerialDescriptor` for a scalar would wrap it in an object `{ ... }`. For a single value, `PrimitiveSerialDescriptor` keeps it a bare scalar on the wire.
- What happens if descriptor kind is LONG but serialize calls encodeString?The descriptor and behavior disagree. Schema-driven formats (ProtoBuf/CBOR) build a wrong or invalid schema; it's a bug even if JSON happens to tolerate it. Keep them consistent.
- What's a safe convention for serialName?Use the fully-qualified type name so it's globally unique and won't collide in polymorphic registries.
saying these in an interview costs you the question
- Hardcoding a non-unique serialName like "value"
- Mismatching PrimitiveKind with the encode call
- Using buildClassSerialDescriptor for a single scalar
- Thinking serialName is cosmetic and never matters
- Recreating the descriptor on every serialize call instead of once