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 accept the identifier as Option[Long ], as you did with the email. Then you check if it is installed, and yes, you create an instance of Pk using Id[Long](id ), and if you do not provide an instance of NotAssigned Pk singleton.
Extra tip
Alternatively you can try using
implicit val jsonFormatter = Json.format[User]
It will create Reads and Writes for you directly at compile time. There are two problems with this if you use the anorm object directly:
First you need a format for 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) }
Secondly, it does not work if you do not send an identifier at all, even with a null value, so your client needs to send {"id": null, "name": "Tim"} , because it does not even try to call PkFormatter, which could handle JsUndefined but just gives you the error "validate.error.missing-path"
If you do not want to send null identifiers, you cannot use the Json.format[User] macro