[英](De)serialize enum as string in Scala 3
我试图找到一种简单有效的方法来使用circe
在 Scala 3 中(反)序列化枚举。
考虑以下示例:
import io.circe.generic.auto._
import io.circe.syntax._
enum OrderType:
case BUY
case SELL
case class Order(id: Int, `type`: OrderType, amount: String)
val order = Order(1, OrderType.SELL, "123.4")
order.asJson
在序列化数据时,它变成
{
"id" : 1,
"type" : {
"SELL" : {
}
},
"amount" : "123.4"
}
代替
{
"id" : 1,
"type" : "SELL",
"amount" : "123.4"
}
这就是我想要的。
我知道我可以为此编写一个自定义(反)序列化器,它将解决这个特定实例的问题,如下所示:
implicit val encodeOrderType: Encoder[OrderType] = (a: OrderType) =>
Encoder.encodeString(a.toString)
implicit def decodeOrderType: Decoder[OrderType] = (c: HCursor) => for {
v <- c.as[String]
} yield OrderType.valueOf(v)
但我一直在寻找可能适用于任何enum
的通用解决方案。
编辑 1
进行序列化的一种方法(反序列化不起作用:/)是使所有枚举扩展一个共同特征并为所有扩展它的枚举定义编码器。 对于上面的示例,它看起来像这样。
trait EnumSerialization
enum OrderType extends EnumSerialization:
case BUY
case SELL
enum MagicType extends EnumSerialization:
case FIRE
case WATER
case EARTH
case WIND
implicit def encodeOrderType[A <: EnumSerialization]: Encoder[A] = (a: A) => Encoder.encodeString(a.toString)
// This correctly serializes all instances of enum into a string
case class Order(id: Int, `type`: OrderType, amount: String)
val order = Order(1, OrderType.SELL, "123.4")
val orderJson = order.asJson
// Serializes to { "id" : 1, "type" : "SELL", "amount" : "123.4"}
case class Magic(id: Int, magic: MagicType)
val magic = Magic(3, MagicType.WIND)
val magicJson = magic.asJson
// Serializes to { "id" : 3, "magic" : "WIND"}
但是,这不会扩展到反序列化。
在 Scala 3 中,您可以使用 Mirrors 直接进行推导:
import io.circe._
import scala.compiletime.summonAll
import scala.deriving.Mirror
inline def stringEnumDecoder[T](using m: Mirror.SumOf[T]): Decoder[T] =
val elemInstances = summonAll[Tuple.Map[m.MirroredElemTypes, ValueOf]]
.productIterator.asInstanceOf[Iterator[ValueOf[T]]].map(_.value)
val elemNames = summonAll[Tuple.Map[m.MirroredElemLabels, ValueOf]]
.productIterator.asInstanceOf[Iterator[ValueOf[String]]].map(_.value)
val mapping = (elemNames zip elemInstances).toMap
Decoder[String].emap { name =>
mapping.get(name).fold(Left(s"Name $name is invalid value"))(Right(_))
}
inline def stringEnumEncoder[T](using m: Mirror.SumOf[T]): Encoder[T] =
val elemInstances = summonAll[Tuple.Map[m.MirroredElemTypes, ValueOf]]
.productIterator.asInstanceOf[Iterator[ValueOf[T]]].map(_.value)
val elemNames = summonAll[Tuple.Map[m.MirroredElemLabels, ValueOf]]
.productIterator.asInstanceOf[Iterator[ValueOf[String]]].map(_.value)
val mapping = (elemInstances zip elemNames).toMap
Encoder[String].contramap[T](mapping.apply)
enum OrderType:
case BUY
case SELL
object OrderType:
given decoder: Decoder[OrderType] = stringEnumDecoder[OrderType]
given encoder: Encoder[OrderType] = stringEnumEncoder[OrderType]
end OrderType
import io.circe.syntax._
import io.circe.generic.auto._
case class Order(id: Int, `type`: OrderType, amount: String)
val order = Order(1, OrderType.SELL, "123.4")
order.asJson
// {
// "id" : 1,
// "type" : "SELL",
// "amount" : "123.4"
// }: io.circe.Json
它使用inline
和Mirror
Map[String, T]
或Map[T, String]
map
/ contramap
String
编解码器,以便翻译是嵌套的,也不需要区分字段它仅适用于由case object
的枚举,并且如果任何case
存储了一些嵌套数据,它将失败 - 因为它使用带有鉴别器字段的标准派生过程或以嵌套类型命名的嵌套结构。
您可以导入stringEnumDecoder
和stringEnumEncoder
并给出它们,尽管我更愿意手动添加它们,因为它们更像是一个例外而不是规则。
尝试通过字符串构造Encoder
和Decoder
实例
import io.circe.*
import io.circe.parser.*
import io.circe.syntax.*
enum OrderType:
case BUY
case SELL
given Encoder[OrderType] = (a: OrderType) => a.toString.asJson
given Decoder[OrderType] = (c: HCursor) =>
Decoder.decodeString(c).flatMap { str =>
Try(OrderType.valueOf(str)).toEither.leftMap { _ =>
DecodingFailure(s"no enum value matched for $str", List(CursorOp.Field(str)))
}
}
有一个库可以帮助您: circe-tagged-adt-codec-scala3
有了这个你的代码看起来像
import org.latestbit.circe.adt.codec._
enum OrderType derives JsonTaggedAdt.Encoder:
case BUY
case SELL
enum MagicType derives JsonTaggedAdt.Encoder:
case FIRE
case WATER
case EARTH
case WIND
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.