Document Object Model (DOM)

While borer’s core design is DOM-less, writing directly to and reading directly from the respective stream of CBOR/JSON data items, it is sometimes convenient to nevertheless have access to an object structure that mirrors the structure of a CBOR/JSON document as closely as possible.
(Many JSON-libraries solely rely on such an intermediate “AST” structure for their encoding and decoding operations.)

For such cases borer provides you with a simple “DOM” ADT (see the respective source file here), which you can use like this:

CBOR
sourceimport io.bullet.borer.Cbor
import io.bullet.borer.Dom.*

val dom = MapElem.Sized(
  "foo" -> ArrayElem.Sized(IntElem(42), StringElem("rocks")),
  "bar" -> DoubleElem(26.8)
)

val encoded = Cbor.encode(dom).toByteArray

encoded ==> hex"A263666F6F82182A65726F636B7363626172FB403ACCCCCCCCCCCD"

val decoded = Cbor.decode(encoded).to[Element].value

decoded ==> dom
JSON
sourceimport io.bullet.borer.Dom.*
import io.bullet.borer.Json

val dom = MapElem.Unsized(
  "foo" -> ArrayElem.Unsized(IntElem(42), StringElem("rocks")),
  "bar" -> DoubleElem(26.8)
)

val encoded = Json.encode(dom).toByteArray

new String(encoded, "UTF8") ==> """{"foo":[42,"rocks"],"bar":26.8}"""

val decoded = Json.decode(encoded).to[Element].value

decoded ==> dom

Any CBOR or JSON document can be deserialized into a io.bullet.borer.Dom.Element, which can then be queried or transformed in any way, and potentially written back to CBOR or JSON. Make sure to also look at borer’s built-in support for efficient transcoding if that is something you are interested in.

As such, the borer DOM can also be used to transform JSON to CBOR, e.g. like this:

sourceimport io.bullet.borer.{Cbor, Json}
import io.bullet.borer.Dom.*

import scala.util.Try

def jsonToCbor(json: String): Try[Array[Byte]] =
  Json
    .decode(json getBytes "UTF8")
    .to[Element]
    .valueTry
    .flatMap(dom => Cbor.encode(dom).toByteArrayTry)

jsonToCbor("""{"foo":[42,"rocks"],"bar":26.8}""").get ==>
hex"BF63666F6F9F182A65726F636B73FF63626172FB403ACCCCCCCCCCCDFF"

DOM Transformation

Having a DOM representation of a particular data structure (e.g. nested case class instances representing application state) opens up a variety of opportunities for runtime inspection and/or transformation of that data structure, which are often not that easy to achieve otherwise.

One solution to such tasks can be “optics” (like Lenses) for which dedicated libraries like Monocle exist. They are an excellent tool if the point of interest/change is deep and relatively small compared to the total size of the overage data structure.

However, if many changes need to be done or the data structure must be walked in its completion (for example in order to gather some statistics) transcoding into a DOM and working on that alternate representation could be easier.

borer provides one relatively simple tool for transforming an existing DOM structure:
the DOM.Transformer trait.

See the chapter on transcoding for an example of how to use it.