skip to content

How do you turn a Kotlin object into a JSON string and back using kotlinx.serialization, and what is the role of the Json object?

level: juniorimportance: must knowfreq 80%

answer

  1. @Serializable on the class
  2. encodeToString / decodeFromString
  3. Json = default StringFormat instance
  4. reified <T> finds the serializer
  5. Json { } builds a reusable custom instance

basics

~10 s

Mark the class with @Serializable, then call Json.encodeToString(obj) to get JSON text and Json.decodeFromString(text) to read it back. Json is the format instance that does the conversion.

solid answer

~30 s

Annotate the data class with @Serializable so the compiler plugin generates its serializer. Use the kotlinx.serialization.json.Json instance: Json.encodeToString(value) produces a JSON String, and Json.decodeFromString<T>(string) parses one back. The reified inline overloads pick up the serializer automatically via serializer<T>(); you can also pass an explicit KSerializer. Json (capital J, the companion of class Json) is a default StringFormat-implementing instance. For custom behavior you build your own with the Json { } builder (e.g. Json { prettyPrint = true }) and reuse that instance. encodeToString/decodeFromString are extension functions on StringFormat; there are also encodeToJsonElement/decodeFromJsonElement for the in-memory tree.

code

kotlin · 11 lines
kotlin
import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class User(val id: Int, val name: String)

fun main() {
    val text = Json.encodeToString(User(1, "Ada")) // {"id":1,"name":"Ada"}
    val back = Json.decodeFromString<User>(text)
    println(back) // User(id=1, name=Ada)
}

go deeper

for a junior

Knows to annotate with @Serializable and call encodeToString/decodeFromString on Json.

for a middle

Explains the compiler plugin (no reflection), reified serializer lookup, and reusing a configured instance.

for a senior

Discusses StringFormat vs BinaryFormat, JsonElement entry points, and the cost of building Json instances.

for a principal

Frames serializer resolution (compile-time vs contextual), instance reuse as a performance/consistency concern, and multiplatform implications.

## What kotlinx.serialization is kotlinx.serialization is Kotlin's official, reflection-free serialization library. A **compiler plugin** generates serialization code at compile time for any class you annotate with `@Serializable`, so it works on Kotlin/JVM, Native, and JS without runtime reflection. ## The two pieces - **`@Serializable`** — placed on a class. The plugin generates a companion `serializer()` that knows each property's name and type. - **A format instance** — `Json` is the JSON format. The simplest is the built-in default instance, available as `Json` (the companion object of class `Json`). ## Encoding and decoding ```kotlin import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable data class User(val id: Int, val name: String) val json = Json.encodeToString(User(1, "Ada")) // {"id":1,"name":"Ada"} val user = Json.decodeFromString<User>(json) // User(id=1, name=Ada) ``` - `encodeToString(value)` serializes to a JSON `String`. - `decodeFromString<T>(string)` parses a JSON `String` into `T`. These are `inline` `reified` extension functions: `<T>` lets the compiler call `serializer<T>()` for you. You may also pass an explicit `KSerializer`, e.g. `Json.encodeToString(User.serializer(), value)`. ## Custom configured instances The default `Json` uses strict defaults. To change behavior, build your own and **reuse it**: ```kotlin val pretty = Json { prettyPrint = true } pretty.encodeToString(User(1, "Ada")) ``` Building a `Json {}` instance is relatively expensive — create it once (e.g. a `val`/bean) rather than per call. ## Related entry points - `encodeToJsonElement` / `decodeFromJsonElement` work with the in-memory `JsonElement` tree instead of strings. - `Json` implements `StringFormat`; the binary formats (`ProtoBuf`, `Cbor`) implement `BinaryFormat`.

  • Why reuse a single Json instance instead of creating Json { } on every call?
    Constructing a Json instance compiles its configuration into a JsonConfiguration and is comparatively costly; reusing one avoids that overhead and keeps configuration consistent.
  • What happens if a class isn't annotated @Serializable?
    You get a compile-time error (no serializer found) for plugin-generated cases, or a SerializationException at runtime if resolved dynamically.

Json is like a translator: @Serializable gives each class a phrasebook, and encode/decode are 'say it in JSON' and 'understand this JSON'.

saying these in an interview costs you the question

  • Thinking kotlinx.serialization uses runtime reflection like Jackson/Gson
  • Creating a new Json { } instance on every encode/decode call
  • Forgetting the @Serializable annotation
  • Confusing Json (companion default) with the Json class you build

context