Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Kotlin JSON Serialization and Formatting Libraries

In modern software development, JSON (JavaScript Object Notation) is the de facto standard for data interchange, especially in web APIs and data storage. Kotlin, with its focus on conciseness and safety, offers excellent libraries to handle the process of converting Kotlin objects into JSON strings (serialization) and JSON strings back into Kotlin objects (deserialization). This process is often called marshalling or unmarshalling.

While you could manually parse JSON strings or build them piece by piece, using a robust library provides numerous benefits, including type safety, reduced boilerplate code, better performance, and handling of complex data structures, formatting, and edge cases.

Why Use JSON Libraries in Kotlin?

Libraries automate the tedious and error-prone tasks:

  • Type Safety: Directly map JSON fields to Kotlin data class properties, leveraging Kotlin's type system.
  • Less Boilerplate: Automatically generate serialization and deserialization logic, saving you from writing repetitive code.
  • Handling Complex Structures: Seamlessly manage nested objects, arrays, collections, and polymorphic types.
  • Performance: Optimized parsing and generation for efficiency.
  • Formatting: Easily control output format, like pretty-printing for readability.

Key Libraries for Kotlin

Several libraries exist for handling JSON in Kotlin. The most popular and recommended ones are:

  • kotlinx.serialization: The official, multiplatform serialization library developed by JetBrains. This is often the go-to choice for new Kotlin projects, especially those targeting multiple platforms (JVM, JS, Native, Android, iOS, etc.).
  • Moshi: Developed by Square, known for its Kotlin-friendly API and use of Kotlin's reflection capabilities (or code generation). A solid choice, particularly popular in the Android community before kotlinx.serialization matured.
  • GSON: Google's library. A very mature and widely used Java library that works well with Kotlin, though it might require more setup or boilerplate compared to Kotlin-native libraries.

For most new Kotlin development, especially if multiplatform is a consideration, kotlinx.serialization is the recommended library due to its tight integration with the language and its multiplatform nature. We will focus primarily on this library.

Deep Dive into kotlinx.serialization

kotlinx.serialization is a plugin-based library. You apply a compiler plugin to your project, and it automatically generates the serialization code for classes you annotate.

Adding Dependency

You need to add the serialization plugin and runtime library to your project's build file (e.g., build.gradle.kts or build.gradle).

build.gradle.kts (Kotlin DSL)

plugins {
    kotlin("jvm") // or multiplatform, android, etc.
    kotlin("plugin.serialization") version "1.9.22" // Use your Kotlin version
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") // Use the latest version
    // Add other dependencies here
}

build.gradle (Groovy DSL)

plugins {
    id 'org.jetbrains.kotlin.jvm' // or multiplatform, android, etc.
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22' // Use your Kotlin version
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0' // Use the latest version
    // Add other dependencies here
}

Basic Serialization & Deserialization

To make a class serializable, annotate it with @Serializable. Then, use the Json object to encode or decode instances.

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class User(val name: String, val age: Int, val isStudent: Boolean)

fun main() {
    // Serialization
    val user = User(name = "Alice", age = 30, isStudent = false)
    val jsonString = Json.encodeToString(user)
    println("Serialized JSON: $jsonString") // Output: {"name":"Alice","age":30,"isStudent":false}

    // Deserialization
    val receivedJson = """{"name":"Bob","age":25,"isStudent":true}"""
    val decodedUser = Json.decodeFromString<User>(receivedJson)
    println("Decoded User: $decodedUser") // Output: User(name=Bob, age=25, isStudent=true)
}

Customizing Serialization

kotlinx.serialization offers several annotations and configurations to customize how your data is serialized.

Changing Key Names (@SerialName)

If your JSON keys don't match your Kotlin property names, use @SerialName.

@Serializable
data class Product(
    @SerialName("product_id") val id: String,
    val name: String,
    val price: Double
)

fun main() {
    val product = Product(id = "sku123", name = "Example Widget", price = 19.99)
    val jsonString = Json.encodeToString(product)
    println("Serialized JSON: $jsonString") // Output: {"product_id":"sku123","name":"Example Widget","price":19.99}
}

Ignoring Properties (@Transient)

Use @Transient to exclude properties from serialization. Note that transient properties must have a default value.

@Serializable
data class SensitiveData(
    val secretKey: String,
    @Transient val derivedValue: String = "ignored" // Must have default value
)

fun main() {
    val data = SensitiveData(secretKey = "very secret", derivedValue = "calculated on the fly")
    val jsonString = Json.encodeToString(data)
    println("Serialized JSON: $jsonString") // Output: {"secretKey":"very secret"}
}

Default Values & Missing Fields

By default, missing fields in JSON will cause deserialization errors. You can use default values in your data class properties to make them optional.

@Serializable
data class Settings(
    val theme: String = "dark", // Default value
    val notificationsEnabled: Boolean // Required
)

fun main() {
    val jsonWithTheme = """{"theme":"light","notificationsEnabled":false}"""
    val settings1 = Json.decodeFromString<Settings>(jsonWithTheme)
    println("Settings 1: $settings1") // Output: Settings(theme=light, notificationsEnabled=false)

    val jsonWithoutTheme = """{"notificationsEnabled":true}"""
    val settings2 = Json.decodeFromString<Settings>(jsonWithoutTheme) // 'theme' will use default "dark"
    println("Settings 2: $settings2") // Output: Settings(theme=dark, notificationsEnabled=true)

    // val jsonMissingRequired = """{"theme":"light"}"""
    // Json.decodeFromString<Settings>(jsonMissingRequired) // Throws SerializationException: Field 'notificationsEnabled' is required...
}

Nullable Types

Kotlin's nullable types (?) map directly to JSON null. If a field is nullable, it can be missing or have a null value in the JSON.

