What is JSON to Scala Case Class Generator?
Scala models JSON data with case classes — immutable value types that come with a generated constructor, equals, hashCode, copy, and pattern-matching support. Modeling an API response by hand is tedious because you have to pick the right Scala numeric type, wrap nullable fields in Option[T], and write a separate companion-object codec for whichever JSON library you use. This tool generates the case classes plus the serialization boilerplate for either Play JSON or Circe so you can paste the result straight into a .scala file. The two ecosystems differ in philosophy. Play JSON (built into the Play Framework) uses Reads/Writes/Format type classes and the Json.format[T] macro to derive a codec in one line. Circe — the de-facto choice for functional Scala (cats, http4s, tapir) — uses Encoder/Decoder type classes and the deriveCodec / semiauto / auto modules to generate instances at compile time. Both are compile-time, type-safe, and reflection-free, which is what makes Scala JSON handling fast and refactor-proof. For algebraic data types (sealed trait hierarchies) the two libraries also diverge: Circe encodes the subtype as a discriminator field, while Play JSON needs a hand-written Format or a library like play-json-derived-codecs. This generator focuses on the common product-type case (one object → one case class).
How to Use
- Set the root class name to a domain concept (e.g. "OrderResponse"), set the package name, and choose Plain, Play JSON, or Circe
- Enable Option types if the API can omit fields or return null — every nullable field becomes Option[T] with a default of None
- Paste a real API response, not a hand-written sample — Int vs Long and nullability are inferred from the actual values
- For Play projects, pick Play JSON to get an implicit Format via Json.format[T] in each companion object
- For http4s / tapir / cats-based services, pick Circe to get deriveCodec instances
- Click Generate and copy the case classes (children first) plus their codecs into your project
Why Use This Tool?
Tips & Best Practices
- Circe derivation needs the io.circe:circe-generic module and "import io.circe.generic.semiauto._"; Play JSON only needs play-json — do not mix the two in one file
- Json.format[T] requires every field to have a Reads instance; if you add a custom type later, write its Format first or the macro fails to expand at compile time
- For ADTs (e.g. a sealed trait Payment with subclasses Card and Cash), Circe adds a "type" discriminator by default — override it with Configuration if your JSON uses a different tag field
- Prefer Option[T] over null: a non-Option field that is missing in the JSON makes Circe return a DecodingFailure and Play JSON a JsError, both at runtime
- Large IDs that exceed Int range (2,147,483,647) must be Long or BigInt — using Int silently overflows; enable the right inference by feeding real data
- In Scala 3 you can replace deriveCodec with "derives Codec.AsObject" directly on the case class — cleaner than a companion object
Frequently Asked Questions
What is the complete JSON type to Scala type mapping?
JSON string → String. JSON integer within Int range → Int, larger → Long (or BigInt for very large). JSON float → Double (or BigDecimal for exact money). JSON boolean → Boolean. JSON null → Option[T] = None when Option types are enabled, where T is inferred from non-null values. JSON array → List[T] with T from the first element. JSON object → a dedicated case class. Mixed-type arrays → List[Json] (Circe) or List[JsValue] (Play JSON).
Circe vs Play JSON — which should I choose?
Choose Play JSON if you are already on the Play Framework: it ships with the framework, integrates with controllers and request body parsers, and Json.format[T] is a one-liner. Choose Circe for everything else, especially functional stacks (http4s, tapir, fs2, ZIO with zio-json-interop): it has richer combinators, better ADT support, and is the community standard. Both are compile-time and fast; Circe has a larger optics/cursor API for partial decoding.
How does Circe auto-derive a codec, and what is the difference between semiauto and auto?
semiauto (deriveCodec/deriveEncoder/deriveDecoder) generates the instance explicitly where you ask, usually in the companion object — predictable and fast to compile. auto (import io.circe.generic.auto._) derives instances implicitly on demand anywhere they are needed — convenient but can slow compilation and cause surprising re-derivations. Most production code uses semiauto for control.
How do I model a sealed trait ADT (one of several JSON shapes)?
Define a sealed trait and case classes for each variant. With Circe, deriveCodec on the trait emits a JSON object tagged with a "type" discriminator; customize the field via io.circe.generic.extras.Configuration. Play JSON has no built-in ADT support — write a Format that reads a discriminator key and dispatches, or add the play-json-derived-codecs library. This generator emits product types; convert to an ADT manually when several endpoints return different shapes under one field.
Why wrap nullable fields in Option instead of using null?
Scala discourages null; using it reintroduces NullPointerExceptions that the type system cannot catch. Option[T] makes absence explicit and forces callers to handle the None case via map/getOrElse/pattern matching. With a codec, Option also makes a missing key decode to None instead of failing, whereas a plain T fails decoding when the key is absent.
Is my data sent to a server?
No. All generation runs in your browser; your JSON never leaves your device.
Real-world Examples
Decoding a payments API response with Circe
A http4s service consumes a Stripe-style charge response. Circe deriveCodec gives a Decoder so the JSON body decodes directly into the case class; Option handles the optional refund object.
{
"id": "ch_3Nx921",
"amount": 4999,
"currency": "usd",
"paid": true,
"captured": true,
"description": "Pro plan annual",
"refunded": false,
"metadata": {
"orderId": "ord_8842",
"customerTier": "gold"
}
}package com.example.payments
import io.circe.Codec
import io.circe.generic.semiauto.deriveCodec
final case class Metadata(
orderId: String,
customerTier: String
)
object Metadata {
implicit val codec: Codec[Metadata] = deriveCodec
}
final case class Charge(
id: String,
amount: Long,
currency: String,
paid: Boolean,
captured: Boolean,
description: String,
refunded: Boolean,
metadata: Metadata
)
object Charge {
implicit val codec: Codec[Charge] = deriveCodec
}Play Framework controller body with optional fields
A Play JSON controller parses an incoming profile-update request where bio and avatarUrl may be absent. Option types make the missing keys decode to None instead of returning a JsError.
{
"userId": "u_5521",
"displayName": "Mara",
"bio": null,
"avatarUrl": null,
"followers": 1280,
"joinedYear": 2021
}package controllers.models
import play.api.libs.json.{Json, OFormat}
final case class ProfileUpdate(
userId: String,
displayName: String,
bio: Option[String] = None,
avatarUrl: Option[String] = None,
followers: Int,
joinedYear: Int
)
object ProfileUpdate {
implicit val format: OFormat[ProfileUpdate] =
Json.format[ProfileUpdate]
}