2014-01-10 2 views
10

Вот это простой JSON Я хочу писать/читать/из MongoDB:Play: Как преобразовать JSON при записи/чтении в/из MongoDB

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

Перед хранением в MongoDB, "ff59ab34cc59ff59ab34cc59" должен преобразуются в ObjectID и 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, т.е. переименования _id к id и преобразующим в ObjectID в виде простой текст в формате "ff59ab34cc59ff59ab34cc59".

+0

Есть ли причина, по которой вы не можете читать и писать свои документы с помощью «_id» вместо «id»? Кроме того, почему вы хотите преобразовать id в тип ObjectID? Вы можете хранить любой тип в поле _id в MongoDB. –

+1

«_id» - это техническая специфика MongoDB ... и я не хочу вводить такие зависимости, как в моей модели. Тогда, конечно, вы можете сохранить любой тип, который вы хотите в поле _id ... но ObjectID требует всего 12 байтов для хранения id [gu] (в двоичном формате), в то время как для простого текстового представления требуется 24 байта. – j3d

+0

Какой язык вы используете для своего приложения? Рассматривали ли вы использование уровня ODM? –

ответ

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 serializer/deserializer следующим образом:

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 \ $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) 

Вы не можете иметь BSONObjectID сейчас, поскольку вы манипулируете объектом из иерархии типов JsValue. Когда вы передаете json для ответа, он преобразуется в BSONValue. JsObject будет преобразован в BSONDocument. если JsObject содержит путь для _id\$oid, этот путь будет автоматически преобразован в BSONObjectId, и он будет сохранен как ObjectID в mongodb.

0

Исходный вопрос действительно о реактивированных (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 s.

Смежные вопросы