Skip to content

JSON CLI Tool

Build a command-line tool that reads a JSON array from stdin and prints a human-readable summary. It’s your “Hello World” for Kotlin — but actually useful, and it exercises a real project from scratch: dependencies, serialization, stdin, data classes, and a few collection idioms.

  • Creating a Kotlin Gradle project from scratch
  • Adding a dependency (kotlinx-serialization-json)
  • Reading stdin, writing stdout
  • Core Kotlin: val/var, string templates, data classes, when/groupBy
  1. Read a JSON array of objects from stdin.
  2. Parse the JSON into Kotlin data classes.
  3. Print a formatted summary to stdout.

Pipe this to stdin:

[
{"name": "Alice", "language": "Kotlin", "years": 3},
{"name": "Bob", "language": "TypeScript", "years": 5},
{"name": "Charlie", "language": "Go", "years": 2},
{"name": "Diana", "language": "Kotlin", "years": 1},
{"name": "Eve", "language": "TypeScript", "years": 4}
]
=== Developer Summary ===
Total developers: 5
By language:
Go: 1 developers (avg 2.0 years)
Kotlin: 2 developers (avg 2.0 years)
TypeScript: 2 developers (avg 4.5 years)
Most experienced: Bob (TypeScript, 5 years)
Least experienced: Diana (Kotlin, 1 years)

A single-module Gradle project — three files do the whole job:

  • Directoryjson-cli/
    • build.gradle.kts deps + build config
    • settings.gradle.kts project name
    • Directorysrc/main/kotlin/com/example/jsoncli/
      • Main.kt the entire program

Two plugins matter here: kotlin("jvm") to compile, and kotlin("plugin.serialization") to turn @Serializable data classes into JSON parsers at compile time (no reflection). The one runtime dependency is the JSON library itself.

build.gradle.kts
plugins {
kotlin("jvm") version "2.1.0"
kotlin("plugin.serialization") version "2.1.0"
application
}
group = "com.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
application {
mainClass.set("com.example.jsoncli.MainKt")
}
settings.gradle.kts
rootProject.name = "json-cli"

The whole program. A few things to notice if you’re coming from TS/Go:

  • @Serializable on the data class is all the JSON wiring you need — Json.decodeFromString<List<Developer>>(...) parses straight into typed objects (like JSON.parse in TS, but type-checked, or json.Unmarshal in Go without the struct tags).
  • generateSequence(::readLine) lazily pulls every line from stdin, then joinToString glues them back — that’s the idiomatic “read all of stdin”.
  • groupBy { it.language } returns a Map<String, List<Developer>>; the rest is ordinary collection math.
  • String templates ($language, ${devs.size}) read like JS template literals.
src/main/kotlin/com/example/jsoncli/Main.kt
package com.example.jsoncli
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class Developer(
val name: String,
val language: String,
val years: Int
)
fun main() {
// Read all of stdin into a single string
val input = generateSequence(::readLine).joinToString("\n")
if (input.isBlank()) {
System.err.println("Error: No input provided. Pipe JSON to stdin.")
return
}
// Parse JSON into a list of Developer objects
val json = Json { ignoreUnknownKeys = true }
val developers = try {
json.decodeFromString<List<Developer>>(input)
} catch (e: Exception) {
System.err.println("Error parsing JSON: ${e.message}")
return
}
if (developers.isEmpty()) {
println("No developers found in input.")
return
}
println("=== Developer Summary ===")
println("Total developers: ${developers.size}")
println()
// Group by language and print stats
println("By language:")
developers
.groupBy { it.language }
.toSortedMap()
.forEach { (language, devs) ->
val avgYears = devs.map { it.years }.average()
println(" $language: ${devs.size} developers (avg ${"%.1f".format(avgYears)} years)")
}
println()
// Find extremes
val mostExperienced = developers.maxByOrNull { it.years }!!
val leastExperienced = developers.minByOrNull { it.years }!!
println("Most experienced: ${mostExperienced.name} (${mostExperienced.language}, ${mostExperienced.years} years)")
println("Least experienced: ${leastExperienced.name} (${leastExperienced.language}, ${leastExperienced.years} years)")
}
  1. Build the project:

    Terminal window
    ./gradlew build
  2. Run with sample data piped to stdin:

    Terminal window
    echo '[{"name":"Alice","language":"Kotlin","years":3},{"name":"Bob","language":"TypeScript","years":5}]' | ./gradlew run --quiet
  3. Or pipe a file:

    Terminal window
    cat sample.json | ./gradlew run --quiet