PCSalt
YouTube GitHub
Back to Kotlin
Kotlin · 3 min read

JSON Parsing in Kotlin — Parsing with Jackson

Parse a complex JSON response using Jackson with the Kotlin module — the server-side powerhouse that's also the default in Spring Boot.


This is Part 5 of the JSON Parsing in Kotlin series. Jackson is the most widely used JSON library in the Java ecosystem, and with jackson-module-kotlin, it works seamlessly with Kotlin data classes. If you’re doing Spring Boot, you’re already using Jackson — you just might not know it.

Looking for the Java version? Read it here.

The JSON

Throughout this series, we’re parsing the same JSON — a typical SaaS API response with users, subscriptions, teams, and pagination. It has nested objects, arrays, nullable fields, enums, dates, and decimals.

{
  "status": "success",
  "data": {
    "users": [
      {
        "id": "usr_a1b2c3d4",
        "name": "Navkrishna",
        "email": "[email protected]",
        "role": "ADMIN",
        "verified": true,
        "createdAt": "2026-01-15T10:30:00Z",
        "profile": {
          "avatar": "https://cdn.example.com/avatars/nav.jpg",
          "bio": "Full-stack developer",
          "social": {
            "github": "krrishnaaaa",
            "twitter": null,
            "linkedin": "navkrishna"
          }
        },
        "subscription": {
          "plan": "PRO",
          "billingCycle": "YEARLY",
          "price": 199.99,
          "features": ["analytics", "api_access", "priority_support"],
          "trialEndsAt": null
        },
        "teams": [
          {
            "id": "team_x1y2",
            "name": "Backend",
            "role": "OWNER",
            "memberCount": 5
          },
          {
            "id": "team_z3w4",
            "name": "Mobile",
            "role": "MEMBER",
            "memberCount": 3
          }
        ],
        "lastLoginAt": "2026-03-14T18:45:00Z"
      },
      {
        "id": "usr_e5f6g7h8",
        "name": "Priya Sharma",
        "email": "[email protected]",
        "role": "MEMBER",
        "verified": false,
        "createdAt": "2026-03-01T14:00:00Z",
        "profile": {
          "avatar": null,
          "bio": null,
          "social": {
            "github": "priya-dev",
            "twitter": "priyacodes",
            "linkedin": null
          }
        },
        "subscription": {
          "plan": "TRIAL",
          "billingCycle": null,
          "price": 0.0,
          "features": ["analytics"],
          "trialEndsAt": "2026-03-31T23:59:59Z"
        },
        "teams": [],
        "lastLoginAt": null
      }
    ],
    "pagination": {
      "page": 1,
      "perPage": 20,
      "totalItems": 2,
      "totalPages": 1,
      "hasNext": false
    }
  },
  "meta": {
    "requestId": "req_9k8j7h6g",
    "timestamp": "2026-03-15T00:00:00Z",
    "apiVersion": "v2"
  }
}

Setup

Jackson needs a few modules to work well with Kotlin. Here’s the Maven dependency:

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
    <version>2.17.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.17.0</version>
</dependency>

Or Gradle:

implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'

The jackson-module-kotlin dependency pulls in jackson-core, jackson-databind, and jackson-annotations transitively, so you don’t need to add them separately. The jackson-datatype-jsr310 module handles java.time types like Instant.

Model Classes

Jackson with the Kotlin module can deserialize directly into data classes. Use @JsonProperty when the JSON field name doesn’t match your property name, and @JsonIgnoreProperties to skip unknown fields.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import java.time.Instant

