Skip to main content

Kotlin JSON Processing

Introduction​

JSON (JavaScript Object Notation) is a lightweight data interchange format that's easy for humans to read and write and easy for machines to parse and generate. When developing modern applications, processing JSON data is a common requirement, whether you're working with web APIs, configuration files, or data storage.

In this guide, you'll learn how to work with JSON in Kotlin, covering several approaches from built-in libraries to popular third-party options. We'll explore serialization (converting Kotlin objects to JSON) and deserialization (parsing JSON into Kotlin objects) with practical examples.

JSON Libraries in Kotlin​

Kotlin doesn't include a JSON processing library in its standard library, but there are several excellent options available:

  1. kotlinx.serialization - Official Kotlin serialization library
  2. Gson - Popular Java library that works well with Kotlin
  3. Jackson - Feature-rich Java library with good Kotlin support
  4. Moshi - Modern JSON library for Kotlin and Java by Square

Let's explore each option with examples.

Using kotlinx.serialization​

kotlinx.serialization is the official Kotlin serialization framework. It's designed specifically for Kotlin and uses compile-time code generation.

Setting up kotlinx.serialization​

To use kotlinx.serialization, add these dependencies to your build file:

kotlin
// build.gradle.kts
plugins {
kotlin("jvm") version "1.8.0"
kotlin("plugin.serialization") version "1.8.0"
}

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}

Basic Serialization and Deserialization​

Here's a simple example showing how to serialize a Kotlin data class to JSON and deserialize it back:

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

@Serializable
data class Person(val name: String, val age: Int, val email: String)

fun main() {
val json = Json { prettyPrint = true }

// Create a Person object
val person = Person("Alice", 29, "alice@example.com")

// Serialize to JSON string
val jsonString = json.encodeToString(person)
println("Serialized JSON:")
println(jsonString)

// Deserialize from JSON string
val deserializedPerson = json.decodeFromString<Person>(jsonString)
println("\nDeserialized object:")
println("Name: ${deserializedPerson.name}")
println("Age: ${deserializedPerson.age}")
println("Email: ${deserializedPerson.email}")
}

Output:

Serialized JSON:
{
"name": "Alice",
"age": 29,
"email": "alice@example.com"
}

Deserialized object:
Name: Alice
Age: 29
Email: alice@example.com

Working with Complex Objects​

Let's work with a more complex nested structure:

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

@Serializable
data class Address(val street: String, val city: String, val zipCode: String)

@Serializable
data class Employee(
val id: Int,
val name: String,
val address: Address,
val roles: List<String>,
val isActive: Boolean = true
)

fun main() {
val json = Json {
prettyPrint = true
ignoreUnknownKeys = true // Ignore JSON fields that don't match class properties
}

val employee = Employee(
id = 101,
name = "John Doe",
address = Address("123 Main St", "Springfield", "12345"),
roles = listOf("Developer", "Team Lead")
)

val jsonString = json.encodeToString(employee)
println("Employee JSON:")
println(jsonString)

// Parse JSON string
val jsonEmployee = """
{
"id": 102,
"name": "Jane Smith",
"address": {
"street": "456 Oak Ave",
"city": "Rivertown",
"zipCode": "67890"
},
"roles": ["Designer", "UX Specialist"],
"isActive": false,
"extraField": "This will be ignored"
}
""".trimIndent()

val parsedEmployee = json.decodeFromString<Employee>(jsonEmployee)
println("\nParsed employee:")
println("ID: ${parsedEmployee.id}")
println("Name: ${parsedEmployee.name}")
println("City: ${parsedEmployee.address.city}")
println("Roles: ${parsedEmployee.roles.joinToString(", ")}")
println("Active: ${parsedEmployee.isActive}")
}

Working with Dynamic JSON Content​

Sometimes you might need to work with JSON where the structure isn't known at compile time:

kotlin
import kotlinx.serialization.json.*

fun main() {
val jsonString = """
{
"name": "Product X",
"price": 29.99,
"inStock": true,
"tags": ["electronics", "gadget"],
"dimensions": {
"length": 10,
"width": 5,
"height": 3
}
}
""".trimIndent()

// Parse to JsonElement
val jsonElement = Json.parseToJsonElement(jsonString)

// Access as JsonObject
val jsonObject = jsonElement.jsonObject

// Access properties
val name = jsonObject["name"]?.jsonPrimitive?.content ?: "Unknown"
val price = jsonObject["price"]?.jsonPrimitive?.doubleOrNull ?: 0.0
val inStock = jsonObject["inStock"]?.jsonPrimitive?.booleanOrNull ?: false

// Access array
val tags = jsonObject["tags"]?.jsonArray?.mapNotNull {
it.jsonPrimitive.contentOrNull
} ?: emptyList()

// Access nested object
val dimensions = jsonObject["dimensions"]?.jsonObject
val length = dimensions?.get("length")?.jsonPrimitive?.intOrNull ?: 0

println("Product Info:")
println("Name: $name")
println("Price: $price")
println("In Stock: $inStock")
println("Tags: $tags")
println("Length: $length")
}

Using Gson​

Gson is a mature Java library for JSON processing that works well with Kotlin.

Setting up Gson​

Add Gson dependency to your project:

kotlin
// build.gradle.kts
dependencies {
implementation("com.google.code.gson:gson:2.10.1")
}

Basic Serialization and Deserialization with Gson​

kotlin
import com.google.gson.Gson
import com.google.gson.GsonBuilder

data class User(val name: String, val age: Int, val email: String)

