Search code examples
mongodbscalamongo-scala-driver

Is it possible to create a Codec Provider from a generic case class?


I am trying to make a generic function which produces a CodecProvider from a given generic case class.

The BSON macro documentation does not give any examples of this.

This (unanswered) SO question is similar, however I am not interested in enumerating all possible codec's for a given type parameter. Also, my question does not deal with type bounds or type variances.

Here is a minimal example of the code which does not compile.

import org.mongodb.scala.bson.codecs.Macros

case class Foo(x: Int)

case class Bar[T](x: T)

def fooCodecProvider = Macros.createCodecProvider[Foo]()
// Compiles! (No generic)

def barCodecProvider[T] = Macros.createCodecProvider[Bar[T]]()
// Compile Error:(8, 70) class Bar takes type parameters

I expect the barCodecProvider to compile, however it does not.

The compilation error thrown by the above code reads class Bar takes type parameters which is confusing because I have clearly provided the type parameter T to Bar via the signature of the generic barCodecProvider function. Do I have a typing-related syntax error? Is error a sign that I am using the mongo-scala-driver incorrectly?


Solution

  • While It's possible with other libraries like circe via implicit lookup. It seems impossible with org.mongodb.scala.bson.codecs.Macros because no Macro function take parameters.

    But you could do it your self if you know how to make a Codec.

    Codec seems to be a simple trait with 3 methods encode decode and getEncoderClass

    implicit val fooCodec : Codec[Foo] = Macros.createCodecProvider[Foo]()
    
    def barCodecProvider[T: ClassTag](implicit codecT : Codec[T]) = new Codec[Bar[T]] {
      override def decode(reader: BsonReader, decoderContext: DecoderContext): Bar[T] = {
        Bar[T](codecT.decode(reader,decoderContext))
      }
    
      override def encode(writer: BsonWriter, value: Bar[T], encoderContext: EncoderContext): Unit = {
        codecT.encode(writer,  value.x, encoderContext)
      }
      //the tricky one
      override def getEncoderClass: Class[Bar[T]] = classTag[Bar[T]].runtimeClass.asInstanceOf[Class[Bar[T]]]
    }
    
    val barFooCodec : Codec[Bar[Foo]] = barCodecProvider[Foo]
    
    

    It's simple example, but it's give an idea of what you can do. Use macro to generate simple instances, and composes theses instances with functions using implicits to fetch automatically the right instances.