3

I'm building a Scala Play app where events and data are persisted in Json format, and I'm trying to model users and the roles they're assigned. My thinking has been to model Roles as case objects, as each standard role only needs defining once for all users in the application, and I'd like to pattern match on the type of role a particular user has been assigned. So far, I have this;

package models

abstract class User {
  def displayName: String
  def role: Role
}

case class GuestUser(displayName: String, role: Role) extends User {
}

case class RegisteredUser(displayName: String, role: Role) extends User {
}

trait Role { // have tried abstract class too - but like idea of developing stackable traits for role permissions
}
object Role {
  implicit val RoleTypeFormat: Format[Role] = Json.format[Role]
  def apply(className: String): Role = Class.forName(className: String).asInstanceOf[Role]
  def unapply(role: Role): Option[String] = Option(this.getClass.getName) // have also tried .getSimpleName
}

case object GuestUserRole extends Role {
}

case object RegisteredUserRole extends Role {
}

If I don't define an apply and unapply method in object Role, and rely only on the implicit value that uses Json.format[Role], I get a 'no apply function found' or 'no unapply function found' error - so I added them, to try and get rid of this error.

I couldn't get it to compile without adding .asInstanceOf[Role] to the Role apply method. It now compiles, but when I try to set the role: Role parameter of a new RegisteredUser instance using,

val role: Role = RegisteredUserRole

a new RegisteredUser instance is created, where the role property gets serialized to Json as;

"role":{"className":"models.Role$”}

But when I try to deserialize it, I get Exception in thread "pool-4868-thread-1" java.lang.ClassCastException: java.lang.Class cannot be cast to models.Role

My aim is to end up with the same RegisteredUser instance (or GuestUser instance), so I can do pattern matching in the view controllers, along the lines of;

def isAuthorized: Boolean = { 
    role match { 
      case RegisteredUserRole => true
      case GuestUserRole => false
      // etc
    }
}

Any help and advice on this would be very much appreciated. I'm not yet skilled and knowledgeable enough in Scala and Play to know whether I'm even on the right track with modelling Users and Roles.

functup
  • 43
  • 5
  • I would recommend defining a custom JSON format (i.e. not using the `Json.format` shortcut), rather than trying to add `apply` and `unapply` methods to make the sugar work. But to answer your actual question, you can't cast the `Class` to the object instance, you need to use the reflection API to get its static member (which has a name that's something like `MODULE$`) which will the be the actual instance. – lmm Dec 19 '14 at 11:50
  • Thanks for your recommendation and answer @lmm - I'll explore both approaches. – functup Dec 19 '14 at 14:33

1 Answers1

3

As @lmm suggested, it would be better to provide a custom Format[Role] rather than trying to create instances in a weird way.

Something like this:

implicit val fmt = new Format[Role] {

    def reads(js: JsValue): JsResult[Role] = {
        js.validate[String] fold (
            error => JsError(error),
            role => role match {
                case "GuestUserRole" => JsSuccess(GuestUserRole)
                case "RegisteredUserRole" => JsSuccess(RegisteredUserRole)
                case _ => JsError(Nil) // Should probably contain some sort of `ValidationError`
            }
        )
    }

    def writes(r: Role): JsValue = JsString(r.toString)
}
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • Thanks so much for this solution. It works perfectly :) (and thanks @lmm for recommending this route). I love the fact the case objects are referenced in a straight-forward and clear way. It would have taken me ages, if ever, to have arrived at this solution from my present state of knowledge, so thanks again. I shall study play.api.libs.json in more depth, to understand better custom formatting. – functup Dec 19 '14 at 17:29