2013-10-20 110 views
1

写道:我是一个新手,斯卡拉/播放,并需要playframework的JSON帮助读取/写入。修改JSON读取和playframework 2.1

我用Json.reads [T]和Json.writes [T]宏定义JSON读取和写入我的班。不过,我想有一个属性名称(总是)映射不同。也就是说,我有属性命名我的班id,我希望它被表示为_id当对象转换成JSON,反之亦然。

是否有修改的方式读取/写入由Json.reads和Json.writes宏来实现这个生成的对象还是我不得不重写读取和写入手动只是有不同的命名一个属性?

编辑

让我试着更好地解释这个问题。考虑模型对象用户:

case class User (id: BigInt, email: String, name: String) 

当序列化用户以JSON用于在静止的情况下,供应JSON的目的API的JSON应该是这样的:

{ “ID”:23432, “名 “: ”乔“, ” 电子邮件: “[email protected]” }

当序列化用户以JSON为目的,存储/更新/阅读形式MongoDB的JSON应该是这样的:

{ “_id”:23432, “名”: “乔”, “电子邮件: “[email protected]” }

换句话说一切都只是与蒙戈id通信时应该是相同的表示为_id

我知道我可以手动为每个模型对象编写两组读写操作(Darcy Qiu在答案中建议使用一个用于web和另一个用于与Mongo通信),但是保持两组读取和这是除了id属性好像所以我不知道有很多重复的代码是否有更好的方法几乎相同的写入。

回答

1

比方说,你的测试用例类,这是你的问题T,被命名为User,具有definision如下

case class User(_id: String, age: Int) 

你读有可能被定义为

implicit val userReads = new Reads[User] { 
    def reads(js: JsValue): User = { 
    User(
     (js \ "id").as[String], 
     (js \ "age").as[Int] 
    ) 
    } 
} 

writes[User]应遵循同样的逻辑。

+0

感谢您的回答。看看更新后的问题,我希望避免手动编写读/写两次,只有id属性序列化的区别。 –

+0

我明白了。我能想到的最好方法是分别有两种不同的“格式”来处理这两种情况。 – darcyq

+0

我最终得到了2组读写,正如答案中所建议的,所以我打算将其标记为已接受。 –

0

如果你投入足够的代码,你可以用变压器实现这一目标:

val idToUnderscore = (JsPath).json.update((JsPath).read[JsObject].map { o:JsObject => 
    o ++ o.transform((JsPath\"_id").json.put((JsPath\"id").asSingleJson(o))).get 
}) andThen (JsPath\"id").json.prune 
val underscoreWrite = normalWrites.transform(jsVal => jsVal.transform(idToUnderscore).get) 

下面是完整的测试:

import play.api.libs.functional.syntax._ 
import play.api.libs.json._ 

val u = User("overlord", "Hansi Meier", "[email protected]") 

val userToProperties = {u:User => (u.id, u.name, u.email)} 
val normalWrites = (
    (JsPath\"id").write[String] and 
    (JsPath\"name").write[String] and 
    (JsPath\"email").write[String] 
)(userToProperties) 

val idToUnderscore = (JsPath).json.update((JsPath).read[JsObject].map { o:JsObject => 
    o ++ o.transform((JsPath\"_id").json.put((JsPath\"id").asSingleJson(o))).get 
}) andThen (JsPath\"id").json.prune 
val underscoreWrite = normalWrites.transform(jsVal => jsVal.transform(idToUnderscore).get) 

info(Json.stringify(Json.toJson(u)(normalWrites))) 
info(Json.stringify(Json.toJson(u)(underscoreWrite))) 

现在,如果你修改normalWrites(比如通过增加额外的属性) ,下划线将仍然做你想要的。

+0

我写了一个测试来断言这是有效的。 它仍然觉得应该有一个更好的方式来改变写作。我将不胜感激任何有关如何改进代码的建议。 –

1

首先定义转换为重命名ID/_id来回:

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

val into: Reads[JsObject] = __.json.update(// copies the full JSON 
    (__ \ 'id).json.copyFrom((__ \ '_id).json.pick) // adds id 
) andThen (__ \ '_id).json.prune // and after removes _id 

val from: Reads[JsObject] = __.json.update(// copies the full JSON 
    (__ \ '_id).json.copyFrom((__ \ 'id).json.pick) // adds _id 
) andThen (__ \ 'id).json.prune // and after removes id 

(要理解为什么Reads是一个转变,请阅读:https://www.playframework.com/documentation/2.4.x/ScalaJsonTransformers

假设我们有宏观产生WritesReads为我们的实体类:

def entityReads: Reads[T] // eg Json.reads[Person] 
def entityWrites: Writes[T] // eg Json.writes[Person] 

然后我们混有m个转变acro生成代码:

private[this] def readsWithMongoId: Reads[T] = 
    into.andThen(entityReads) 
private[this] def writesWithMongoId: Writes[T] = 
    entityWrites.transform(jsValue => jsValue.transform(from).get) 

最后一件事。 Mongo驱动程序希望确保它(即类型安全)确定它插入的json是一个JsObject。这就是为什么我们需要一个OWrites。我还没有发现比更好的办法:

private[this] def oWritesWithMongoId = new OWrites[T] { 
    override def writes(o: T): JsObject = writesWithMongoId.writes(o) match { 
    case obj: JsObject => obj 
    case notObj: JsValue => 
     throw new InternalError("MongoRepo has to be" + 
     "definded for entities which serialize to JsObject") 
    } 

}

最后一步是privide隐式OFormat

implicit val routeFormat: OFormat[T] = OFormat(
    readsWithMongoId, 
    oWritesWithMongoId 
)