I did not compile this, but it should work:
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: Option[String])
implicit object UserReads extends Reads[User] {
def reads(json: JsValue) = JsSuccess(User(
(json \ "id").asOpt[Long].map(id => Id[Long](id)).getOrElse(NotAssigned)
(json \ "name").as[String],
(json \ "email").asOpt[String])
}
implicit object UserWrites extends Writes[User] {
def writes(user: User) = JsObject(Seq(
"id" -> JsNumber(user.id.get),
"name" -> JsString(user.name),
"email" -> Json.toJson(user.email))
}
Basically you take the id as Option[Long
] like you did with the email. Then you check if it is set and if yes you create the Pk instance with Id[Long](id
) and if not you provide the NotAssigned
Pk singleton instance.
Additional Tip
Alternatively you can try to use
implicit val jsonFormatter = Json.format[User]
It will create the Reads
and the Writes
for you directly at compile time.
There are two problem with this if you are using an anorm object directly:
First you need a Format for the Pk[Long]
implicit object PkLongFormat extends Format[Pk[Long]] {
def reads(json: JsValue): JsResult[Pk[Long]] = {
json.asOpt[Long].map {
id => JsSuccess(Id[Long](id))
}.getOrElse(JsSuccess(NotAssigned))
}
def writes(id: Pk[Long]): JsNumber = JsNumber(id.get)
}
Second it does not work, if you do not send an id at all, not even with value null, so your client needs to send {"id": null, "name": "Tim"}
because it does not even try to call the PkFormatter which could handle the JsUndefined, but simply gives you an "validate.error.missing-path" error
If you don't want to send null ids you cannot use the macro Json.format[User]