Skip to content

Formatters & Code

JSON to Scala Case Class

Scala case classes for Circe / Play JSON.

Runs in your browser
JSON · source
lines: 17chars: 261size: 261 B
Scala case class · result
lines: 16chars: 244size: 244 B
live

Understanding JSON → Scala

Case classes and typeclass derivation.

Why Scala JSON is library-dependent, the three serious choices, and the implicit dance that derives encoders and decoders at compile time.

Case classes are the data model.

Scala case classes have been the answer to "what's a Java POJO?" since Scala 1.0. Immutable by default, with auto-generated equals / hashCode / toString / copy / pattern-matching support. A JSON DTO is just a case class: case class User(userId: Int, name: String, avatar: Option[String]). The serializer's job is to turn case classes into JSON and back.

Three serious library choices.

Circe, Play JSON, uPickle. Circe is the most popular in functional-leaning codebases — typeclass-based, compile-time derivation, integrates with cats. Play JSON ships with the Play framework and is widely used in non-functional projects — slightly more imperative API. uPickle is Li Haoyi's "minimal effort" alternative — fastest to adopt, smaller surface, slightly less rich. Pick one per project; mixing them inside a single codebase is rarely worth the cost.

Compile-time derivation.

The Scala way of getting an encoder/decoder is implicit derivation: write import io.circe.generic.auto._ and the compiler synthesizes them on demand for any case class in scope. No runtime reflection (unlike Jackson); no codegen step (unlike Dart). The compiler reads the case class's fields and emits the encoder logic at compile time. Slower compiles, faster runtime.

A worked example with Circe.

From { "user_id": 7, "name": "Q", "avatar": null }: import io.circe.generic.semiauto._ import io.circe.{Decoder, Encoder} case class User(userId: Int, name: String, avatar: Option[String]) object User { implicit val decoder: Decoder[User] = deriveDecoder implicit val encoder: Encoder[User] = deriveEncoder } The semiauto derivation is explicit and recommended over fully-auto for control over when the derivation fires (auto can be slow on large codebases). The implicits live in the companion object so they're discovered automatically wherever the case class is used.

Snake_case bridging

deriveConfiguredDecoder + Configuration.default.withSnakeCaseMemberNames

Configure once; transformer applies to every member.

deriveConfiguredDecoder[User]

= No per-field overrides needed

Option for nullable, sealed traits for unions.

Like Kotlin and Swift, Scala uses Option[T] for fields that can be missing or null. The decoder maps absent JSON keys to None, present non- null values to Some(value). For tagged unions, sealed traits give you the same shape as Kotlin's sealed classes: a closed set of subtypes, exhaustive pattern matching, type-driven discrimination. Circe's generic-extras module adds explicit discriminator support.

Scala 3 changes.

Scala 3 (2021) replaced the implicit-derivation machinery. The Mirror typeclass and derives keyword make derivation a first-class language feature: case class User(...) derives Encoder.AsObject, Decoder. Cleaner syntax; same compile-time semantics. Libraries are converging on Scala 3 support; most examples on the web still use Scala 2.13 patterns.

Effects libraries.

Scala's effect ecosystem (cats-effect, ZIO) wraps everything in monadic types. A server using Circe through http4s ends up with IO[Either[DecodingFailure, User]] rather than a plain User. The serialization step is the same; what changes is the calling convention. Codegen tools emit plain case classes and decoders; the effect wrapping happens at the call site, not the type definition.

Frequently asked questions

Quick answers.

Which Scala versions are supported?

The output follows standard Scala 2.13 and Scala 3 syntax for case classes. It uses standard collection types like `List` or `Seq` and optional fields where appropriate.

Does it support Circe or Play JSON?

Yes. It generates the base case class structure which can then be used with `deriveDecoder` in Circe or `Json.format` in Play JSON. Key names are preserved to match the source JSON.

How does it handle nested objects?

The converter recursively identifies nested structures and generates separate, linked case classes for every object found within the payload.

Is my data sent to any external API?

No. The parsing and code generation logic runs entirely within your browser environment. Your JSON structure never leaves your device.

People also search for

Related tools

More in this room.

See all in Formatters & Code