Transcoding

Apart from Encoding and Decoding borer also supports a less frequently used operation called “transcoding”, which combines encoding from a type A and immediately decoding to a type B into one single operation.

The point of transcoding isn’t to generate some byte stream serialization but rather to make use of the existing (and often carefully managed) codec logic to transform A into some other representation, which is easier to inspect or process further.

For example, one could transform values of A into borer’s DOM representation and walk the resulting object tree to collect all members called foo. Or apply some kind of tree transformation before transcoding the DOM instance back to A.

Transcoding vs. Encoding + Decoding

Since transcoding is essentially encoding from one type before immediately decoding to another the same result can be achieved by doing exactly that: two completely separate steps connected only by the intermediate, serialized representation of the data in a byte array for example.

The advantage of borer’s dedicated transcoding operation over separate Encoding + Decoding is the much better performance and decreased overhead. Transcoding connects the respective Encoder[A] and Decoder[B] stages as directly as possible. Instances of basic types like Int, Long, Double and, especially, String don’t need to be serialized and then parsed and recreated, but can be directly reused, which greatly helps with keeping time and memory requirements to a minimum.

Due to borer’s pull-based design transcoding still requires some intermediate buffering but this happens not on the level of serialized bytes but on a higher level and as such allows the transcoding infrastructure to itself run with practically no allocation overhead (when buffer caching isn’t explicitly disabled). Of course the Decoder[B] will usually still need to allocate new memory for the (sub-)structures of B instances.

Transcoding as AST-Substitute

Even though AST-less by design, support for efficient transcoding allows borer to be used for tasks that typically require an AST representation, like programmatic inspection/transformation of runtime object structures. Transcoding to something like borer’s DOM representation can never fail and, because the DOM implementation shipped with borer is not special in any way, you are free to change it or write your own from scratch without too much effort should the need arise.

CBOR vs. JSON Transcoding

Since transcoding skips the actual rendering to and parsing from raw bytes it is, in principle, agnostic to the question of whether CBOR or JSON is the encoding target. Nevertheless, the Encoder and Decoder implementations also have access to the information what the encoding target is and frequently use that information to adapt their logic accordingly. As such, you’ll have to decide on whether to target CBOR or JSON even for transcoding.

Example

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

case class Employee(nick: String, age: Int) derives Codec
case class Department(name: String, employees: List[Employee]) derives Codec

val value =
  Department(
    name = "IT",
    employees = List(
      Employee("Alice", 32),
      Employee("Bob", 28),
      Employee("Charlie", 42),
    )
  )

val dom = Cbor // could also be `Json` to target JSON
  .transEncode(value)
  .transDecode
  .to[Dom.Element]
  .value

val transformer =
  new Dom.Transformer {
    import Dom._

    override def transformMapMember(member: (Element, Element)) =
      member match {
        case (k @ StringElem("age"), IntElem(age)) => k -> IntElem(age + 1)
        case x                                     => super.transformMapMember(x)
      }
  }

val transformed = transformer(dom)

val result = Cbor // could also be `Json` to target JSON
  .transEncode(transformed)
  .transDecode
  .to[Department]
  .valueEither

result ==> Right(
  Department(
    name = "IT",
    employees = List(
      Employee("Alice", 33),
      Employee("Bob", 29),
      Employee("Charlie", 43),
    )
  )
)