the-bson 

Documentation on how to use this library.

API Docs can be found here.

Installation 

using SBT: just add

libraryDependencies += "io.github.raptros" %% "the-bson" % "0.1"`

How to use 

The entire functionality of the-bson can be imported in one go:

import io.github.raptros.bson._
import Bson._

if you find this too cumbersome, functionality and implicits can also be imported in various objects, e.g.

Field encoding 

instances of the trait EncodeBsonField are used to write key-value pairs to DBObjects. for instance, one of the provided definition in EncodeBsonFields is:

implicit val intEncodeField: EncodeBsonField[Int] = directWritable[Int]

you could use this like so:

val dbo = new BasicDBObject() //from the mongo-java-driver
implicitly[EncodeBsonField[Int]].writeTo(dbo, "someKey", 35)

athough, as the next section describes, there are easier ways.

Building DBObjects 

this library provides several methods for building DBObject from key-value pairs in the Builders trait. these methods rely on having EncodeBsonField instances available for the value types.

key-value pair construction 

the tools to build DBObjects rely on DBOKV to write the key-value pairs into the objects. the implicit class StringToDBOKV provides methods for obtaining DBOKVs

val dbo = new BasicDBObject() //from the mongo-java-driver
val kv = "keyname" :> 25 //produces DBOKV[Int]("keyname", 25)
kv.write(dbo) //writes 25 into db at the key "keyname"

note that :> only produces a DBOKV if an implicit EncodeBsonField can be found for the type of the argument.


there are two different ways to write optional values.

DBO and DBOBuilder 

there are two ways to use these DBOKVs:

    val dbo = DBO(
      "key1" :> 33,
      "key2" :> "another value",
      "booleanKey" :> true)

Encoding 

you can encode entire objects by defining instances of EncodeBson

//a junk trait
trait Junk {
  val x: Int
  val y: String
}
//define an encoder using the apply method in the EncodeBson object.
implicit def junkEncodeBson: EncodeBson[Junk] = EncodeBson { j =>
  DBO("x" :> j.x, "y" :> j.y)
}
val junk1 = new Junk {
  val x = 133
  val y = "some stuff"
}
val dbo: DBObject = junkEncodeBson(junk1)
//alternatively
val dbo: DBObject = junkEncodeBson.encode(junk1)

alternatively, using an implicit class in Builders that has so far gone unmentioned - ValueToBson, you could replace the last line there with

val dbo: DBObject = junk1.asBson 

(asBson looks for an implicit encoder instance for the wrapped type, and uses its encode method)


if you have an EncodeBson trait for some type, it can also be used whenever an EncodeBsonField is needed.

val dbo: DBObject = DBO("isJunk" :> true, "junk1" :> junk1)

you could replace the definition of junkEncodeBson using one of the bencode methods defined via EncodeBsons.

implicit def junkEncodeBson: EncodeBson[Junk] =
  bencode2f((j: Junk) => (j.x, j.y))("x", "y")

Decoding DBObjects 

all decode operations in the-bson produce DecodeResult, defined as

// \/ and NonEmptyList come from scalaz
type DecodeResult[+A] = \/[NonEmptyList[DecodeError], A]

if any decode operation fails, all the errors involved will be reported.


DecodeError has fixed set of subtypes:

Decoding Fields 

instances of the trait DecodeBsonField are used to extract values from DBObject. various instances are already provided in DecodeBsonFields

import io.github.raptros.bson._
import scalaz.\/-
import Bson._ 

val dbo = DBO("int-key" :> 35)

val v = implicitly[DecodeBsonField[Int]].decode("int-key", dbo)
//alternatively
val v = implicitly[DecodeBsonField[Int]]("int-key", dbo)

v === \/-(35)
Extractor syntax 

The implicit class DBOWrapper is provided in Extractors; among others methods, it provides field.

val dbo = DBO("doubleKey" :> 33.2)

dbo.field[Double]("doubleKey")
v === \/-(33.2)

it also provides fieldOpt:

val dbo = DBO("k1": true)

val v0 = dbo.fieldOpt[Boolean]("k1")
v0 === \/-(Some(true))

val v1 = dbo.fieldOpt[Boolean]("k2")
v1 === \/-(None)

val v2 = dbo.fieldOpt[Double]("k1)
v2 === -\/(NonEmptyList(WrongFieldType("k1",
  classOf[java.lang.Double],
  classOf[java.lang.Boolean])))
Deriving new instances 

hopefully, you will not need to do this too often (for reasons explained in the next section), but there are several ways to derive new instances.

Decoding objects 

instances of DecodeBson allow you to decode entire DBObjects. this example demonstrates several decoding utilities:

def bdecode4f[A, B, C, D, X](
  fxn: (A, B, C, D) => X)(ak: String, bk: String, ck: String,
  dk: String)(implicit decodea: DecodeBsonField[A], 
  decodeb: DecodeBsonField[B], decodec: DecodeBsonField[C],
  decoded: DecodeBsonField[D]): DecodeBson[X] = DecodeBson { dbo =>
  ApV.apply4(
    decodea(ak, dbo).validation,
    decodeb(bk, dbo).validation,
    decodec(ck, dbo).validation,
    decoded(dk, dbo).validation
  )(fxn).disjunction
}

of course, there are other ways to define new implementations of DecodeBson, which you can find in the Scaladocs.

Extractor syntax 

DBOWrapper defines a method decode, which looks up a DecodeBson[A].

val dbo = DBO("str" :> "some string", "anInt" :> 23, "b" :> false, "d" :> 55.3)
val v = dbo.decode[FourThings]
v === \/-(FourThings("some string", 23, false, 55.3))
decoders and fields 

any DecodeBson instance can also be used as a DecodeBsonField

val dbo = DBO("4things" :> DBO("str" :> "some string",
  "anInt" :> 23, "b" :> false, "d" :> 55.3))
val v = dbo.field[FourThings]("4things")
v === \/-(FourThings("some string", 23, false, 55.3))

Case Classes, Codecs, etc. 

a CodecBson instance is both an encoder and a decoder - it implements both EncodeBson and DecodeBson. the main use for them is to define encoding and decoding of a case class simultaneously.

case class ThreePart(x: String, y: List[Int], z: DateTime)
implicit val codecThreePart =
  bsonCaseCodec3(ThreePart.apply, ThreePart.unapply)("x", "y", "z")
val example = ThreePart("hi", List(4, 3), z = DateTime.now())
val dbo = example.asBson
dbo.keySet === Set("x", "y", "z")
dbo.decode[ThreePart] === \/-(example)

Macros 

BsonMacros defines macros for deriving EncodeBsons, DecodeBsons and CodecBsons for case classes.

case class Simple(name: String, something: Boolean, amount: Int)
implicit val simpleCodec: CodecBson[Simple] =
  BsonMacros.deriveCaseCodecBson[Simple]
val t1 = Simple("test 2", true, 35)
val dbo = t1.asBson
dbo.decode[Simple] === \/-(t1)