我是新手和scala / play,需要帮助playframework的JSON读/写。
我使用Json.reads [T]和Json.writes [T]宏来为我的类定义json读写。但是,我希望(一直)以不同的方式映射一个属性名称。也就是说,我的类中有一个名为id
的属性,当对象转换为json时,我希望它被表示为_id
,反之亦然。
有没有办法修改由Json.reads和Json.writes宏生成的读/写对象来实现这一点,还是我必须手动重写读写只是为了让一个属性命名不同?
修改
让我试着更好地解释这个问题。考虑模型对象User:
case class User (id: BigInt, email: String, name: String)
在将用户序列化为json以便在REST api的上下文中提供json时,json应如下所示:
{ “id”:23432, “名字”:“乔”, “email:”joe@example.com“ }
为了存储/更新/阅读形式将用户序列化为json时,MongoDB json应如下所示:
{ “_id”:23432, “名字”:“乔”, “email:”joe@example.com“ }
换句话说,除了与Mongo id
进行通信时,一切都是相同的,应该表示为_id
。
我知道我可以为每个模型对象手动编写两组读取和写入(一个用于Web,另一个用于与Mongo通信),如Darcy Qiu在答案中所建议的那样,但保持两组读写几乎相同的除了id属性似乎很多代码重复,所以我想知道是否有更好的方法。
答案 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)
假设我们为实体类生成了宏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
)
答案 1 :(得分: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]
应遵循相同的逻辑。
答案 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", "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仍然可以执行您想要的操作。