无法为参数编码器找到隐式值: io.circe.Encoder [com.sweetsoft.SapHealth]

4
我有下面这段代码,无法编译:
import java.time.Instant

import io.circe.{Decoder, Encoder}
import io.circe.generic.auto._
import io.circe.syntax._

trait SapHealth {}

case class SapHealthRejected(reason: String) extends SapHealth

case class SapHealthAccepted(sapId: String, requestedAt: Long) extends SapHealth


object SapHealth {

  private val build: SapHealth = SapHealthAccepted(SapmockActor.system.name, Instant.now().getEpochSecond)

  val create: String = build.asJson.noSpaces

  implicit val encodeFieldType: Encoder[SapHealthAccepted] =
    Encoder.forProduct2("sap-id", "requested_at")(SapHealthAccepted.unapply(_).get)

  implicit val decodeFieldType: Decoder[SapHealthAccepted] =
    Decoder.forProduct2("sap-id", "requested_at")(SapHealthAccepted.apply)

}

编译器报错:
could not find implicit value for parameter encoder: io.circe.Encoder[com.sweetsoft.SapHealth]
[error]   val create: String = build.asJson.noSpaces

我错过了什么?
3个回答

5
你已经将build转换为SapHealth,但你没有为SapHealth提供Encoder实例(只有SapHealthAccepted),而且circe-generic无法派生出该实例,因为你没有封闭特质层次结构。
最直接的解决方案是添加sealed
import io.circe.{Decoder, Encoder}
import io.circe.generic.auto._
import io.circe.syntax._

sealed trait SapHealth {}
case class SapHealthRejected(reason: String) extends SapHealth
case class SapHealthAccepted(sapId: String, requestedAt: Long) extends SapHealth

object SapHealth {
  implicit val encodeFieldType: Encoder[SapHealthAccepted] =
    Encoder.forProduct2("sap-id", "requested_at")(SapHealthAccepted.unapply(_).get)

  implicit val decodeFieldType: Decoder[SapHealthAccepted] =
    Decoder.forProduct2("sap-id", "requested_at")(SapHealthAccepted.apply)

  private val build: SapHealth = SapHealthAccepted("foo", 123L)

  val create: String = build.asJson.noSpaces
}

请注意,您还需要重新排列定义以避免由于初始化顺序而遇到空指针异常(如果您将 create 放在 encodeFieldType 之前,则派生的 SapHealth 编码器将尝试在其初始化之前使用 encodeFieldType)。通过上述重新排列,这个问题得到了解决。
scala> SapHealth.create
res2: String = {"SapHealthAccepted":{"sap-id":"foo","requested_at":123}}

请注意,派生的SapHealth编码器正在使用您的自定义SapHealthAccepted编码器,我假设这正是您想要的。

sealed 关键字的含义是什么? - softshipper
3
sealed关键字使得除了当前编译单元外无法扩展SapHealth。circe-generic中的泛型派生依赖于Shapeless对ADT的通用表示方式,而Shapeless并没有为非密封的trait继承层次结构提供通用表示方式。 - Travis Brown

2

应该将特质SapHealth封装。

最初的回答

3
抱歉,但我认为这个答案并不太有帮助——它没有解释为什么在这种情况下密封很重要,为什么 encodeFieldType 没有起作用,初始化顺序的问题等等。也许提问者能从中得到解决方案,也可能不行,但它肯定不太可能对其他人有所帮助。 - Travis Brown
@TravisBrown 你比我快了。在你回答之后,我没有看到更新我的原因。祝好运。 - Dmytro Mitin
不太明白“这是一个占位符答案,等我写好了再来”的意思,但 ¯_(ツ)_/¯。 :) - Travis Brown

1

仅对于 case class,您可以使用 deriveEncoder / deriveDecoder。但是,对于复杂的类,您应该(我认为)为参数的类指定它。

https://circe.github.io/circe/codecs/semiauto-derivation.html

import io.circe._, io.circe.generic.semiauto._

case class Foo(a: Int, b: String, c: Boolean)

implicit val fooDecoder: Decoder[Foo] = deriveDecoder[Foo]
implicit val fooEncoder: Encoder[Foo] = deriveEncoder[Foo]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接