1

我是新手和 scala/play,需要 playframework 的 JSON 读/写方面的帮助。

我使用 Json.reads[T] 和 Json.writes[T] 宏来为我的类定义 json 读取和写入。但是,我希望(始终)以不同方式映射一个属性名称。即,我id在我的类中命名了属性,我希望它表示为_id对象转换为 json 的时间,反之亦然。

有没有办法修改由 Json.reads 和 Json.writes 宏生成的读/写对象来实现这一点,还是我必须手动重写读和写只是为了让一个属性命名不同?

编辑

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

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

在将 User 序列化为 json 以便在 REST api 的上下文中提供 json 时,json 应如下所示:

{ "id": 23432, "name": "Joe", "email: "joe@example.com" }

当为了存储/更新/读取表单而将用户序列化为 json 时,MongoDB json 应该如下所示:

{“_id”:23432,“姓名”:“乔”,“电子邮件:“joe@example.com”}

换句话说,除了与 Mongo 通信时,一切都相同,id应该表示为_id.

我知道我可以按照 Darcy Qiu 在答案中的建议为每个模型对象手动编写两组读取和写入(一组用于 Web,另一组用于与 Mongo 通信),但是维护两组读取和写入几乎除了 id 属性似乎有很多代码重复,所以我想知道是否有更好的方法。

4

3 回答 3

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 )

假设我们为实体类生成了Writes宏:Reads

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

然后我们将转换与宏生成的代码混合:

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

最后一件事情。Mongo 驱动程序希望确定(即 typesafe-sure)它插入的 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")
  }

}

最后一步是提供一个隐含的OFormat.

implicit val routeFormat: OFormat[T] = OFormat(
  readsWithMongoId,
  oWritesWithMongoId
)
于 2015-09-04T10:33:30.007 回答
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", "evil.overlord@hansi-meier.de")

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(例如通过添加其他属性),underscoreWrite 仍然会执行您想要的操作。

于 2013-12-23T09:47:29.787 回答
0

假设T您的问题中的案例类已命名User并具有如下定义

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]应该遵循相同的逻辑。

于 2013-10-20T14:44:30.590 回答