kotlinx.serialization supports more than one way of laying out polymorphic data on the wire. Explain ARRAY_WRAPPED vs the discriminator object mode, and when you must use which.
answer
- object mode injects 'type' field; needs an object payload
- ARRAY_WRAPPED = [typeName, payload]
- non-object subtype (value class/primitive) forces array mode
- useArrayPolymorphism = true / classDiscriminatorMode
- object mode = REST-friendly default
basics
~20 sNormally the type name is a field inside the object (discriminator mode). But that only works when the value is a JSON object. For non-object values (like a primitive), the library wraps it as a two-element array [typeName, value]. You can also force array mode.
solid answer
~40 sThe default JSON polymorphism is JsonClassDiscriminator mode: an extra field (default "type") is injected into the serialized **object**. This requires the subtype to serialize to a JSON **object** — you can't inject a key into a JSON number or array. To handle non-object or structurally conflicting cases, kotlinx.serialization can use ARRAY_WRAPPED mode, emitting a two-element array: [discriminatorValue, payload]. You opt in via Json { classDiscriminatorMode = ... } or by setting useArrayPolymorphism = true (legacy flag). Array mode is mandatory when a subtype's serializer produces a non-object (e.g., a value class serializing to a primitive) or when the payload could itself contain a key colliding with the discriminator. The trade-off: array mode is less human-readable and not idiomatic REST JSON, so prefer the object/discriminator mode for public APIs unless a subtype forces array mode.
go deeper
Aware that the type tag normally lives inside the JSON object.
Can switch the discriminator key and knows array mode exists as an alternative layout.
Explains exactly when array mode is mandatory (non-object subtypes, key collisions) and the readability trade-off.
Sets format policy across services: object mode for public APIs, array mode only where forced, with versioned discriminator governance.
## Two on-wire layouts kotlinx.serialization can encode a polymorphic value two ways: ### 1. Discriminator-in-object (default) The type tag is an **extra field** inside the object: ```json { "type": "click", "x": 3, "y": 4 } ``` Controlled by `JsonClassDiscriminator` / `Json { classDiscriminator = "type" }`. This is clean, REST-friendly, and the usual choice. ### 2. ARRAY_WRAPPED The value is encoded as a **two-element JSON array** `[discriminator, payload]`: ```json [ "click", { "x": 3, "y": 4 } ] ``` Enabled by `Json { useArrayPolymorphism = true }` (legacy boolean) or by the broader `classDiscriminatorMode` setting. ## Why array mode exists — when you MUST use it The object/discriminator mode **injects a key into the payload object**. That's impossible (or unsafe) when: - **The subtype doesn't serialize to an object.** A value/inline class or a custom serializer can serialize to a JSON **primitive** or **array**. You can't add a `"type"` field to `42` or to `[1,2,3]`. Array wrapping is then the only correct layout. - **Key collision risk.** If the payload itself contains a field literally named like the discriminator, injecting the discriminator would clash. Object mode actually throws if the payload already has the discriminator key; array mode sidesteps this because the tag lives outside the payload. ## How to choose ```kotlin // Object / discriminator mode (default) - REST-friendly val rest = Json { classDiscriminator = "type" } // Array mode - required for non-object subtypes val arr = Json { useArrayPolymorphism = true } ``` - **Public HTTP/JSON APIs**: prefer object mode; it's idiomatic and readable. - **Subtypes that are value classes / primitive-serializing / custom non-object serializers**: array mode is required. - **Internal/storage formats where readability is irrelevant**: either; array mode avoids collisions. ## Discriminator-output controls `classDiscriminatorMode` also lets you suppress the discriminator entirely (e.g., `POLYMORPHIC` vs `ALL` vs `NONE`-style behavior depending on version) — useful when the type is implied by context and you don't want the tag emitted. Be careful: if you stop emitting a discriminator, decoding back to the base type can become ambiguous. ## Key APIs/keywords `classDiscriminator`, `useArrayPolymorphism`, `classDiscriminatorMode`, `JsonClassDiscriminator`, value/inline classes, custom serializers, `JsonObject` vs `JsonArray`.
- What error can object/discriminator mode raise that array mode avoids?If the payload already contains a field with the discriminator key name, object mode conflicts/throws; array mode keeps the tag outside the payload.
- Why can't a value class serializing to a primitive use object discriminator mode?There's no JSON object to inject a 'type' field into — the value is a bare number/string — so array wrapping is required.
saying these in an interview costs you the question
- Claiming object discriminator mode works for any subtype including primitives
- Not knowing array mode exists / why it's needed
- Confusing classDiscriminator (the key string) with useArrayPolymorphism (the layout)
- Recommending array mode for public REST APIs without reason