For this case class:
case class People(names: Set[Int])
Travis Brown explained how to create PeopleReads: Reads[People] at this ans
Good question! The reason you can't use map is because Writes isn't a functor.
You can think of Writes[A] as something kind of like A => JsValue. But suppose I've got a A => JsValue and a A => B. Try to come up with some way of composing those functions to get a B => JsValue—it's just not possible.
Reads[A], on the other hand, is kind of like JsValue => A, and is a functor—it has a map method that takes a A => B, composes it with the Reads[A] / JsValue => A, and returns a Reads[B] / JsValue => B.
Writes is, however, a contravariant functor, and luckily enough Play knows that. When F is a contravariant functor, F[A] has a method contramap[B](f: B => A) instead of the usual map[B](f: A => B). So you can just write this:
case class People(names: Set[Int])
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val PeopleWrites: Writes[People] =
(__ \ 'names).write[Set[Int]].contramap(_.names)
Here (__ \ 'names).write[Set[Int]] is a Writes[Set[Int]] and (_.names) is a function People => Set[Int]. Combining them with contramap gives us a Writes[People].