Last active
March 8, 2016 22:29
-
-
Save nevang/4690568 to your computer and use it in GitHub Desktop.
Reader and writer in order to work with spray-json and reactivemongo. Based on https://github.com/zenexity/Play-ReactiveMongo.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import spray.json._ | |
import reactivemongo.bson._ | |
import reactivemongo.bson.handlers.{ BSONReader, BSONWriter, RawBSONWriter } | |
import scala.util.{ Try, Success, Failure } | |
import org.apache.commons.codec.binary.Hex | |
import org.joda.time.format.ISODateTimeFormat | |
import org.joda.time.{ DateTime, DateTimeZone } | |
import java.nio.ByteBuffer | |
import org.jboss.netty.buffer.ChannelBuffers | |
/** Readers and Writers for Reactivemongo BSON and Spray Json. | |
* | |
* Credits: | |
* It is based on the work Stephane Godbillon and Pascal Voitot on play reactivemongo plugin | |
* @see https://github.com/zenexity/Play-ReactiveMongo | |
*/ | |
trait JsHandling { | |
//** Helper which allow to get json values from reactivemongo. */ | |
implicit object JsObjectReader extends BSONReader[JsObject] { | |
def fromBSON(doc: BSONDocument): JsObject = JsBSONReader.readObject(doc.toTraversable) | |
} | |
/** Helper which allow to send json values to reactivemongo. */ | |
object JsObjectWriter extends BSONWriter[JsObject] { | |
def toBSON(doc: JsObject) = JsBSONWriter.writeObject(doc) | |
} | |
/** Helper which allow to send json values to reactivemongo. */ | |
implicit object JsValueWriter extends RawBSONWriter[JsValue] { | |
def write(doc: JsValue) = { | |
doc match { | |
case o: JsObject => JsBSONWriter.writeObject(o).makeBuffer | |
case a: JsArray => JsBSONWriter.writeArray(a).makeBuffer | |
case _ => throw new RuntimeException("JsValue can only be JsObject/JsArray") | |
} | |
} | |
} | |
} | |
/** Readers | |
* | |
* @see http://docs.mongodb.org/manual/reference/mongodb-extended-json/ | |
*/ | |
object JsBSONReader { | |
def readObject(doc: TraversableBSONDocument) = JsObject(doc.iterator.toSeq.map(readElement): _*) | |
def readElement(e: BSONElement): (String, JsValue) = e.name -> (e.value match { | |
case BSONString(value) => JsString(value) | |
case BSONInteger(value) => JsNumber(value) | |
case BSONLong(value) => JsNumber(value) | |
case BSONDouble(value) => JsNumber(value) | |
case BSONBoolean(true) => JsTrue | |
case BSONBoolean(false) => JsFalse | |
case BSONNull => JsNull | |
case doc: TraversableBSONDocument => readObject(doc) | |
case doc: AppendableBSONDocument => readObject(doc.toTraversable) | |
case arr: TraversableBSONArray => readArray(arr) | |
case arr: AppendableBSONArray => readArray(arr.toTraversable) | |
case oid @ BSONObjectID(value) => JsObject("$oid" -> JsString(oid.stringify)) | |
case BSONDateTime(value) => JsString(isoFormatter.print(value)) // Doesn't follow mongdb-extended | |
case bb: BSONBinary => readBSONBinary(bb) | |
case BSONRegex(value, flags) => JsObject("$regex" -> JsString(value), "$options" -> JsString(flags)) | |
case BSONTimestamp(value) => JsObject("$timestamp" -> JsObject( | |
"t" -> JsNumber(value.toInt), "i" -> JsNumber((value >>> 32).toInt))) | |
case BSONUndefined => JsObject("$undefined" -> JsTrue) | |
// case BSONMinKey => JsObject("$minKey" -> JsNumber(1)) // Bug on reactivemongo | |
case BSONMaxKey => JsObject("$maxKey" -> JsNumber(1)) | |
// case BSONDBPointer(value, id) => JsObject("$ref" -> JsString(value), "$id" -> JsString(Hex.encodeHexString(id))) // Not implemented | |
// NOT STANDARD AT ALL WITH JSON and MONGO | |
case BSONJavaScript(value) => JsObject("$js" -> JsString(value)) | |
case BSONSymbol(value) => JsObject("$sym" -> JsString(value)) | |
case BSONJavaScriptWS(value) => JsObject("$jsws" -> JsString(value)) | |
}) | |
def readArray(array: TraversableBSONArray) = JsArray(array.iterator.toSeq.map(readElement(_)._2): _*) | |
def readBSONBinary(bb: BSONBinary) = { | |
val arr = new Array[Byte](bb.value.readableBytes()) | |
bb.value.readBytes(arr) | |
val sub = ByteBuffer.allocate(4).putInt(bb.subtype.value).array | |
JsObject("$binary" -> JsString(Hex.encodeHexString(arr)), | |
"$type" -> JsString(Hex.encodeHexString(sub))) | |
} | |
val isoFormatter = ISODateTimeFormat.dateTime.withZone(DateTimeZone.UTC) | |
} | |
/** Writers | |
* | |
* @see http://docs.mongodb.org/manual/reference/mongodb-extended-json/ | |
*/ | |
object JsBSONWriter { | |
def writeObject(obj: JsObject): BSONDocument = BSONDocument(obj.fields.map(writePair).toSeq: _*) | |
def writeArray(arr: JsArray): BSONArray = | |
BSONArray(arr.elements.zipWithIndex.map(p => writePair(p._2.toString, p._1)).map(_._2): _*) | |
def writePair(p: (String, JsValue)): (String, BSONValue) = (p._1, p._2 match { | |
case JsString(str @ IsoDateTime(y, m, d, h, mi, s, ms)) => manageDate(y, m, d, h, mi, s, ms) match { | |
case Success(dt) => dt | |
case Failure(_) => BSONString(str) | |
} | |
case JsString(str) => BSONString(str) | |
case JsNumber(value) => BSONDouble(value.doubleValue) | |
case obj: JsObject => manageSpecials(obj) | |
case arr: JsArray => writeArray(arr) | |
case JsTrue => BSONBoolean(true) | |
case JsFalse => BSONBoolean(false) | |
case JsNull => BSONNull | |
}) | |
def manageDate(year: String, month: String, day: String, hour: String, minute: String, second: String, milli: String) = | |
Try(BSONDateTime((new DateTime(year.toInt, month.toInt, day.toInt, hour.toInt, | |
minute.toInt, second.toInt, milli.toInt, DateTimeZone.UTC)).getMillis)) | |
def manageSpecials(obj: JsObject): BSONValue = | |
if (obj.fields.size > 2) writeObject(obj) | |
else (obj.fields.toList match { | |
case ("$oid", JsString(str)) :: Nil => Try(BSONObjectID(Hex.decodeHex(str.toArray))) | |
case ("$undefined", JsTrue) :: Nil => Success(BSONUndefined) | |
// case ("$minKey", JsNumber(n)) :: Nil if n == 1 => Success(BSONMinKey) // Bug on reactivemongo | |
case ("$maxKey", JsNumber(n)) :: Nil if n == 1 => Success(BSONMaxKey) | |
case ("$js", JsString(str)) :: Nil => Success(BSONJavaScript(str)) | |
case ("$sym", JsString(str)) :: Nil => Success(BSONSymbol(str)) | |
case ("$jsws", JsString(str)) :: Nil => Success(BSONJavaScriptWS(str)) | |
case ("$timestamp", ts: JsObject) :: Nil => manageTimestamp(ts) | |
case ("$regex", JsString(r)) :: ("$options", JsString(o)) :: Nil => | |
Success(BSONRegex(r, o)) | |
case ("$binary", JsString(d)) :: ("$type", JsString(t)) :: Nil => | |
Try(BSONBinary(ChannelBuffers.wrappedBuffer(Hex.decodeHex(d.toArray)), | |
findSubtype(Hex.decodeHex(t.toArray)))) | |
// case ("$ref", JsString(v)) :: ("$id", JsString(i)) :: Nil => // Not implemented | |
// Try(BSONDBPointer(v, Hex.decodeHex(i.toArray))) | |
case _ => Success(writeObject(obj)) | |
}) match { | |
case Success(v) => v | |
case Failure(_) => writeObject(obj) | |
} | |
def manageTimestamp(o: JsObject) = o.fields.toList match { | |
case ("t", JsNumber(t)) :: ("i", JsNumber(i)) :: Nil => | |
Success(BSONTimestamp((t.toLong & 4294967295L) | (i.toLong << 32))) | |
case _ => Failure(new IllegalArgumentException("Illegal timestamp value")) | |
} | |
def findSubtype(bytes: Array[Byte]) = | |
ByteBuffer.wrap(bytes).getInt match { | |
case 0x00 => Subtype.GenericBinarySubtype | |
case 0x01 => Subtype.FunctionSubtype | |
case 0x02 => Subtype.OldBinarySubtype | |
case 0x03 => Subtype.UuidSubtype | |
case 0x05 => Subtype.Md5Subtype | |
// case 0X80 => Subtype.UserDefinedSubtype // Bug on reactivemongo | |
case _ => throw new IllegalArgumentException("unsupported binary subtype") | |
} | |
val IsoDateTime = """^(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$""".r | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I made some changes to make it compile with the latest reactive mongo library: https://gist.github.com/rleibman/7103325d0193be268ed7