Search code examples
mongodbscalacodeccase-classmongo-scala-driver

inherited elements of a case class not getting persisted into mongodb using scala mongo driver


I wanted to persist to mongo db using mongo-scala-driver 2.7.0 . My case class looks a bit as follows

case class App(
                  @BsonProperty("_id") id: String = "",
                  appName: String
                  createdBy: String,
                  createdAt: Date,
                  metadata: AppMetadata,
                )

sealed class AppMetadata {
  final val _t: String = this.getClass.getSimpleName
}

case class App1Metadata(clientId: String,
                        savedSearches: List[String]
                        
                        ) extends ScopeMetadata

case class App2Metadata(agencyId: String,
                        randomData: List[Int]
                        
                        ) extends ScopeMetadata

As you can see the metadata against an app can be very different depending on app(App1 and App2). Therefore, it is not possible to have one single metadata case class which can be used by all apps. Therefore I decided on having a case class for metadata per app, as shown above and extend them all from a parent trait. I was trying to follow scala mongo driver's documentation here and another stackoverflow question here to achieve this. As you see , I have put an _t field in the sealed trait. This according to the document is required as it will serve as hint while decoding and encoding the document to and from Bson.

Here is how my codecs look

def customCodeRegistry() = {
    fromProviders(
      Macros.createCodecProviderIgnoreNone[App](),
      Macros.createCodecProviderIgnoreNone[App1Metadata](),
      Macros.createCodecProviderIgnoreNone[App2Metadata]()
    )
  }


val customCodecRegistry2 = CodecRegistries.fromProviders(Macros.createCodecProvider[ScopeMetadata]())

val codecRegistry = fromRegistries(customCodeRegistry(), DEFAULT_CODEC_REGISTRY,customCodecRegistry2)

I persist using simply

override def addApp(app: App): Future[String] = {
    for {
      _ <- collection.insertOne(app).toFuture()
    } yield user.id
  }

The document that is being persisted in mongo db is missing the _t field inside metadata object, which is necessary according to the documentation in order for Mongo Scala Driver to convert bson to the right metadata case class, therefore when I try getting the document using

override def getUser(email: String): Future[Option[User]] = {
    collection.find(equal(EMAIL_FIELD, email)).headOption()
  }

I get the following in error message

Could not decode sealed case class. Missing '_t' field.

However, if I explicitly add an _t field to mongo inside metadata object with the name of the class it should be decoded to, everything works fine and I get the correct case class during a get operation. I do not understand why though with mongo insert , the _t field is not getting inserted


Solution

  • AFAIU the documentation, you don't need to provide codecs for each subtype and the _t should be added automatically.

    The docs say:

    you only need create a CodecProvider for the parent sealed trait/class. Internally an extra field (_t) is stored alongside the data

    so something like this should be enough for you:

    sealed trait AppMetadata 
    case class App1Metadata(clientId: String, savedSearches: List[String]) extends AppMetadata
    case class App2Metadata(agencyId: String, randomData: List[Int]) extends AppMetadata
    
    fromRegistries(fromProviders(classOf[AppMetadata]), DEFAULT_CODEC_REGISTRY)
    
    

    Assuming that ScopeMetadata is the same thing as AppMetadata.