scala 将枚举字段序列化为BSON时出现异常

ocebsuys  于 2023-03-18  发布在  Scala
关注(0)|答案(2)|浏览(138)

我使用的是带有SCALA和mongodb驱动程序的PlayFramework 2.6。
写入时难以处理枚举序列化。
当我尝试插入包含枚举字段的对象时,出现异常:

Caused by: org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class scala.Enumeration$Val.
at org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.java:46)
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:63)
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
at org.mongodb.scala.bson.codecs.macrocodecs.MacroCodec.writeValue(MacroCodec.scala:167)
at org.mongodb.scala.bson.codecs.macrocodecs.MacroCodec.writeValue$(MacroCodec.scala:162)

我为对象本身创建了编解码器,也尝试为枚举类类型创建编解码器,但看起来是正确的方式。

val routeTypeCodec: CodecProvider = Macros.createCodecProvider[RouteTypeClass]
  val routeCodec: CodecProvider = Macros.createCodecProviderIgnoreNone[Route]

列举:

class RouteTypeClass extends TypeReference[RouteType.type]

object RouteType extends Enumeration {
  type RouteType = Value
  val Repeat, OneTime = Value
}

保存对象:

case class Route(
 ...
 routeType: RouteType
 ...
);

如何为枚举字段编写编解码器,除了将此字段定义为字符串。

oaxa6hgo

oaxa6hgo1#

放弃使用枚举。相反,使用密封的trait和case类。不是很优雅的解决方案,但是可以工作。

sealed trait RouteType {
  def routeType: String
}

case class Repeat() extends RouteType {
  override val routeType: String = "Repeat"
}
case class OneTime() extends RouteType {
  override val routeType: String = "OneTime"
}

objet RouteType {
  def parseFromString(value: String): Option[RouteType] = {
    Vector(Repeat(), OneTime().find(_.routeType == value)
  }
}

并为它们编写自定义BSON编解码器

import org.bson.{BsonReader, BsonWriter}
import org.bson.codecs.{Codec, DecoderContext, EncoderContext}

class RouteTypeCodec extends Codec[RouteType] {
  override def encode(
    writer: BsonWriter,
    value: RouteType,
    encoderContext: EncoderContext
  ): Unit = writer.writeString(value.routeType)
  override def getEncoderClass: Class[RouteType] = classOf[RouteType]
  override def decode(reader: BsonReader,
                        decoderContext: DecoderContext): RouteType= {
    val value = reader.readString()
    val result = RouteType.parseFromString(value)
    if (result.isDefined) result.get else throw new IllegalArgumentException("No such RouteType")
  }
}
0aydgbwb

0aydgbwb2#

我已经为这个问题头疼了一段时间,并且找到了一个解决方案。这是Scala 2.13中的工作,Play 2.8
我从编写一个自定义编解码器开始,但我仍然得到了一个Can't find a codec for CodecCacheKey{clazz=class scala.Enumeration$Val错误。似乎编解码器注册表将识别枚举覆盖Enumeration$Value的编解码器,但仍然找到Enumeration$Val,即使它是一个私有类,不会直接使用。在public mongodb jira中有一个长期开放的问题。
我通过创建一个扩展CodecProvider的类来解决此问题,该类具有Enumeration#Value的通用编解码器,并检查scala.Enumeration$Valscala.Enumeration$Value的className

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)
}

相关问题