Play not finding implicit definition

226 Views Asked by At

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,"[email protected]","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?

2

There are 2 best solutions below

0
Manu Chadha On BEST ANSWER

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 _)

}
3
László van den Hoek On

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._.