@JsonIgnoreProperties(ignoreUnknown = true)
data class Social(
  val github: String?,
  val twitter: String?,
  val linkedin: String?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Profile(
  val avatar: String?,
  val bio: String?,
  val social: Social
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Subscription(
  val plan: String,
  val billingCycle: String?,
  val price: Double,
  val features: List<String>,
  val trialEndsAt: Instant?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Team(
  val id: String,
  val name: String,
  val role: String,
  val memberCount: Int
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class User(
  val id: String,
  val name: String,
  val email: String,
  val role: String,
  val verified: Boolean,
  val createdAt: Instant,
  val profile: Profile,
  val subscription: Subscription,
  val teams: List<Team>,
  val lastLoginAt: Instant?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Pagination(
  val page: Int,
  val perPage: Int,
  val totalItems: Int,
  val totalPages: Int,
  val hasNext: Boolean
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Meta(
  val requestId: String,
  val timestamp: Instant,
  val apiVersion: String
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Data(
  val users: List<User>,
  val pagination: Pagination
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class ApiResponse(
  val status: String,
  val data: Data,
  val meta: Meta
)

Notice that Instant is used directly — no string conversion needed. Jackson’s JavaTimeModule handles the ISO-8601 format out of the box.

Tip: You can skip the @JsonIgnoreProperties on every class by configuring the ObjectMapper globally with DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES set to false. We’ll do that next.

Configuring the ObjectMapper

The ObjectMapper is Jackson’s central class. For Kotlin, you need to register two modules:

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

val mapper: ObjectMapper = ObjectMapper()
  .registerKotlinModule()
  .registerModule(JavaTimeModule())
  .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
  • registerKotlinModule() — teaches Jackson about Kotlin data classes, default parameter values, and nullable types
  • registerModule(JavaTimeModule()) — handles java.time.Instant, LocalDate, ZonedDateTime, etc.
  • FAIL_ON_UNKNOWN_PROPERTIES = false — ignores extra JSON fields instead of throwing (same as @JsonIgnoreProperties but applied globally)

Parsing

With the ObjectMapper configured, parsing is a single line:

import com.fasterxml.jackson.module.kotlin.readValue

fun parse(jsonString: String): ApiResponse {
  return mapper.readValue<ApiResponse>(jsonString)
}

The readValue<ApiResponse>() is a Kotlin extension function from jackson-module-kotlin that uses reified generics. No TypeReference needed — just the type parameter.

Putting It Together

import java.io.File

fun main() {
  val jsonString = File("response.json").readText()

  val response = parse(jsonString)

  println("Status: ${response.status}")
  println("API Version: ${response.meta.apiVersion}")
  println("Total users: ${response.data.pagination.totalItems}")
  println()

  for (user in response.data.users) {
    println("--- ${user.name} ---")
    println("  Email: ${user.email}")
    println("  Role: ${user.role}")
    println("  Verified: ${user.verified}")
    println("  Plan: ${user.subscription.plan}")
    println("  Price: $${user.subscription.price}")
    println("  Features: ${user.subscription.features}")
    println("  GitHub: ${user.profile.social.github}")
    println("  Teams: ${user.teams.size}")
    for (team in user.teams) {
      println("    - ${team.name} (${team.role})")
    }
    println("  Last login: ${user.lastLoginAt}")
    println()
  }
}

Output

Status: success
API Version: v2
Total users: 2

--- Navkrishna ---
  Email: [email protected]
  Role: ADMIN
  Verified: true
  Plan: PRO
  Price: $199.99
  Features: [analytics, api_access, priority_support]
  GitHub: krrishnaaaa
  Teams: 2
    - Backend (OWNER)
    - Mobile (MEMBER)
  Last login: 2026-03-14T18:45:00Z

--- Priya Sharma ---
  Email: [email protected]
  Role: MEMBER
  Verified: false
  Plan: TRIAL
  Price: $0.0
  Features: [analytics]
  GitHub: priya-dev
  Teams: 0
  Last login: null

Using @JsonProperty for Field Mapping

Our JSON uses camelCase which matches Kotlin conventions, so we didn’t need @JsonProperty here. But when the JSON uses a different convention:

data class User(
  @JsonProperty("user_id") val id: String,
  @JsonProperty("full_name") val name: String,
  @JsonProperty("email_address") val email: String
)

Or configure a naming strategy globally:

import com.fasterxml.jackson.databind.PropertyNamingStrategies

val mapper = ObjectMapper()
  .registerKotlinModule()
  .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)

Using in Android

Jackson works on Android, but it’s the heaviest option. The jackson-databind jar alone is about 1.5 MB, and it uses reflection which can slow down cold starts. If you’re on Android, prefer Moshi or Kotlin Serialization instead.

That said, if you’re sharing model classes between an Android app and a JVM backend, Jackson can work:

// build.gradle (app module)
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'

You’ll also need Proguard rules to keep your data classes from being stripped:

-keep class com.yourpackage.model.** { *; }
-keep class com.fasterxml.jackson.** { *; }
-dontwarn com.fasterxml.jackson.databind.**

For Android, Kotlin Serialization and Moshi are significantly lighter and don’t need these workarounds.

Using in Spring Boot

This is where Jackson really shines. Spring Boot includes Jackson by default — you don’t need to add any dependency. The ObjectMapper is auto-configured with the Kotlin module (if jackson-module-kotlin is on the classpath, which it is in any Kotlin Spring Boot project).

// No setup needed — Spring Boot auto-configures everything
// Just use your data classes as request/response bodies

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class UserController(private val userService: UserService) {

  @GetMapping("/api/users")
  fun getUsers(): ApiResponse {
    return userService.getUsers()
  }
}

Spring Boot automatically serializes ApiResponse to JSON using Jackson. It also deserializes incoming JSON request bodies:

import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
class UserController {

  @PostMapping("/api/users")
  fun createUser(@RequestBody user: User): User {
    // Jackson deserializes the request body into a User
    return userService.save(user)
  }
}

If you need to customize the ObjectMapper, add a bean:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class JacksonConfig {

  @Bean
  fun objectMapper(): ObjectMapper {
    return ObjectMapper()
      .registerKotlinModule()
      .registerModule(JavaTimeModule())
  }
}

Jackson is the natural choice for Spring Boot. Zero configuration, full Kotlin support, excellent performance on the server side.

What’s Next

In the next post, we’ll wrap up the Kotlin series with a side-by-side comparison of all five libraries — org.json, Gson, Moshi, Kotlin Serialization, and Jackson. We’ll look at lines of code, null safety, Android support, Spring Boot support, and help you pick the right one for your project.