Question

object Users  {

  implicit object UserReads extends Reads[User] {
    def reads(json: JsValue) = JsSuccess(User(
      Id((json \ "id").as[String].toLong),
      (json \ "name").as[String],
      (json \ "email").as[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)
  }

  def view(id: Long) = Action { implicit request =>
    Ok(Json.toJson(User.find(id)))
  }

  def all() = Action { implicit request =>
    Ok(Json.toJson(User.findAll()))
  }

  def save = Action(parse.json) { request =>
    val userJson = request.body
    val user = userJson.as[User]
    try {
      User.create(user)
      Ok("Saved")
    } catch {
      case e: IllegalArgumentException => BadRequest("Error")
    }
  }
}


case class User(
  id: Pk[Long] = NotAssigned,
  name: String = "",
  email: String = "")

The above two are my controller & model, when I send the User data [id, name, email] as json using angular JS, it creates the User object in the database. But it should be able to create when I input only [name, email] or just [name] as email could be null. If I'm not wrong I should adjust these in the reads and writes method of User, is it?.

Also, can we have two reads/writes for different pursposes, if so how can that be achieved - throw some light. Thanks.

One Issue fixed with the following:

case class User(
  id: Pk[Long] = NotAssigned,
  name: String = "",
  email: Option[String])

implicit object UserReads extends Reads[User] {
    def reads(json: JsValue) = JsSuccess(User(
      Id((json \ "id").as[String].toLong),
      (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))
  }

Now email can be null, but what about id - PK[Long]

Was it helpful?

Solution

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]

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top