在playframework 2.1中修改JSON读写

在playframework 2.1中修改JSON读写,json,scala,playframework-2.1,Json,Scala,Playframework 2.1,我是一名新手,使用scala/play,需要playframework的JSON读写帮助 我使用Json.reads[T]和Json.writes[T]宏来定义类的Json读写。但是,我希望有一个属性名(始终)以不同的方式映射。也就是说,我的类中有一个名为id的属性,我希望在对象转换为json时将其表示为\u id,反之亦然 有没有办法修改由Json.reads和Json.writes宏生成的读/写对象来实现这一点,或者我必须手动重写读和写操作才能使一个属性命名不同 编辑 让我试着更好地解释这个

我是一名新手,使用scala/play,需要playframework的JSON读写帮助

我使用Json.reads[T]和Json.writes[T]宏来定义类的Json读写。但是,我希望有一个属性名(始终)以不同的方式映射。也就是说,我的类中有一个名为
id
的属性,我希望在对象转换为json时将其表示为
\u id
,反之亦然

有没有办法修改由Json.reads和Json.writes宏生成的读/写对象来实现这一点,或者我必须手动重写读和写操作才能使一个属性命名不同

编辑

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

case class User (id: BigInt, email: String, name: String)
为了在REST api上下文中提供json,将用户序列化为json时,json应如下所示:

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

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

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

换言之,除了与Mongo通信时,
id
应表示为
\u id


我知道我可以为每个模型对象手动编写两组读写操作(一组用于web,另一组用于与Mongo通信),正如Darcy Qiu在回答中所建议的那样,然而,除了id属性外,维护两组几乎相同的读写操作似乎有很多代码重复,所以我想知道是否有更好的方法。

假设您的case类,在您的问题中是
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]
    )
  }
}

您的
写入[User]
应该遵循相同的逻辑。

假设您的案例类,在您的问题中是
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]
    )
  }
}

您的
写入[User]
应该遵循相同的逻辑。

如果您输入足够的代码,您可以使用transformers实现这一点:

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

现在,如果您修改normalwrite(比如通过添加其他属性),下划线write仍将执行您想要的操作。

如果您输入足够的代码,您可以使用transformers实现这一点:

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(例如通过添加其他属性),下划线Write仍将执行您想要的操作。

首先,您要定义来回重命名id/\u 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
是一种转换,请阅读:)

假设我们为实体类宏生成了
写入
读取

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

最后一步是隐式
格式

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

首先定义来回重命名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
是一种转换,请阅读:)

假设我们为实体类宏生成了
写入
读取

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

最后一步是隐式
格式

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

谢谢你的回答。看看更新后的问题,我希望避免手动写入读/写两次,唯一的区别是id属性的序列化方式。我明白了。我能想到的最好的方法是使用两种不同的
格式
分别处理这两种情况。我按照答案中的建议完成了两组读写操作,因此我将其标记为已接受。感谢您的回答。看看更新后的问题,我希望避免手动写入读/写两次,唯一的区别是id属性的序列化方式。我明白了。我能想到的最好的方法是使用两种不同的
格式
分别处理这两种情况。我按照答案中的建议完成了两组读写操作,因此我将其标记为已接受。我编写了一个测试来断言这是可行的。现在仍然觉得应该有更好的方法来改变书写方式。如果您能给我一些改进代码的建议,我将不胜感激。我编写了一个测试来证明这是可行的。现在仍然觉得应该有更好的方法来改变书写方式。我很感激任何关于如何改进代码的建议。