10

这是我想向 MongoDB 写入/读取的简单 JSON:

{
  "id": "ff59ab34cc59ff59ab34cc59",
  "name": "Joe",
  "surname": "Cocker"
}

在将其存储在 MongoDB 中之前,"ff59ab34cc59ff59ab34cc59"必须将其转换为 anObjectID并重id命名为_id... 所以给出以下内容Reads,我该如何实现?

val personReads: Reads[JsObject] = (
  (__ \ 'id).read[String] ~ // how do I rename id to _id AND transform "ff59ab34cc59ff59ab34cc59" to an ObjectID?
  (__ \ 'name).read[String] ~
  (__ \ 'surname).read[String]
) reduce

当然,我的 . 格式也需要相反的内容Writes,即将 an 重命名_idid并将其转换ObjectID为纯文本格式"ff59ab34cc59ff59ab34cc59"

4

2 回答 2

11

JsonExtensions

我的应用程序中通常有一个 JsExtensions 对象,如下所示:

import reactivemongo.bson.BSONObjectID
object JsonExtensions {

  import play.api.libs.json._

  def withDefault[A](key: String, default: A)(implicit writes: Writes[A]) = __.json.update((__ \ key).json.copyFrom((__ \ key).json.pick orElse Reads.pure(Json.toJson(default))))
  def copyKey(fromPath: JsPath,toPath:JsPath ) = __.json.update(toPath.json.copyFrom(fromPath.json.pick))
  def copyOptKey(fromPath: JsPath,toPath:JsPath ) = __.json.update(toPath.json.copyFrom(fromPath.json.pick orElse Reads.pure(JsNull)))
  def moveKey(fromPath:JsPath, toPath:JsPath) =(json:JsValue)=> json.transform(copyKey(fromPath,toPath) andThen fromPath.json.prune).get
}

对于一个简单的模型

case class SOUser(name:String,_id:BSONObjectID)

你可以像这样编写你的 json 序列化器/反序列化器:

object SOUser{
  import play.api.libs.json.Format
  import play.api.libs.json.Json
  import play.modules.reactivemongo.json.BSONFormats._

  implicit val soUserFormat= new Format[SOUser]{
    import play.api.libs.json.{JsPath, JsResult, JsValue}
    import JsonExtensions._
    val base = Json.format[SOUser]
    private val publicIdPath: JsPath = JsPath \ 'id
    private val privateIdPath: JsPath = JsPath \ '_id \ '$oid

    def reads(json: JsValue): JsResult[SOUser] = base.compose(copyKey(publicIdPath, privateIdPath)).reads(json)
    def writes(o: SOUser): JsValue = base.transform(moveKey(privateIdPath,publicIdPath)).writes(o)
  }
}

这是您在控制台中得到的:

scala> import reactivemongo.bson.BSONObjectID
import reactivemongo.bson.BSONObjectID

scala> import models.SOUser
import models.SOUser

scala> import play.api.libs.json.Json
import play.api.libs.json.Json

scala>

scala> val user = SOUser("John Smith", BSONObjectID.generate)
user: models.SOUser = SOUser(John Smith,BSONObjectID("52d00fd5c912c061007a28d1"))

scala> val jsonUser=Json.toJson(user)
jsonUser: play.api.libs.json.JsValue = {"name":"John Smith","id":"52d00fd5c912c061007a28d1","_id":{}}

scala> Json.prettyPrint(jsonUser)
res0: String =
{
  "name" : "John Smith",
  "id" : "52d00fd5c912c061007a28d1",
  "_id" : { }
}

scala> jsonUser.validate[SOUser]
res1: play.api.libs.json.JsResult[models.SOUser] = JsSuccess(SOUser(John Smith,BSONObjectID("52d00fd5c912c061007a28d1")),/id)

将此应用于您的示例

val _personReads: Reads[JsObject] = (
  (__ \ 'id).read[String] ~
  (__ \ 'name).read[String] ~
  (__ \ 'surname).read[String]
).reduce

默认情况下不编译,我猜你的意思是这样写:

val _personReads: Reads[(String,String,String)] = (
  (__ \ 'id).read[String] ~
  (__ \ 'name).read[String] ~
  (__ \ 'surname).read[String]
).tupled

在这种情况下,您可以执行以下操作

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import play.modules.reactivemongo.json.BSONFormats._
import reactivemongo.bson.BSONObjectID

def copyKey(fromPath: JsPath,toPath:JsPath ) = __.json.update(toPath.json.copyFrom(fromPath.json.pick))

val json = """{
  "id": "ff59ab34cc59ff59ab34cc59",
  "name": "Joe",
  "surname": "Cocker"
}"""

val originaljson = Json.parse(json)
val publicIdPath: JsPath = JsPath \ 'id
val privateIdPath: JsPath = JsPath \ '_id \ '$oid

val _personReads: Reads[(BSONObjectID,String,String)] = (
  (__ \ '_id).read[BSONObjectID] ~
  (__ \ 'name).read[String] ~
  (__ \ 'surname).read[String]
).tupled
val personReads=_personReads.compose(copyKey(publicIdPath,privateIdPath))

originaljson.validate(personReads)
// yields res5: play.api.libs.json.JsResult[(reactivemongo.bson.BSONObjectID, String, String)] = JsSuccess((BSONObjectID("ff59ab34cc59ff59ab34cc59"),Joe,Cocker),/id)

或者你的意思是你想移动 id 键的值,_id \ $oid可以用

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import play.modules.reactivemongo.json.BSONFormats._
import reactivemongo.bson.BSONObjectID

def copyKey(fromPath: JsPath,toPath:JsPath ) = __.json.update(toPath.json.copyFrom(fromPath.json.pick))

val json = """{
  "id": "ff59ab34cc59ff59ab34cc59",
  "name": "Joe",
  "surname": "Cocker"
}"""

val originaljson = Json.parse(json)
val publicIdPath: JsPath = JsPath \ 'id
val privateIdPath: JsPath = JsPath \ '_id \ '$oid

originaljson.transform(copyKey(publicIdPath,privateIdPath) andThen publicIdPath.json.prune)

由于您正在从 JsValue 类型层次结构中操作对象,因此您现在不能在其中拥有 BSONObjectID。当您将 json 传递给 reactivemongo 时,它会转换为 BSONValue。一个 JsObject 将被转换为一个 BSONDocument。如果 JsObject 包含路径,_id\$oid则此路径将自动转换为 BSONObjectId,并将其作为 ObjectID 存储在 mongodb 中。

于 2014-01-10T15:21:53.100 回答
0

最初的问题实际上是关于 reactivemongo(sgodbillon 等人)对本机 mongodb 的处理_id。选择的答案是有启发性和正确的,但间接地解决了 OP 的担忧,即“一切都会奏效”。

感谢https://github.com/ReactiveMongo/ReactiveMongo-Play-Json/blob/e67e507ecf2be48cc71e429919f7642ea421642c/src/main/scala/package.scala#L241-L255,我相信它会的。

import scala.concurrent.Await
import scala.concurrent.duration.Duration

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.functional.syntax._
import play.api.libs.json._
import play.modules.reactivemongo.json.collection.JSONCollection
import reactivemongo.api._
import reactivemongo.bson.BSONObjectID
import reactivemongo.play.json._

case class Person(
id: BSONObjectID,
name: String,
surname: String
)

implicit val PersonFormat: OFormat[Person] = (
  (__ \ "_id").format[BSONObjectID] and
    (__ \ "name").format[String] and
    (__ \ "surname").format[String]
)(Person.apply, unlift(Person.unapply))

val driver = new reactivemongo.api.MongoDriver
val connection = driver.connection(List("localhost"))
val db = connection.db("test")
val coll = db.collection[JSONCollection]("persons")
coll.drop(false)

val id = BSONObjectID.generate()
Await.ready(coll.insert(Person(id, "Joe", "Cocker")), Duration.Inf)
Await.ready(coll.find(Json.obj()).one[Person] map { op => assert(op.get.id == id, {}) }, Duration.Inf)

以上是您的案例类使用的最小工作示例,id并且数据库将其存储为_id. 两者都被实例化为 12 字节BSONObjectID

于 2018-01-30T20:24:25.107 回答