How to store date/time with ReactiveMongo + Play Json into mongodb

380 Views Asked by At

I'm trying to store java.time.Instant in mongo's native Date format.

Official ReactiveMongo documentation states that date time should be serialized as

JsObject with a $date JsNumber field with the timestamp (milliseconds) as value

enter image description here

(http://reactivemongo.org/releases/1.0/documentation/json/overview.html)

I follow this rule, but the value is not stored as mongo's Date, instead, it is stored as a regular JSON (BSON) document:

{
    "_id" : ObjectId("6057b962af0000af00e81ec7"),
    "username" : "john",
    "createdAt" : {
        "$date" : NumberLong(1616361826198)
    }
}

Scala source code that stores the document:

import play.api.libs.json.Json
import reactivemongo.api.DB
import reactivemongo.api.bson.BSONObjectID
import reactivemongo.api.bson.collection.BSONCollection
import java.time.Instant
import scala.concurrent.{ ExecutionContext, Future }
import reactivemongo.play.json.compat._
import json2bson._
import reactivemongo.api.commands.WriteResult

class Repo(database: DB)(implicit ec: ExecutionContext) {

  def collection: BSONCollection =
    database.collection("users")

  def insertDocument(): Future[WriteResult] = {
    val doc = Json.obj(
      "_id"       -> BSONObjectID.generate(),
      "username"  -> "john",
      "createdAt" -> Json.obj("$date" -> Instant.now().toEpochMilli)
    )

    collection.insert.one(doc)
  }
}

What's wrong here?

P.S.:

If I change extended BSON syntax

Json.obj("$date" -> Instant.now().toEpochMilli)

to BSONDateTime:

  ...
  "createdAt" -> BSONDateTime(Instant.now().toEpochMilli)
  ...

it works.

But still, why doesn't it work with play JSON + extended syntax?

1

There are 1 best solutions below

0
On

The MongoDB JSON extended representation is not a valid v2 one for date. The $date value must be either a formatted string or a long {"$numberLong": "<millis>"}

scala> BSONValue.pretty(implicitly[BSONValue](Json.obj("$date" -> Instant.now().toEpochMilli)))
res12: String =
{
  '$date': NumberLong(1616415630059)
}

scala> BSONValue.pretty(implicitly[BSONValue](Json.obj("$date" -> Instant.now().toString)))
res13: String = ISODate('2021-03-22T12:20:37.571Z')

scala> BSONValue.pretty(implicitly[BSONValue](Json.obj("$date" -> Json.obj("$numberLong" -> Instant.now().toEpochMilli.toString))))
res16: String = ISODate('2021-03-22T12:21:48.843Z')

That being said, using JSON to represent internal query (without value coming as JSON from elsewhere, e.g. a REST API), is useless, and costful.

When required, make sure to have all the required imports (like the extended._ one).