As Play JSON library uses implicits in order to know how to serialize/deserialize some object the type of that object has to be known at compile time. This makes serializing/deserializing generic classes not that straightforward - even if you know at compile time type which is substituted in generic class type parameter, you need to provide somehow reads/writes for each such type, not just for single generic class. The trick is to use def instead of val. Here is an example

Say, we have reads and writes for some classes Foo and Bar, and we’d like to serialize/deserialize such containers with them:

case class MyContainer1[T](data: Seq[T])
case class MyContainer2[T](name: String, data: Seq[T])

You’ll need to define writes like this:

implicit def myContainer1Writes[T](implicit dw: Writes[T]) = 
  new Writes[SeqContainer[T]] {
    override def writes(o: SeqContainer[T]): JsValue =
      Json.toJson(Map("data" -> o.data))
}

implicit def myContainer2Writes[T](implicit dw: Writes[T]) = 
  new Writes[SeqContainer[T]] {
    override def writes(o: SeqContainer[T]): JsValue =
      Json.toJson(Map("name" -> o.name, "data" -> o.data))
}

and reads like this:

implicit def myContainer1Reads[T](implicit dr: Reads[T]):
  Reads[SeqContainer[T]] = 
     (JsPath \ "data").read[Seq[T]].map(MyContainer2[T])

implicit def myContainer2Reads[T](implicit dr: Reads[T]):
  Reads[SeqContainer[T]] = (
     (JsPath \ "name").read[String] and
     (JsPath \ "data").read[Seq[T]]
   )(MyContainer2[T] _)

And now having these implicits in your scope you can write:

val foos1: Seq[Foo] = ...
val json: JsValue = Json.toJson(MyContainer1(foos))
val foos2: Seq[Foo] = json.as[MyContainer1[Foo]].data
foos2 must be(foos1)