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 anEncoder[T]
deriveDecoder[T]
, which derives aDecoder[T]
deriveCodec[T]
, which derives aCodec[T]
, i.e. anEncoder[T]
as well as aDecoder[T]
These methods work for all T
that are are either case class
es, sealed trait
s or sealed abstract class
es.
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 trait
s or sealed abstract class
es and all their sub-types:
deriveAllEncoders[T]
, which derives anEncoder[T]
deriveAllDecoders[T]
, which derives aDecoder[T]
deriveAllCodecs[T]
, which derives aCodec[T]
, i.e. anEncoder[T]
as well as aDecoder[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