scalaplayframeworkplay-reactivemongo

Play ReactiveMongo JSON Serialization Generics


I have a problem using Play 2.5.x and ReactiveMongo Play. I am trying to create a Generic Repository and I have serious problems when serialize and deserialize objects to database. It always give me the following error: No Json deserializer found for type E. Try to implement an implicit Reads or Format for this type.

Here is my generic code:

package repositories.mongo

import javax.inject.Inject

import core.Entity
import play.modules.reactivemongo.ReactiveMongoApi
import reactivemongo.api.QueryOpts
import repositories.Repository

import scala.collection.Seq
import scala.concurrent.{ExecutionContext, Future}
import reactivemongo.play.json._
import play.api.libs.json._
import reactivemongo.play.json.collection.JSONCollection

class MongoRepository[K, E <: Entity[K]] @Inject()(reactiveMongo: ReactiveMongoApi) extends Repository[K, E] {

  protected def collection(implicit ec: ExecutionContext) = reactiveMongo.database.map(_.collection[JSONCollection](this.getCollectionName))

  protected def getCollectionName: String = {
    "users"
  }

  def getAll(count: Int, skip: Int)(implicit ec: ExecutionContext): Future[Seq[E]] = {
    this.collection.flatMap(_.find(Json.obj())
      .options(QueryOpts(skipN = skip))
      .cursor[E]().collect[Seq[E]](count))
  }

  def getFilter(count: Int, skip: Int, f: E => Boolean)(implicit ec: ExecutionContext): Future[Seq[E]] = {

    this.collection.flatMap(_.find(f)
      .options(QueryOpts(skipN = skip))
      .cursor[E]().collect[Seq[E]](count))
  }

  def getById(id: K)(implicit ec: ExecutionContext): Future[Option[E]] = {
    this.collection.flatMap(_.find(Json.obj("_id" -> id.toString)).one[E])
  }

  def create(entity: E)(implicit ec: ExecutionContext): Future[Option[E]] = {
    this.collection.flatMap(_.insert(entity)).flatMap(_ => Future.successful(Option(entity)))
  }

  def updateById(id: K, entity: E)(implicit ec: ExecutionContext): Future[Option[E]] = {
    this.collection.flatMap(_.findAndUpdate(Json.obj("_id" -> id.toString), entity)
      .map(_.result[E]))
  }

  def deleteById(id: K)(implicit ec: ExecutionContext): Future[Option[E]] = {
    this.collection.flatMap(_.findAndRemove(Json.obj("_id" -> id.toString))
      .map(_.result[E]))
  }

}

Here is my concrete class that includes the json format serializer.

package core

import play.api.libs.json.Json

trait Entity[K] {
  val id: K
}

case class User(
                 id: String,
                 name: String,
                 email: String
               ) extends Entity[String] {

}

object User {
  implicit val jsonFormat = Json.format[User]
}

Solution

  • When you create your MongoRepository you need to say that E needs a json Format. You can do it like this:

    class MongoRepository[K, E <: Entity[K]: Format]
    
    // this is the same as
    
    class MongoRespository[K, E <: Entity[K]](implicit formatter: Format[E])