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
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
.