Derivation Basics

In order to have borer automatically derive Encoders and/or Decoders for your custom types you need to first include the borer-derivation module as a dependency.
See the Getting Started chapter for details.

Once that is done you need to pick one of two available encoding strategies:

As the name implies the difference between these two encoding strategies lies in the type of the underlying structure that class instances are serialized to.

The Array-Based Codecs render class instances into arrays where each member is identified merely via its positional index. This is more efficient than Map-Based encoding, with regard to both runtime performance and storage requirements.

The Map-Based Codecs on the other hand render class members as named map entries, which has the the benefit that the wire format is more descriptive, not dependent on ordering and even allows for missing and default members.

Semi-Automatic Derivation

Once you have the respective import for Array-Based or Map-Based Codecs derivation in scope the following macros for semi-automatic derivation are available:

  • deriveEncoder[T], which derives an Encoder[T]
  • deriveDecoder[T], which derives a Decoder[T]
  • deriveCodec[T], which derives a Codec[T], i.e. an Encoder[T] as well as a Decoder[T]

These methods work for all T that are are either case classes, sealed traits or sealed abstract classes.

See the chapters on Array-Based Codecs or Map-Based Codecs for examples.

Fully-Automatic Derivation

In addition to semi-automatic derivation, which will only ever derive a typeclass instance for a single type, borer also provides the following macros for fully-automatic derivation of encoder/decoder typeclasses for whole ADT hierarchies, i.e. sealed traits or sealed abstract classes and all their sub-types:

  • deriveAllEncoders[T], which derives an Encoder[T]
  • deriveAllDecoders[T], which derives a Decoder[T]
  • deriveAllCodecs[T], which derives a Codec[T], i.e. an Encoder[T] as well as a Decoder[T]

To understand what these macros do consider this simple example:

sourcesealed trait Animal
case class Dog(age: Int, name: String)                      extends Animal
case class Cat(weight: Double, color: String, home: String) extends Animal
case class Fish(color: String)                              extends Animal
case object Yeti                                            extends Animal

With semi-automatic derivation you’d have to explicitly call a derivation macro for each sub-type individually:

sourceimport io.bullet.borer.Codec
import io.bullet.borer.derivation.MapBasedCodecs.*

given Codec[Animal] = {
  given Codec[Dog]       = deriveCodec[Dog]
  given Codec[Cat]       = deriveCodec[Cat]
  given Codec[Fish]      = deriveCodec[Fish]
  given Codec[Yeti.type] = deriveCodec[Yeti.type]
  deriveCodec[Animal]
}

This gives you full control but can be a bit tedious for larger ADT hierarchies.

Fully-automatic derivation on the other hand lets you reduce the boilerplate to a minimum by generating the calls to the derivation macro for each ADT sub-type automatically, so you only need to write a single line:

sourceimport io.bullet.borer.Codec
import io.bullet.borer.derivation.MapBasedCodecs.*

given Codec[Animal] = deriveAllCodecs[Animal]

Derivation via derives

borer also supports Scala 3’s new derives clause, which allows for a very succinct definition of Encoder, Decoder and Codec instances:

sourceimport io.bullet.borer.{Codec, Json}
import io.bullet.borer.derivation.MapBasedCodecs.*

case class Color(r: Int, g: Int, b: Int) derives Codec

val color = Color(255, 0, 0)

Json.encode(color).toUtf8String ==> """{"r":255,"g":0,"b":0}"""

The derives Codec clause automatically generates a respective given instance in the type companion object with a deriveCodec call to either MapBasedCodecs, CompactMapBasedCodecs or ArrayBasedCodecs, depending on what you have imported. (With derives Encoder, derives Decoder or derives Encoder, Decoder the same holds true analogously.)

The snippet above is therefore equivalent to this one:

sourceimport io.bullet.borer.{Codec, Json}
import io.bullet.borer.derivation.MapBasedCodecs.*

case class Color(r: Int, g: Int, b: Int)

object Color:
  given Codec[Color] = deriveCodec

val color = Color(255, 0, 0)

Json.encode(color).toUtf8String ==> """{"r":255,"g":0,"b":0}"""

derives for Enums and ADTs

The derives Encoder, derives Decoder and derives Codec clauses shown above only ever generate calls to deriveEncoder, deriveDecoder and deriveCodec, respectively.
Often times, however, we want to generate calls to deriveAllEncoders and friends instead so as to make use of Fully-Automatic Derivation.

This is possible via the following clauses:

  • derives Encoder.All
  • derives Decoder.All
  • derives Codec.All

Here is an example:

sourceimport io.bullet.borer.{Codec, Json}
import io.bullet.borer.derivation.MapBasedCodecs.*

enum Body derives Codec.All:
  case Sun, Earth, Moon
  case Asteroid(mass: Double)

val body: Body = Body.Asteroid(1.23)

// Json.encode(body).toByteArray or
Json.encode(body).toUtf8String ==> """{"Asteroid":{"mass":1.23}}"""

And this is what the derives Codec.All clause generates:

sourceimport io.bullet.borer.Codec
import io.bullet.borer.derivation.MapBasedCodecs.*

enum Body:
  case Sun, Earth, Moon
  case Asteroid(mass: Double)

object Body:
  given Codec[Body] = deriveAllCodecs