[JAVA-3553] Codec for Scala Enumeration Created: 13/Jun/19  Updated: 21/Mar/23

Status: Backlog
Project: Java Driver
Component/s: Scala
Affects Version/s: None
Fix Version/s: None

Type: New Feature Priority: Major - P3
Reporter: JOSE RONIERISON SANTOS SILVA Assignee: Unassigned
Resolution: Unresolved Votes: 5
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Linux based environment



 Description   

Hi Folks,

I've tried to use Scala Enumeration class in a case class that is a model stored in mongo using Scala Mongo Driver 2.4.2.

I did not find any default Codec for it in my search so I tried to create a Codec for it based on this ticket SCALA-343.

As the get method of CodecProvider only the clazz[T] value with Enumaration#Val or Enumeration#Value, is not possible to identify which Enumeration class should be decodec.

Let me provide an example of an workaround I did for it:

case object EnumOne extends Enumeration {
  val e1, e2 = Value
}
 
case object EnumTwo extends Enumeration {
  val e3, e4 = Value
}
 
object EnumerationCodecProvider extends CodecProvider {
  def isEnumVal[T](clazz: Class[T]): Boolean = {
   // I had to check the class name to know if it is a Enumeration Val or Value, since the   Enumeration#Val class is private 
    clazz.getName.equals("scala.Enumeration$Val") ||
      clazz.getName.equals("scala.Enumeration$Value")
  }
 
  override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = {
    if (isEnumVal(clazz)) {
      EnumerationCodec.asInstanceOf[Codec[T]]
    } else {
      null
    }
  }
 
  object EnumerationCodec extends Codec[Enumeration#Value] {
    private val enumMap: Map[String, String => Enumeration#Value] = Map(
      "enum_one" -> EnumOne,
      "enum_two" -> EnumTwo
    )
 
    override def decode(reader: BsonReader, decoderContext: DecoderContext): Enumeration#Value = {
      val identifier = reader.getCurrentName
 
      val enumValue = reader.readString()
      // I used the enumMap indexed with the collection key (identifier) to get the Enumeration class
      enumMap(identifier).withName(enumValue)
      }
    }
 
    override def encode(writer: BsonWriter, value: Enumeration#Value, encoderContext: EncoderContext): Unit = {
      writer.writeString(value.toString)
    }
 
    override def getEncoderClass: Class[Enumeration#Value] = classOf[Enumeration#Value]
  }
}

Is there a plan to add it in a future release for Scala Enumeration class? Or any way to not use the enumMap strategie I used in the previous example?

Thanks in advance,



 Comments   
Comment by Graham Houser [ 17/Mar/23 ]

EDIT: Additional testing, this only works if you have a single Enumeration in your codec registry. When I created additional Enumerations and tried to make them a provider through the same technique, only the first one added to the registry is used. Every subsequent Enumeration throws and error when trying to encode or decode.

I've been bashing my head on this issue, too. After writing a codec for an enumeration, I continued to get a "Can't find a codec for CodecCacheKey{clazz=class scala.Enumeration$Val" error, which led me to this thread.

I have a solution for your `enumMap` issue, I made `EnumerationCodecProvider` a class, passing in the Enumeration type to make the codec to be used in the `get`.

 

class EnumerationCodecProvider(enumeration: Enumeration) extends CodecProvider {
 
  def enumerationCodec[E <: Enumeration](enumeration: E): Codec[E#Value] = new Codec[E#Value] {
    override def decode(reader: BsonReader, decoderContext: DecoderContext): E#Value =
      enumeration.withName(reader.readString())
 
    override def encode(writer: BsonWriter, value: E#Value, encoderContext: EncoderContext): Unit =
      writer.writeString(value.toString)
 
    override def getEncoderClass: Class[E#Value] = classOf[E#Value]
  }
 
  def isEnumVal[T](clazz: Class[T]): Boolean = {
    clazz.getName.equals("scala.Enumeration$Val") ||
      clazz.getName.equals("scala.Enumeration$Value")
  }
 
  override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = {
    if (isEnumVal(clazz)) {
      enumerationCodec(enumeration).asInstanceOf[Codec[T]]
    } else {
      null
    }
  }
 
}
 
object Example extends Enumeration {
  type Example = Value
  val id: Example = Value("id")
 
  val codecProvider: CodecProvider = new EnumerationCodecProvider(Example)
 
}

Comment by Ivan Petrov [ 19/Apr/21 ]

Hi!

How did you make it compile?

private val enumMap: Map[String, String => Enumeration#Value] = 
Map(      
"enum_one" -> EnumOne,      
"enum_two" -> EnumTwo    )

this one doesn't compile for me...

Generated at Thu Feb 08 08:59:53 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.