fun main() {
val gson = GsonBuilder().setPrettyPrinting().create()

// Create a User object
val user = User("Bob", 32, "bob@example.com")

// Serialize to JSON
val jsonString = gson.toJson(user)
println("JSON:")
println(jsonString)

// Deserialize from JSON
val deserializedUser = gson.fromJson(jsonString, User::class.java)
println("\nDeserialized user:")
println("Name: ${deserializedUser.name}")
println("Age: ${deserializedUser.age}")
}

Output:

JSON:
{
"name": "Bob",
"age": 32,
"email": "bob@example.com"
}

Deserialized user:
Name: Bob
Age: 32

Real-World Application: Working with REST APIs​

One of the most common use cases for JSON processing is working with RESTful APIs. Let's see an example using kotlinx.serialization and the Kotlin HTTP client ktor-client:

Setting up dependencies​

kotlin
// build.gradle.kts
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation("io.ktor:ktor-client-core:2.2.4")
implementation("io.ktor:ktor-client-cio:2.2.4")
implementation("io.ktor:ktor-client-content-negotiation:2.2.4")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.2.4")
}

Example: Fetching and Processing Weather Data​

kotlin
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class WeatherResponse(
val location: Location,
val current: CurrentWeather
)

@Serializable
data class Location(
val name: String,
val region: String,
val country: String
)

@Serializable
data class CurrentWeather(
val temp_c: Float,
val temp_f: Float,
val condition: Condition
)

@Serializable
data class Condition(
val text: String,
val icon: String
)

fun main() = runBlocking {
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
}

try {
// Note: You'd need to sign up for a real API key
val response = client.get("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London")

// For demonstration (with a mock response)
val mockJson = """
{
"location": {
"name": "London",
"region": "City of London, Greater London",
"country": "United Kingdom"
},
"current": {
"temp_c": 15.0,
"temp_f": 59.0,
"condition": {
"text": "Partly cloudy",
"icon": "//cdn.weatherapi.com/weather/64x64/day/116.png"
}
}
}
"""

val weatherData = Json.decodeFromString<WeatherResponse>(mockJson)

println("Weather in ${weatherData.location.name}, ${weatherData.location.country}")
println("Temperature: ${weatherData.current.temp_c}°C / ${weatherData.current.temp_f}°F")
println("Conditions: ${weatherData.current.condition.text}")

} catch (e: Exception) {
println("Error fetching weather data: ${e.message}")
} finally {
client.close()
}
}

Handling Common JSON Challenges​

Optional and Default Values​

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

@Serializable
data class Product(
val id: String,
val name: String,
val price: Double,
val description: String? = null, // Optional field
val category: String = "Uncategorized", // Default value
val tags: List<String> = emptyList() // Default empty list
)

fun main() {
val json = Json { prettyPrint = true }

// Minimal JSON with only required fields
val minimalJson = """{"id": "prod123", "name": "Basic Widget", "price": 19.99}"""
val basicProduct = json.decodeFromString<Product>(minimalJson)

println("Basic product:")
println(basicProduct)
println("Description: ${basicProduct.description ?: "No description provided"}")
println("Category: ${basicProduct.category}")

// Complete JSON with all fields
val completeProduct = Product(
id = "prod456",
name = "Deluxe Widget",
price = 49.99,
description = "Our premium widget with extra features",
category = "Premium",
tags = listOf("featured", "sale", "new")
)

val completeJson = json.encodeToString(completeProduct)
println("\nComplete product JSON:")
println(completeJson)
}

Custom Naming Strategies​

Sometimes JSON field names don't match Kotlin property naming conventions. You can use annotations to handle this:

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

@Serializable
data class ServerResponse(
@SerialName("user_id") val userId: String,
@SerialName("full_name") val fullName: String,
@SerialName("is_active") val isActive: Boolean,
@SerialName("last_login_ts") val lastLoginTimestamp: Long
)

fun main() {
val json = Json { prettyPrint = true }

val jsonString = """
{
"user_id": "U12345",
"full_name": "Jane Smith",
"is_active": true,
"last_login_ts": 1645729944000
}
""".trimIndent()

val response = json.decodeFromString<ServerResponse>(jsonString)
println("User ID: ${response.userId}")
println("Name: ${response.fullName}")
println("Active: ${response.isActive}")
}

Summary​

In this guide, we've covered various approaches to JSON processing in Kotlin:

  1. kotlinx.serialization - The official Kotlin serialization library that offers compile-time safety and excellent Kotlin integration.
  2. Gson - A flexible, easy-to-use Java library that works well with Kotlin.

We've learned how to:

  • Serialize Kotlin objects to JSON
  • Deserialize JSON into Kotlin objects
  • Work with complex, nested JSON structures
  • Handle dynamic JSON content
  • Deal with optional and default values
  • Use custom naming strategies
  • Integrate JSON processing with web APIs

JSON processing is a fundamental skill for modern application development, especially for web and mobile applications that need to communicate with APIs or store configuration data.

Additional Resources​

Exercises​

  1. Create a program that reads a JSON file containing an array of products and computes the total inventory value.
  2. Build a simple command-line JSON validator tool that checks if a given string is valid JSON.
  3. Create a data model for a blog post (with title, content, author, comments) and implement functions to serialize and deserialize between Kotlin and JSON.
  4. Try parsing JSON from a public API (like weather, movies, or books) and display the information in a formatted way.
  5. Implement a JSON configuration manager that can read, modify, and save application settings in JSON format.


If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)