Circe Scala - 编码和解码 Map[] 和案例类

Posted

技术标签:

【中文标题】Circe Scala - 编码和解码 Map[] 和案例类【英文标题】:Circe Scala - Encode & Decode Map[] and case classes 【发布时间】:2019-03-13 10:46:49 【问题描述】:

我正在尝试为我拥有的案例类创建编码器和解码器:

case class Road(id: String, light: RoadLight, names: Map[String, String])

RoadLight 是一个带有枚举的 java 类。

public enum RoadLight 
red,yellow,green

我尝试进行半自动编码和解码:制作隐式编码器和解码器。 我从 Map[String,String] 类型开始:

implicit val namesDecoder: Decoder[Map[String, String]] = deriveDecoder[Map[String, String]]
implicit val namesEncoder: Encoder[Map[String, String]] = deriveEncoder[Map[String, String]]

但我确实得到了他们两个的错误!

1: 找不到 io.circe.generic.decoding.DerivedDecoder[A] 类型的惰性隐式值

2: 错误:没有足够的参数用于方法 derivedDecoder: (implicit decode: shapeless.Lazy[io.circe.generic.decoding.DerivedDecoder[A]])io.circe.Decoder[A ]。 未指定值参数解码。 隐式验证名称解码器:解码器[地图[字符串,字符串]]=派生解码器

我已经按照书本做了所有事情,无法理解哪里出了问题。我什至没有尝试解析案例类,只有地图,即使这样也行不通。

有什么想法吗?谢谢!

【问题讨论】:

【参考方案1】:

Scaladoc 说

/**
 * Semi-automatic codec derivation.
 *
 * This object provides helpers for creating [[io.circe.Decoder]] and [[io.circe.ObjectEncoder]]
 * instances for case classes, "incomplete" case classes, sealed trait hierarchies, etc.

Map 不是密封特征层次结构的案例类或元素。

https://github.com/circe/circe/issues/216

Encode Map[String, MyCaseClass] into Seq[String, String] using circe

Circe and Scala's Enumeration type

【讨论】:

【参考方案2】:

circe-generic 不会为 java 枚举创建编解码器,仅适用于 scala 乘积和总和类型。但是为RoadLight 滚动你自己并不难。一旦你有了它,你就会得到地图。

下面的代码有效:

object RoadLightCodecs 
  implicit val decRl: Decoder[RoadLight] = Decoder.decodeString.emap 
    case "red" => Right(RoadLight.Red)
    case "yellow" => Right(RoadLight.Yellow)
    case "green" => Right(RoadLight.Green)
    case s => Left(s"Unrecognised traffic light $s")
  

  implicit val encRl: Encoder[RoadLight] = Encoder.encodeString.contramap(_.toString)


  implicit val decodeMap = Decoder.decodeMap[String, RoadLight]
  implicit val encodeMap = Encoder.encodeMap[String, RoadLight]

所以我们所做的是为基本类型制作编解码器,然后使用它们来构建更大的地图编解码器。

现在据我所知,没有任何库可以为 java 枚举自动执行此操作,尽管理论上应该可以编写一个。但是在基本编解码器上使用组合器来构建更复杂的编解码器效果很好并且可以很好地扩展。

编辑:我玩过自动派生 java 枚举编解码器,你可以几乎做到这一点:

  def decodeEnum[E <: Enum[E]](values: Array[E]): Decoder[E] = Decoder.decodeString.emap  str =>
    values.find(_.toString.toLowerCase == str)
      .fold[Either[String, E]](Left(s"Value $str does not map correctly"))(Right(_))
  

  def encodeEnum[E <: Enum[E]]: Encoder[E] =
    Encoder.encodeString.contramap(_.toString.toLowerCase)

  implicit val roadLightDecoder = decodeEnum[RoadLight](RoadLight.values())
  implicit val roadLightEncoder = encodeEnum[RoadLight]

所以 encodeEnum 可以是自动的(你可以让它隐含而不是最后的 val)但是需要给解码器提供值(我认为没有办法从类型中自动获取),所以你需要通过创建编解码器时的那些。

【讨论】:

以上是关于Circe Scala - 编码和解码 Map[] 和案例类的主要内容,如果未能解决你的问题,请参考以下文章

带有案例类的抽象类的scala circe编码器/解码器

如何在circe中编码/解码json的时间戳?

用于编码/解码 arity 0 的密封特征实例的 Circe 实例?

Scala使用circe将None编码为NaN json值

从 Scala Circe 中的 Map 列表创建一个 json

尝试为ADT编写Circe编码器或解码器时遇到错误