skip to content

@SerialName, @Transient & @Required

@SerialName renames a field in the wire format, @Transient excludes one, and @Required and the encode-defaults settings control what may be omitted. These four cover most of the mismatch between a Kotlin class and an external schema.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

How do you make a kotlinx.serialization property serialize under a different JSON key than its Kotlin name, and what is the role of @SerialName?

level: juniorimportance: must knowfreq 75%

answer

  1. Overrides the serial NAME in the descriptor, not the Kotlin name
  2. camelCase in code, snake_case on the wire
  3. On a class = polymorphic discriminator value
  4. Explicit @SerialName beats JsonNamingStrategy
  5. Format-agnostic; doesn't touch JVM reflection

basics

~10 s

Put @SerialName("json_key") on the property. The Kotlin field keeps its name in code, but the JSON uses the name you give. Useful when JSON uses snake_case but Kotlin uses camelCase.

solid answer

~40 s

@SerialName("...") overrides the name used in the serialized form for a property (or the type discriminator for a class). The Kotlin source name stays untouched, so you write idiomatic camelCase in code while matching an external snake_case API: @SerialName("first_name") val firstName: String. It works for any format (JSON, ProtoBuf, CBOR), because it changes the descriptor's element name at codegen time, not just JSON output. On a class it changes the polymorphic discriminator value. Without it, the property name in the descriptor equals the Kotlin name. It does not change Kotlin reflection or the field name on the JVM. For bulk camelCase->snake_case you can instead set JsonNamingStrategy.SnakeCase on the Json config, but @SerialName is explicit and per-property and wins for one-off renames.

code

kotlin · 7 lines
kotlin
@Serializable
data class Account(
    @SerialName("account_id") val accountId: Long,
    @SerialName("is_active") val active: Boolean,
)

// {"account_id":7,"is_active":true}

go deeper

for a junior

Knows to add @SerialName("json_key") to bridge camelCase and snake_case.

for a middle

Explains it edits the descriptor's serial name, is format-agnostic, and is symmetric for decode.

for a senior

Contrasts with JsonNamingStrategy, notes class-level discriminator use, and the duplicate-name rule.

for a principal

Reasons about API contract stability, when to centralize via naming strategy vs explicit per-field annotations across a large model and multiple formats.

## What @SerialName does kotlinx.serialization generates a **serial descriptor** for every `@Serializable` class. The descriptor lists each property's **serial name** — the string key used in the encoded form (the JSON object key, the ProtoBuf field, etc.). By default the serial name equals the Kotlin property name. `@SerialName("value")` overrides that serial name **without** changing the Kotlin identifier. You keep writing idiomatic Kotlin (camelCase) while matching an external contract (snake_case, kebab-case, reserved words). ```kotlin import kotlinx.serialization.* import kotlinx.serialization.json.Json @Serializable data class User( @SerialName("first_name") val firstName: String, @SerialName("id") val userId: Long, ) Json.encodeToString(User("Ada", 1)) // {"first_name":"Ada","id":1} ``` Decoding is symmetric: the parser looks for `first_name` in the input, not `firstName`. ## Key facts - **Format-agnostic.** It edits the descriptor, so it affects JSON, CBOR, ProtoBuf, etc. — not just JSON text. - **On a class**, `@SerialName` changes the **polymorphic type discriminator** value used in sealed-hierarchy serialization (e.g. `{"type":"my.alias"}`), not a property key. - **It does not touch JVM reflection** — `User::firstName.name` is still `firstName`. Only the serialization layer sees the alias. - **Compile-time / plugin-driven.** The serialization compiler plugin bakes the name into the generated serializer; there is no runtime cost. ## @SerialName vs JsonNamingStrategy For a whole-model convention (every property camelCase -> snake_case) prefer a naming strategy: ```kotlin val json = Json { namingStrategy = JsonNamingStrategy.SnakeCase } ``` This applies automatically to all properties **that do not** carry an explicit `@SerialName` (an explicit `@SerialName` always wins). `JsonNamingStrategy` is **JSON-only** and decoding becomes order/strategy sensitive, so for a handful of fields the explicit per-property `@SerialName` is clearer. ## Gotchas - Two properties must not resolve to the **same** serial name — the descriptor would have a duplicate key and the plugin/runtime rejects it. - `@SerialName` is allowed on enum entries too, to rename the encoded enum constant.

  • If you set JsonNamingStrategy.SnakeCase globally and also annotate one field with @SerialName, which wins?
    The explicit @SerialName wins for that property; the strategy only fills in the rest.
  • Does @SerialName change the result of User::firstName.name via Kotlin reflection?
    No. Reflection still reports the Kotlin name firstName; @SerialName only affects the serialization descriptor.

Like a stage name: the actor's legal name (Kotlin property) stays the same, but the marquee (JSON key) shows the alias.

saying these in an interview costs you the question

  • Claiming @SerialName renames the Kotlin field on the JVM
  • Thinking it only affects JSON and not other formats
  • Believing both encoding and decoding don't use the alias (decoding does)
  • Assigning the same @SerialName to two properties
  • Confusing it with @Serializable

context

open as a page

What does @Transient do in kotlinx.serialization, and why must such a property have a default value?

level: middleimportance: must knowfreq 65%

basics

~20 s

@Transient tells the serializer to skip a property entirely: it is not written to JSON and not read from it. Because it is never decoded, it must have a default value so an object can still be constructed.

open as a page

By default kotlinx.serialization treats a property with a default value as optional during decoding. How does @Required change that, and when would you use it?

level: middleimportance: should knowfreq 50%

basics

~20 s

Properties with a default value can be left out of the JSON and the default fills in. @Required forces that property to be present in the input even though it has a default — decoding fails if it's missing.

open as a page

You receive a partner API: keys are snake_case, an 'apiVersion' must always be present in every payload you send, an internal 'requestTrace' must never leave your process, and a 'currency' field has a sensible default but must be present in inbound requests. Which kotlinx.serialization annotations/settings do you apply to each?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Rename keys with @SerialName (or a snake_case naming strategy); force apiVersion out with @EncodeDefault(ALWAYS); hide requestTrace with @Transient; make currency mandatory on input with @Required while keeping its default.

open as a page

Explain @EncodeDefault and the encodeDefaults Json setting. How do you ensure (or prevent) default-valued properties appearing in the output?

level: seniorimportance: should knowfreq 45%

basics

~10 s

By 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.

open as a page