Explain @EncodeDefault and the encodeDefaults Json setting. How do you ensure (or prevent) default-valued properties appearing in the output?
answer
- encodeDefaults: global write-defaults switch (Json default = true)
- @EncodeDefault per-property override: ALWAYS / NEVER
- No-arg @EncodeDefault == ALWAYS
- Encode-only; orthogonal to @Required (decode)
- @Required + NEVER => broken round-trip
basics
~10 sBy default kotlinx-json writes out properties even when they equal their default. encodeDefaults=false drops them. @EncodeDefault overrides this per property: ALWAYS forces it written, NEVER forces it skipped, regardless of the global setting.
solid answer
~40 sTwo controls govern whether a default-valued property is written. Globally, Json { encodeDefaults = ... } — for Json the default is true (defaults ARE written); set false to omit a property whose value equals its declared default, shrinking payloads. Per-property, @EncodeDefault(Mode.ALWAYS | Mode.NEVER) overrides the global setting for that one property: ALWAYS guarantees it is always emitted even if encodeDefaults=false; NEVER guarantees it is never emitted even if encodeDefaults=true. @EncodeDefault without an argument defaults to ALWAYS. This only affects ENCODING — it has nothing to do with whether the key may be omitted from input (that's @Required). Watch the round-trip interaction: encodeDefaults=false + @Required on the same property means you omit it on output yet demand it on input, breaking re-decode. Note encodeDefaults default differs by format/config, so be explicit.
code
kotlin · 11 linesval compact = Json { encodeDefaults = false }
@Serializable
data class Doc(
val id: Int,
@EncodeDefault(EncodeDefault.Mode.ALWAYS) val version: Int = 1,
val note: String = "",
)
compact.encodeToString(Doc(7))
// {"id":7,"version":1} -- note omitted (=default), version forced by ALWAYSgo deeper
Knows defaults can appear or be omitted and that a setting controls it.
Distinguishes encodeDefaults global vs @EncodeDefault per-property with ALWAYS/NEVER.
Explains override precedence, no-arg default = ALWAYS, encode-only scope, and the round-trip hazard with @Required.
Designs payload-size vs explicitness policy across a model and formats, ensuring decode/encode symmetry and schema-version fields always present.
## The problem: do defaults get written? When a property equals its **default value**, should the encoder emit the key or skip it? kotlinx.serialization gives a global switch and a per-property override. ## Global: Json { encodeDefaults } ```kotlin val verbose = Json { encodeDefaults = true } // default for Json: write defaults val compact = Json { encodeDefaults = false } // omit values equal to their default @Serializable data class Box(val a: Int, val b: Int = 0) verbose.encodeToString(Box(1)) // {"a":1,"b":0} compact.encodeToString(Box(1)) // {"a":1} ``` `encodeDefaults = false` is the common choice for compact payloads: a property is omitted **iff** its current value equals its declared default. ## Per-property: @EncodeDefault(mode) `@EncodeDefault` overrides the global setting for a single property: ```kotlin import kotlinx.serialization.EncodeDefault import kotlinx.serialization.EncodeDefault.Mode @Serializable data class Settings( val name: String, @EncodeDefault(Mode.ALWAYS) val retries: Int = 3, // always written @EncodeDefault(Mode.NEVER) val debug: Boolean = false, // never written ) ``` - **Mode.ALWAYS** — always emit, even when `encodeDefaults = false`. - **Mode.NEVER** — never emit when equal to default, even when `encodeDefaults = true`. - **`@EncodeDefault` with no argument == `Mode.ALWAYS`.** This lets you mix: keep payloads compact globally but force a few critical fields to always appear (e.g. a schema version), or keep verbose globally but hide a noisy field. ## Encode-only — and the @Required interaction `@EncodeDefault`/`encodeDefaults` affect **encoding only**. Whether a key may be **omitted from input** is governed by defaults + `@Required` (decoding). The dangerous combination: ```kotlin @Serializable data class R( @Required @EncodeDefault(Mode.NEVER) val v: Int = 0 ) // Encodes WITHOUT v, but decoding the result fails: v is @Required. ``` This breaks round-tripping: you produced output that you cannot read back. So align the two: if a field is `@Required`, make sure it is actually encoded (don't `NEVER` it / don't rely on `encodeDefaults=false` for it). ## Mental checklist - Want smaller payloads? `encodeDefaults = false`. - Want one field always present regardless? `@EncodeDefault(Mode.ALWAYS)`. - Want one field never present when default? `@EncodeDefault(Mode.NEVER)`. - Must the field be present on **input**? That's `@Required`, a separate axis. ## Gotchas - The `encodeDefaults` default value can differ between formats/builders, so set it explicitly when it matters. - Omission is by **value equality** to the default, not by whether the caller 'set' it — Kotlin has no notion of 'was this assigned'.
- What does @EncodeDefault with no Mode argument mean?It defaults to Mode.ALWAYS — the property is always encoded even if encodeDefaults is false.
- How does @EncodeDefault interact with @Required?They are orthogonal: @EncodeDefault controls encoding, @Required controls decoding. Combining NEVER with @Required can produce output that cannot be re-decoded.
encodeDefaults is the printer's 'print blank fields?' setting; @EncodeDefault is a per-field stamp that overrides it as 'always print' or 'never print'.
saying these in an interview costs you the question
- Saying @EncodeDefault controls whether a key may be omitted from input
- Thinking Mode.NEVER also makes the field optional on decode
- Assuming encodeDefaults is false by default for Json
- Believing omission tracks 'was the property explicitly set' rather than value equality
- Pairing @Required with NEVER and expecting round-tripping to work