Skip to main content
Version: 2.0.x


Automatic Derivation

Assume we want to encode this case class

case class Banana(curvature: Double)

To produce JSON from our data we define a JsonEncoder like this:

import zio.json._

object Banana {
implicit val encoder: JsonEncoder[Banana] =
// res0: String = "{\"curvature\":0.5}"


Say we extend our data model to include more data types

sealed trait Fruit

case class Banana(curvature: Double) extends Fruit
case class Apple (poison: Boolean) extends Fruit

we can generate the encoder for the entire sealed family

import zio.json._

object Fruit {
implicit val encoder: JsonEncoder[Fruit] =
val apple: Fruit = Apple(poison = false)
// apple: Fruit = Apple(poison = false)
// res2: String = "{\"Apple\":{\"poison\":false}}"

Almost all of the standard library data types are supported as fields on the case class, and it is easy to add support if one is missing.

Manual instances

Sometimes it is easier to reuse an existing JsonEncoder rather than generate a new one. This can be accomplished using convenience methods on the JsonEncoder typeclass to derive new decoders:

trait JsonEncoder[A] {
def contramap[B](f: B => A): JsonEncoder[B]


We can use contramap from an already existing encoder:

import zio.json._

case class FruitCount(value: Int)

object FruitCount {
implicit val encoder: JsonEncoder[FruitCount] =

// res3: String = "3"