play anorm, create a model from json without passing anorm PK value in the json
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]
Type anorm.Pk
is almost exactly like scala.Option
in form and function. Avoid writing concrete Reads
and Writes
for all types it might possibly contain. An example that follows the OptionReads
and OptionWrites
implementations is as follows:
implicit def PkWrites[T](implicit w: Writes[T]): Writes[Pk[T]] = new Writes[Pk[T]] { def writes(o: Pk[T]) = o match { case Id(value) => w.writes(value) case NotAssigned => JsNull }}implicit def PkReads[T](implicit r: Reads[T]): Reads[Pk[T]] = new Reads[Pk[T]] { def reads(js: JsValue): JsResult[Pk[T]] = r.reads(js).fold(e => JsSuccess(NotAssigned), v => JsSuccess(Id(v)))}
This way, you support every T
for which there's a respective Reads[T]
or Writes[T]
, and it's more reliable in handling Id
and NotAssigned
.