BSONDocument в JsObject и переопределить BSONDateTimeFormat

Я использую ReactiveMongo 0.11.11 для Play 2.5 и хочу преобразовать BSONDocument в JsObject.

Для большинства типов данных BSON (String, Int ...) значения по умолчанию вполне подходят, чтобы позволить библиотеке выполнять эту работу. Для типа BSON DateTime (BSONDateTime) значение свойства JSON не дает мне нужного формата.

Значение JSON для Date - это JsObject с именем свойства $date и меткой времени UNIX в миллисекундах в качестве значения:

{
    "something": {
        "$date": 1462288846873
    }
}

JSON, который я хочу, представляет собой строковое представление даты следующим образом:

{
    "something": "2016-05-03T15:20:46.873Z"
}

К сожалению, я не знаю, как изменить поведение по умолчанию, не переписывая все и не изменяя код в самой библиотеке.

Думаю, именно здесь и происходит (исходный код):

val partialWrites: PartialFunction[BSONValue, JsValue] = {
    case dt: BSONDateTime => Json.obj("$date" -> dt.value)
}

Моя версия должна была бы выглядеть так:

val partialWrites: PartialFunction[BSONValue, JsValue] = {
    case dt: BSONDateTime =>
        JsString(Instant.ofEpochMilli(dt.value).toString)
}

Можно ли отменить этот бит?

Я поставил эксперимент ...

import java.time.Instant
import play.api.libs.json._
import reactivemongo.bson._
import reactivemongo.play.json.BSONFormats.BSONDocumentFormat

object Experiment {

    // Original document (usually coming from the database)
    val bson = BSONDocument(
        "something" -> BSONDateTime(1462288846873L) // equals "2016-05-03T15:20:46.873Z"
    )

    // Reader: converts from BSONDateTime to JsString
    implicit object BSONDateTimeToJsStringReader extends BSONReader[BSONDateTime, JsString] {
        def read(bsonDate: BSONDateTime): JsString = {
            JsString(Instant.ofEpochMilli(bsonDate.value).toString)
        }
    }

    // Reader: converts from BSONDateTime to JsValue
    implicit object BSONDateTimeToJsValueReader extends BSONReader[BSONDateTime, JsValue] {
        def read(bsonDate: BSONDateTime): JsValue = {
            JsString(Instant.ofEpochMilli(bsonDate.value).toString)
        }
    }

    // Read and print specific property "something" using the `BSONReader`s above
    def printJsDate = {
        val jsStr: JsString = bson.getAs[JsString]("something").get
        println(jsStr) // "2016-05-03T15:20:46.873Z"

        val jsVal: JsValue = bson.getAs[JsValue]("something").get
        println(jsVal) // "2016-05-03T15:20:46.873Z"
    }

    // Use ReactiveMongo's default format to convert a BSONDocument into a JsObject
    def printAsJsonDefault = {
        val json: JsObject = BSONDocumentFormat.writes(bson).as[JsObject]
        println(json) // {"something":{"$date":1462288846873}}
        // What I want: {"something":"2016-05-03T15:20:46.873Z"}
    }

}

Я хотел бы отметить, что преобразование BSONDateTime в JsValue всегда должно работать, когда я конвертирую BSONDocument в JsObject, а не только когда я вручную выбираю конкретное известное свойство. Это означает, что свойство «что-то» в моем примере может иметь любое имя, а также отображаться во вложенном документе.

BTW: Если вам интересно, я обычно работаю с коллекциями BSON в моем проекте Play, но я все равно не думаю, что это имеет значение в данном случае.

Изменить

Я пробовал предоставить Writes[BSONDateTime], но, к сожалению, он не используется, и я по-прежнему получаю тот же результат, что и раньше. Код:

import java.time.Instant
import play.api.libs.json._
import reactivemongo.bson.{BSONDocument, BSONDateTime}

object MyImplicits {
    implicit val dateWrites = Writes[BSONDateTime] (bsonDate =>
        JsString(Instant.ofEpochMilli(bsonDate.value).toString)
    )

    // I've tried this too:
//  implicit val dateWrites = new Writes[BSONDateTime] {
//      def writes(bsonDate: BSONDateTime) = JsString(Instant.ofEpochMilli(bsonDate.value).toString)
//  }
}

object Experiment {
    // Original document (usually coming from the database)
    val bson = BSONDocument("something" -> BSONDateTime(1462288846873L))

    // Use ReactiveMongo's default format to convert a BSONDocument into a JsObject
    def printAsJson = {
        import reactivemongo.play.json.BSONFormats.BSONDocumentFormat
        import MyImplicits.dateWrites // import is ignored

        val json: JsObject = BSONDocumentFormat.writes(bson).as[JsObject]
        //val json: JsValue = Json.toJson(bson) // I've tried this too
        println(json) // {"something":{"$date":1462288846873}}
    }
}

person Nick    schedule 04.05.2016    source источник


Ответы (1)


Как и для любого типа, значение BSON преобразуется в Play JSON с использованием экземпляров из Writes[T].

Там вам нужно предоставить в неявной области видимость вашего собственного Writes[BSONDateTime].

import reactivemongo.bson._
import play.api.libs.json._

object MyImplicits {
  implicit val dateWrites = Writes[BSONDateTime] { date =>
    ???
  }

  def jsonDoc(doc: BSONDocument) = 
    JsObject(bson.elements.map(elem => elem._1 -> myJson(elem._2)))

  implicit val docWrites = OWrites[BSONDocument](jsonDoc)

  def myJson(value: BSONValue): JsValue = value match {
    case BSONDateTime(value) = ???
    case doc @ BSONDocument(_) => jsonDoc(doc)
    case bson => BSONFormats.toJSON(bson)
  }
}

/* where needed */ import MyImplicits.dateWrites
person cchantep    schedule 04.05.2016
comment
Большое спасибо за ваш ответ! Я реализовал Writes[BSONDateTime], как вы предложили, но, похоже, его игнорируют. См. Правка в моем вопросе. Мне почему-то кажется, что это потому, что я использую BSONDocumentFormat. Есть ли у вас какие-либо идеи? - person Nick; 04.05.2016
comment
Попробуйте println(s"writer = ${implicitly[Writes[BSONDateTime]]}") - person cchantep; 04.05.2016
comment
Отпечатки: writer = play.api.libs.json.Writes$$anon$6@b9c81b0 - person Nick; 04.05.2016