@Serializable
data class Contact(
    val email: String,
    val phone: String? = null // Optional phone number
)

fun main() {
    val jsonWithPhone = """{"email":"a@example.com","phone":"123-4567"}"""
    val contact1 = Json.decodeFromString<Contact>(jsonWithPhone)
    println("Contact 1: $contact1") // Output: Contact(email=a@example.com, phone=123-4567)

    val jsonWithoutPhone = """{"email":"b@example.com"}"""
    val contact2 = Json.decodeFromString<Contact>(jsonWithoutPhone)
    println("Contact 2: $contact2") // Output: Contact(email=b@example.com, phone=null)

    val jsonWithNullPhone = """{"email":"c@example.com","phone":null}"""
    val contact3 = Json.decodeFromString<Contact>(jsonWithNullPhone)
    println("Contact 3: $contact3") // Output: Contact(email=c@example.com, phone=null)
}

JSON Formatting (Pretty Printing)

By default, Json.encodeToString produces a compact JSON string with no unnecessary whitespace. For human readability, you can configure the Json instance to pretty print.

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class ComplexData(
    val id: Int,
    val items: List<String>,
    val details: Map<String, Double>,
    val nested: User? = null
)

fun main() {
    @Serializable data class User(val name: String, val age: Int) // Define locally for example

    val data = ComplexData(
        id = 101,
        items = listOf("apple", "banana", "cherry"),
        details = mapOf("width" to 10.5, "height" to 20.0),
        nested = User("Charlie", 42)
    )

    // Compact JSON (default)
    val compactJson = Json.encodeToString(data)
    println("Compact JSON:\n$compactJson")

    // Pretty Printed JSON
    val prettyJson = Json { prettyPrint = true }
    val prettyJsonString = prettyJson.encodeToString(data)
    println("\nPretty JSON:\n$prettyJsonString")
/* Output will look something like:
Compact JSON:
{"id":101,"items":["apple","banana","cherry"],"details":{"width":10.5,"height":20.0},"nested":{"name":"Charlie","age":42}}

Pretty JSON:
{
    "id": 101,
    "items": [
        "apple",
        "banana",
        "cherry"
    ],
    "details": {
        "width": 10.5,
        "height": 20.0
    },
    "nested": {
        "name": "Charlie",
        "age": 42
    }
}
*/
}

You can configure the Json object further using the builder lambda, for example, to ignore unknown keys during deserialization, use different naming strategies, etc.

// Example of a configured Json instance
val lenientJson = Json {
    prettyPrint = true
    ignoreUnknownKeys = true // Don't fail if JSON has fields not in the data class
    isLenient = true // Allow non-standard JSON like comments or unquoted keys
    // Add other configurations as needed
}

@Serializable
data class PartialData(val name: String)

fun main() {
    val jsonWithExtraField = """{"name":"David", "extraField":"should be ignored"}"""
    val data = lenientJson.decodeFromString<PartialData>(jsonWithExtraField)
    println("Decoded with lenient parser: $data") // Output: PartialData(name=David)
}

Working with Lists and Maps

Serialization works seamlessly with standard Kotlin collections like List, Set, and Map, provided their contents are also serializable.

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class Item(val id: Int, val description: String)

fun main() {
    val itemsList = listOf(Item(1, "First"), Item(2, "Second"))
    val itemsMap = mapOf("item1" to Item(1, "First"), "item2" to Item(2, "Second"))

    // Serialize a list
    val listJson = Json.encodeToString(itemsList)
    println("List JSON: $listJson") // Output: [{"id":1,"description":"First"},{"id":2,"description":"Second"}]

    // Serialize a map
    val mapJson = Json.encodeToString(itemsMap)
    println("Map JSON: $mapJson") // Output: {"item1":{"id":1,"description":"First"},"item2":{"id":2,"description":"Second"}}

    // Deserialize a list
    val decodedList = Json.decodeFromString<List<Item>>(listJson)
    println("Decoded List: $decodedList") // Output: [Item(id=1, description=First), Item(id=2, description=Second)]

    // Deserialize a map
    val decodedMap = Json.decodeFromString<Map<String, Item>>(mapJson)
    println("Decoded Map: $decodedMap") // Output: {item1=Item(id=1, description=First), item2=Item(id=2, description=Second)}
}

Notice the use of explicit type parameters (<List<Item>>, <Map<String, Item>>) for deserializing collections.

Other Notable Libraries

Moshi

Moshi is a mature JSON library for Java and Kotlin. It has excellent Kotlin support, including handling nullability and default values without requiring reflection at runtime (using a codegen processor like KSP or KAPT). It's known for its adapter system which makes custom serialization logic relatively straightforward.

Choose Moshi if you prefer its API or adapter system, or if you're in an Android environment where it has historically been very popular.

GSON

GSON is a widely adopted library from Google, originally for Java. It works with Kotlin, but often requires more manual configuration or custom type adapters for optimal use with Kotlin's specific features like null safety.

Use GSON if you are integrating with an existing Java project that already uses it heavily, or if you require some of its specific advanced features or extensive ecosystem of adapters. For new Kotlin projects, kotlinx.serialization or Moshi are generally more idiomatic choices.

Conclusion

Handling JSON is a fundamental task in many Kotlin applications. While multiple libraries exist, the official kotlinx.serialization library is a powerful, modern, and multiplatform-ready solution that leverages Kotlin's language features effectively. By annotating your data classes and using the Json object, you can easily serialize Kotlin objects to JSON and deserialize JSON back into objects, handling complex structures and custom requirements with minimal boilerplate. Understanding how to configure the Json instance, especially for tasks like pretty-printing, is essential for debugging and human-readable output.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool