【问题标题】:BSONDocument to JsObject and override BSONDateTimeFormatBSONDocument 到 JsObject 并覆盖 BSONDateTimeFormat
【发布时间】:2016-05-04 17:07:01
【问题描述】:

我将 ReactiveMongo 0.11.11 用于 Play 2.5,并希望将 BSONDocument 转换为 JsObject。

对于大多数 BSON 数据类型(String、Int...),默认值完全可以让库完成这项工作。对于 BSON 类型 DateTime (BSONDateTime),JSON 属性的值没有提供我需要的格式。

日期的 JSON 值是一个 JsObject,其属性名称为 $date,其值为 UNIX 时间戳(以毫秒为单位):

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

我想要的 JSON 是 Date 的字符串表示形式,如下所示:

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

不幸的是,我不知道如何在不重写所有内容或更改库本身的代码的情况下覆盖默认行为。

这就是我认为它发生的地方 (source code):

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"}
    }

}

我想指出,当我将 BSONDocument 转换为 JsObject 时,BSONDateTime 转换为 JsValue 应该始终有效,而不仅仅是当我手动选择特定的已知属性时。这意味着我的示例中的属性“某物”可以有任何名称,也可以出现在子文档中。

顺便说一句:如果您想知道,我通常在我的 Play 项目中使用 BSON 集合,但无论如何我认为这在这种情况下并没有什么不同。

编辑

我尝试提供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}}
    }
}

【问题讨论】:

    标签: scala reactivemongo play-reactivemongo


    【解决方案1】:

    对于任何类型,BSON 值都使用instances of Writes[T] 转换为 Play JSON。

    您需要在隐式范围内提供您自己的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
    

    【讨论】:

    • 非常感谢您的回答!我已经按照您的建议实现了Writes[BSONDateTime],但它似乎被忽略了。请参阅我的问题中的编辑。我不知何故觉得那是因为我使用了BSONDocumentFormat。你有什么想法吗?
    • 试试println(s"writer = ${implicitly[Writes[BSONDateTime]]}")
    • 打印:writer = play.api.libs.json.Writes$$anon$6@b9c81b0
    猜你喜欢
    • 1970-01-01
    • 2013-01-02
    • 1970-01-01
    • 2012-11-15
    • 2023-03-24
    • 2019-08-04
    • 2019-05-19
    • 2022-06-11
    • 1970-01-01
    相关资源
    最近更新 更多