0

I am writing a JSON Writes. In models/Users.scala, I have defined an implicit object with implicit definitions.

object UserImplicits {

  /*Writes (write to JsValue) are used by toJson method of Json object to convert data (say the model) to JsValue*/
    implicit val profileWrites:Writes[UserProfile] = (
        (JsPath \ "confirmed").write[Boolean] and
          (JsPath \ "email").write[String] and
          (JsPath \ "firstname").write[String] and
          (JsPath \ "lastname").write[String]
        ) (unlift(UserProfile.unapply))

      implicit val userWrites: Writes[User] = (
        (JsPath \ "id").write[UUID] and
          (JsPath \ "user-profile").write[UserProfile]
        ) (unlift(User.unapply))

      implicit val usersResourceWrites:Writes[UsersResource] = (
        (JsPath \ "id").write[String] and
          (JsPath \ "url").write[String] and
          (JsPath \ "user").write[User]
        ) (unlift(UsersResource.unapply))


  /*Reads (read from JsValue) is used by Json object's as or asOpt methods to convert JsValue to some other data, eg your model*/

      implicit val profileReads:Reads[UserProfile] = (
        (JsPath \ "confirmed").read[Boolean] and
          (JsPath \ "email").read[String] and
          (JsPath \ "firstname").read[String] and
          (JsPath \ "lastname").read[String]
        ) (UserProfile.apply _)

      implicit val userReads: Reads[User] = (
        (JsPath \ "id").read[UUID] and
          (JsPath \ "user-profile").read[UserProfile]
        ) (User.apply _)

      implicit val usersResourceReads: Reads[UsersResource] = (
        (JsPath \ "id").read[String] and
          (JsPath \ "url").read[String] and
          (JsPath \ "user").read[User]
        ) (UsersResource.apply _)

}

In my controller class, I have imported models._ and have defined the controller as follows:

import models._

import scala.concurrent.{ExecutionContext, Future}

class UserController @Inject()(cc: ControllerComponents)(implicit exec: ExecutionContext) extends AbstractController(cc){

  //TODOM - remove hard coded response
  def addUser = Action.async{ implicit request => {
    println("addUser controller called")
    val user = User(UUID.randomUUID(),UserProfile(true,"m@m.com","m","c"))
    val userResource = UsersResource(user.id.toString(),"/ws/users",user)
    val json = Json.toJson(userResource); //converts the model to JsValue using Writes defined in Users model class
    println("returning json:",Json.prettyPrint(json))
    Future{Ok(json)}}
  }

I am getting the following compilation error.

No Json serializer found for type models.UsersResource. Try to implement an implicit Writes or Format for this type. for code val json = Json.toJson(userResource);

The issue seem to be Play cannot find the implicit Writes. The code works if I move the implicit definitions in controller instead of defining in models. How could I make the implicits defined in model/user.scala visible in the controller class?

Manu Chadha
  • 15,555
  • 19
  • 91
  • 184

2 Answers2

0

If you move your implicits into the companion objects of the classes you're (de)serializing, the compiler will automatically pick them up. So, if this is your UserProfile case class:

case class UserProfile(confirmed: Boolean,
                       email: String,
                       firstname: String,
                       lastname: String)

...then you can just write this below (the key point being that it is identically named):

object UserProfile {
  implicit val profileWrites: Writes[UserProfile] = //...
  implicit val profileReads: Reads[UserProfile] = //...
}

or, just use one Format (which is a Reads and a Writes rolled into one), which can be trivially implemented if the JSON structure corresponds exactly to your case class field names:

object UserProfile {
  implicit val profileFormat: Format[UserProfile] = Json.format[UserProfile]

Alternatively, you can import UserImplicits._.

László van den Hoek
  • 3,955
  • 1
  • 23
  • 28
  • ‘Import’ worked but not the companion object. Maybe I didnt define the object correctly. ‘UserResource’ is a case class. I suppose it already has a companion object. How do I define companion object of a case class? – Manu Chadha Feb 21 '18 at 10:29
  • @ManuChadha updated my answer to show how to create a companion object. Also, see how to create a `Format` instead of separate, explicit `Reads` and `Write` instances. – László van den Hoek Feb 21 '18 at 10:42
  • Companion object didn’t work. Got the original missing implicit error – Manu Chadha Feb 21 '18 at 10:53
0

I had to create another object (not companion object) and add the implicit definitions there.

to use these implicits, use import models.UserImplicits._ in files where implicits are required.

object UserImplicits {

    /*Writes (write to JsValue) are used by toJson method of Json object to convert data (say the model) to JsValue*/
    implicit val profileWrites:Writes[UserProfile] = (
      (JsPath \ "confirmed").write[Boolean] and
        (JsPath \ "email").writeNullable[String] and
        (JsPath \ "firstname").writeNullable[String] and
        (JsPath \ "lastname").writeNullable[String]
      ) (unlift(UserProfile.unapply))


    implicit val userWrites: Writes[User] = (
      (JsPath \ "id").write[UUID] and
        (JsPath \ "user-profile").write[UserProfile]
      ) (unlift(User.unapply))

   implicit val usersResourceWrites:Writes[UsersResource] = (
      (JsPath \ "id").write[String] and
        (JsPath \ "url").write[String] and
        (JsPath \ "user").write[User]
      ) (unlift(UsersResource.unapply))


    /*Reads (read from JsValue) is used by Json object's as or asOpt methods to convert JsValue to some other data, eg your model*/

  implicit val profileReads:Reads[UserProfile] = (
    (JsPath \ "confirmed").read[Boolean] and
      (JsPath \ "email").readNullable[String] and
      (JsPath \ "firstname").readNullable[String] and
      (JsPath \ "lastname").readNullable[String]
    ) (UserProfile.apply _)


    implicit val userReads: Reads[User] = (
      (JsPath \ "id").read[UUID] and
        (JsPath \ "user-profile").read[UserProfile]
      ) (User.apply _)

    implicit val usersResourceReads: Reads[UsersResource] = (
      (JsPath \ "id").read[String] and
        (JsPath \ "url").read[String] and
        (JsPath \ "user").read[User]
      ) (UsersResource.apply _)

}
Manu Chadha
  • 15,555
  • 19
  • 91
  • 184