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:
- kotlinx.serialization - Official Kotlin serialization library
- Gson - Popular Java library that works well with Kotlin
- Jackson - Feature-rich Java library with good Kotlin support
- 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:
// 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:
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, "[email protected]")
// 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": "[email protected]"
}
Deserialized object:
Name: Alice
Age: 29
Email: [email protected]
Working with Complex Objects
Let's work with a more complex nested structure:
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:
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:
// build.gradle.kts
dependencies {
implementation("com.google.code.gson:gson:2.10.1")
}
Basic Serialization and Deserialization with Gson
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, "[email protected]")
// 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": "[email protected]"
}
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
// 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
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
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:
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:
- kotlinx.serialization - The official Kotlin serialization library that offers compile-time safety and excellent Kotlin integration.
- 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
- Create a program that reads a JSON file containing an array of products and computes the total inventory value.
- Build a simple command-line JSON validator tool that checks if a given string is valid JSON.
- Create a data model for a blog post (with title, content, author, comments) and implement functions to serialize and deserialize between Kotlin and JSON.
- Try parsing JSON from a public API (like weather, movies, or books) and display the information in a formatted way.
- 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 [email protected]. I’d greatly appreciate your feedback! :)