How to represent a GeoJSON point in a ReactiveMongo model?

mongodb, playframework, playframework-2.2, reactivemongo, scala

Solution

The problem you are facing is related to a mixup between `JSONCollection`and `BSONCollection`.

`BSONCollection` is the default collection reactivemongo uses. This implementation needs an implementation of a `BSONDocumentWriter`and a `BSONReader`for a case class to get (de-)serialised.

`JSONCollection`on the other hand is the default collection implementation that the play-reactive module uses. Since you defined the collection to be a `JSONCollection` in `db.collection[JSONCollection]("test")` you need to provide an implicit json format.

The json format you provide is

implicit val pointFormat = Json.format[Point]

This will serialize an object to the following format

{
    "lon": -0.0015,
    "lat": 51.0015 
}

If you want to serialise your `Point` to an array you need to replace the above implicit `pointFormat`:

import play.api.libs.json._
import play.api.libs.json.Reads._

case class Point(lng: Double, lat: Double)

object Point {

  val pointWrites = Writes[Point]( p => Json.toJson(List(p.lng, p.lat)))

  val pointReads = minLength[List[Double]](2).map(l => Point(l(0), l(1)))

  implicit val pointFormat = Format(pointReads, pointWrites)
}

You actualy don't need the `BSONReader`and `BSONDocumentWriter`.

Edit: Here is a reads that also validates the `type` attribute of the document:

val pointReads =
  (__ \ 'type).read[String](constraints.verifying[String](_ == "Point")) andKeep
    (__ \ 'coordinates).read[Point](minLength[List[Double]](2).map(l => Point(l(0), l(1))))

Problem

In order to do geospatial queries in MongoDB a document with a location (with a `2d` or `2dsphere` geospatial index) should look something like this: ``` { _id: …, loc: { type: "Point", coordinates: [ <longitude>, <latitude> ] } } ``` I'm very new to Scala, ReactiveMongo and Play Framework, but in my opinion an obvious way to use such a location is through a case class like: ``` case class Point(lon: Double, lat: Double) ``` And the JSON representation that a website's API deals with should look something like: ``` { _id: … loc: [ <longitude>, <latitude> ] } ``` Now, I can't figure out how to tell my ReactiveMongo model to serialize/deserialize between these formats. My controller looks like this: ``` package controllers import play.api._ import play.api.mvc._ import play.api.libs.json._ import scala.concurrent.Future // Reactive Mongo imports import reactivemongo.api._ import scala.concurrent.ExecutionContext.Implicits.global // Reactive Mongo plugin import play.modules.reactivemongo.MongoController import play.modules.reactivemongo.json.collection.JSONCollection object Application extends Controller with MongoController { def collection: JSONCollection = db.collection[JSONCollection]("test") import play.api.data.Form import models._ import models.JsonFormats._ def createCC = Action.async { val user = User("John", "Smith", Point(-0.0015, 51.0015)) val futureResult = collection.insert(user) futureResult.map(_ => Ok("Done!")) } } ``` I tried to use a PointWriter and PointReader. This is my models.scala: ``` package models import reactivemongo.bson._ import play.modules.reactivemongo.json.BSONFormats._ case class User( // _id: Option[BSONObjectID], firstName: String, lastName: String, loc: Point) case class Point(lon: Double, lat: Double) object Point { implicit object PointWriter extends BSONDocumentWriter[Point] { def write(point: Point): BSONDocument = BSONDocument( "type" -> "Point", "coordinates" -> Seq(point.lat, point.lon) ) } implicit object PointReader extends BSONReader[BSONDocument, Point] { def read(doc: BSONDocument): Point = Point(88, 88) } } object JsonFormats { import play.api.libs.json.Json import play.api.data._ import play.api.data.Forms._ import play.api.libs.json._ import play.api.libs.functional.syntax._ implicit val pointFormat = Json.format[Point] } ``` When I call the controller action `createCC` I would expect have a properly formatted Point object the newly created document, but what I actually get is something like: ``` { "_id": ObjectId("52ac76dd1454bbf6d96ad1f1"), "loc": { "lon": -0.0015, "lat": 51.0015 } } ``` So my attempt to use `PointWriter` and `PointReader` to tell ReactiveMongo how to write such a `Point` object to the database has no effect at all. Can anybody help me understand what I have to do? (I come from a PHP background and try to get my head round Scala...) Update: Thanks to tmbo's answer I came up with this writer: ``` val pointWrites = Writes[Point]( p => Json.obj( "type" -> JsString("Point"), "coordinates" -> Json.arr(JsNumber(p.lon), JsNumber(p.lat)) ) ) ```

Original source