2
votes

I have a use case where I want to serialize a User entity to json. This user entity includes private fields I don't want to expose, such as password.

I'm using a custom OWrites to deal with this when I return a single user:

val userSafeWrites: OWrites[User] = (
      (__ \ EMAIL_FIELD).write[String] ~
      (__ \ FIRST_NAME_FIELD).write[String] ~
      (__ \ LAST_NAME_FIELD).write[String] ~
      (__ \ PHONE_NUMBER_FIELD).write[Option[String]] ~
      (__ \ ID_FIELD).write[Long]
    )(p => (p.email, p.firstName, p.lastName, p.phoneNumber, p._id.get))

and then I specify the OWrites instead of using the implicit:

 Ok(Json.toJson(user)(User.userSafeWrites))

However, I have now to return a Set[User].

How can I do that? Do I need to implement a OWrites[Set[User]]? I can understand how to do that if I was to return an object with a field name with the results, such as:

{
  "users": [{user1}, {user2}]
}

However, I want to return simply an array, to comform to the output in other endpoints:

[{user1}, {user2}]

Or I should map each element of the set to a JsObject and apply the custom OWrites to each object? What would be the most efficient way to do that?

I feel this is something pretty simple and I'm just being a moron for not finding the answer myself.

2
You have to "expose" your OWrites[User] in the implicit scope (either explicitly import it, or have it in the companion object of User). Then, the Writes[Set[T]] provided by Play will worker with T = User, without more to do.cchantep
In this case I can't have the OWrites as an implicit, because I already have another OWrites[User] in the implicit scope. I have a two different serialization requirements for this class.redwulf
Nothing prevent you from importing this as a local implicit (only within the scope of one function, not for the whole class).cchantep

2 Answers

1
votes

You are reimplementing writes for traversable that are trivial anyway but still. Another option is to reuse that Writes

val users: Set[User] = ???
Json.toJson(users)(Writes.traversableWrites(userSafeWrites))

Writes for Traversable are defined as follows:

implicit def traversableWrites[A: Writes] = Writes[Traversable[A]] { as =>
  JsArray(as.map(toJson(_)).toSeq)
}

Which is just what you did.

It takes as implicit parameter of type Writes[A] and uses it to write each member. You can pass this parameter explicitly to obtain desired Writes.

Another option as @cchantep mentioned is just to import your implicit where you need it.

For example, having some model with default writes

case class User(num: Int)

object User {
  implicit val writes = Json.writes[User]
}

and another object with the custom writes

object OtherWrites {
  implicit val custom: Writes[User] = new Writes[User] {
    override def writes(o: User): JsValue = JsNull
  }
}

in the class that is the client

object Client extends App {
  val obj = List(User(1))
  print(Json.toJson(obj))
}

You would get [{"num":1}] printed as default writes are present in the implicit scope because they are defined in companion object of User.

You can import custom writes where you need them and those from companion object will be overriden

object Client extends App {
  import test.OtherWrites._
  val obj = List(User(1))
  print(Json.toJson(obj))
}

And you get [null] printed.

0
votes

Meh, it's as simple as:

Ok(JsArray(userSet.map(user => Json.toJson(user)(User.userSafeWrites)).toSeq))

Spent over one hour googling and trying to sort it out in the most ridiculous ways before posting the question. Solved it in one minute after posting the question.

Having one of those days... unless there's a better way to do it