Table of Contents
Working with BSON Documents
Along with the other tools, HexArc also provides a DSL to work with BSON documents in a more declarative way and with less boilerplate code with respect to Mongo Java Driver. This DSL is called BsonDSL
.
At the moment, the DSL supports only a few of the existing BSON types, which should be enough for most common applications. If it wasn’t enough, the DSL can be easily extended by defining custom BsonEncoder
s and BsonDecoder
s, either for the required missing BSON types or your own custom types (e.g. POJOs…).
Example
This section will present a set of examples that show how to manipulate BSON documents using the BsonDSL
.
Creating BSON Documents
import io.github.jahrim.hexarc.persistence.bson.dsl.BsonDSL.{*, given}
import org.bson.*
// Creating documents without `BsonDSL`
val documentWithoutDSL: BsonDocument =
BsonDocument()
.append("booleanField", BsonBoolean(true))
.append("stringField", BsonString("value"))
.append("intField", BsonInt32(10))
.append("longField", BsonInt64(10_000_000_000_000L))
.append("doubleField", BsonDouble(0.33d))
.append("dateField", BsonDateTime(java.time.Instant.now.toEpochMilli))
.append("arrayField1", BsonArray(
java.util.List.of(BsonInt32(1), BsonInt32(2), BsonInt32(3))
))
.append("arrayField2", BsonArray(
java.util.List.of(BsonInt32(1), BsonInt32(2), BsonInt32(3))
))
.append("objectField1", BsonDocument()
.append("subfield", BsonInt32(0))
)
.append("objectField2", BsonDocument()
.append("subfield", BsonInt32(0))
)
// Creating documents with `BsonDSL`
val documentWithDSL: BsonDocument =
bson {
"booleanField" :: true // define a boolean
"stringField" :: "value" // define a string
"intField" :: 10 // define an integer
"longField" :: 10_000_000_000_000L // define a long
"doubleField" :: 0.33D // define a double
"dateField" :: java.time.Instant.now // define a date
"arrayField1" :: array(1,2,3) // define a homogeneous array
"arrayField2" :* (1,2,3) // shortcut syntax for defining a homogeneous array
"objectField1" :: bson { // define an object
"subfield" :: 0
}
"objectField2" :# { // shortcut syntax for defining an object
"subfield" :: 0
}
}
Full example here.
Accessing BSON Documents
val document: BsonDocument = documentWithDSL
// Accessing an element via `apply` retrieves an `Option[BsonValue]`.
val bsonBooleanOption: Option[BsonValue] = document("booleanField")
// `BsonValue`s can be decoded into objects via the method `as[T]`.
// Objects can be encoded back to `BsonValue`s via the method `asBson`.
val booleanOption: Option[Boolean] = bsonBooleanOption.map(_.as[Boolean])
// Accessing an element via `require` retrieves a `BsonValue` or throws
// a `NoSuchElementException`.
val boolean: Boolean = document.require("booleanField").as[Boolean]
// `Option[BsonValue]`s can also be accessed via `apply` for accessing nested
// elements.
val subfieldOption: Option[BsonValue] = document("objectField1")("subField")
// Field paths are also accepted to access nested elements.
val subfield: Int = document.require("objectField1.subField").as[Int]
Updating BSON Documents
val document: BsonDocument =
bson {
"field1" :: "value"
"field2" :: "value"
}
println(document)
// Output: { "field1": "value", "field2": "value" }
val updatedDocument: BsonDocument =
document.update {
"field2" :: "otherValue"
"field3" :: "otherValue"
}
println(updatedDocument)
// Output: { "field1": "value", "field2": "otherValue", "field3": "otherValue" }
Extending the BsonDSL
import io.github.jahrim.hexarc.persistence.bson.dsl.BsonDSL.{*, given}
import io.github.jahrim.hexarc.persistence.bson.codecs.{
BsonDocumentDecoder,
BsonDocumentEncoder,
BsonDecoder,
BsonDecoder
}
import org.bson.BsonDocument
// Define a custom class and its codec
case class CustomObject(subfield1: Int, subfield2: Long, subfield3: String)
/** Codec for `CustomObject`. */
object CustomObjectCodec:
/** A `BsonEncoder` converting `CustomObject`s to `BsonDocument`s. */
given BsonDocumentEncoder[CustomObject] = obj =>
bson {
"subfield1" :: obj.subfield1
"subfield2" :: obj.subfield2
"subfield3" :: obj.subfield3
}
/** A `BsonDecoder` converting `BsonDocument`s to `CustomObject`s. */
given BsonDocumentDecoder[CustomObject] = bson =>
CustomObject(
bson.require("subfield1").as[Int],
bson.require("subfield2").as[Long],
bson.require("subfield3").as[String]
)
// Use interchangeably
import CustomObjectCodec.given
val document: BsonDocument =
bson { "objectField" :: CustomObject(10, 10L, "10") }
val customObject: CustomObject =
document.require("objectField").as[CustomObject]
Note: defining
BsonDocumentEncoder
s andBsonDocumentDecoder
s is required to provide a custom codec forBsonDocument
s. In order to provide a custom codec for primitive types, it’s possible to use the more generalBsonEncoder
s andBsonDecoder
s, which handle conversions toBsonValue
s and fromBsonValue
s respectively.