使用 circe 在类主体中解码带有 `require` 的 case 类会引发异常而不是返回 `Left`

问题描述

我有一个案例类,其中一些逻辑约束在案例类主体中实现为 require。当尝试从表示语法正确但逻辑上无效的实例的 JSON 中解码此 case 类时,异常实际上是在调用线程上抛出的,而不是作为 Left 中的 decode 返回。

重现代码片段:

case class Foo(bar: String) {
  require(bar.length < 5)
}

object Sandbox extends App {
  import io.circe.generic.auto._
  import io.circe.parser._

  val foo1 = decode[Foo](""" {"bar":"abc"} """)
  println(s"foo1: $foo1")

  //expected: Left(IllegalArgumentException("requirement failed"))
  //actual: throws IllegalArgumentException("requirement failed")
  val foo2 = decode[Foo](""" {"bar":"abcdefg"} """) 
  println(s"foo2: $foo2")
}

是否可以在不抛出的情况下将此异常作为 Leftdecode 返回?欢迎任何想法/建议...

TIA

M.

解决方法

使用精炼自动派生这些编解码器并带有约束。

或者,如果您想验证 JSON 而不是构造函数,您可以随时调整编解码器,如

case class Foo(bar: String)
object Foo {
  implicit val decoder: Decoder[Foo] = deriveDecoder[Foo].emapTry(foo =>
    Try(require(foo.bar.length < 5))
  )
}

有时你也可以使用中间类型进行派生和映射:

case class Foo(bar: String) {
  require(bar.length < 5)
}
object Foo {
  // allows usage of derivation,especially if you would derive several typeclasses
  // and then adjust their behavior
  private case class FooHelper(bar: String)

  implicit val decoder: Decoder[Foo] = deriveDecoder[FooHelper].emapTry(helper =>
    Try(Foo(helper.foo))
  )
}

总的来说,我建议不要使用 require,尤其是在构造函数中,而是使用智能构造函数。

sealed abstract case class Foo private (bar: String)
object Foo {

  def parse(bar: String): Either[String,Foo] =
    if (bar.length < 5) Right(new Foo(bar) {})
    else Left(s"Invalid bar value: $bar")

  def parseUnsafe(bar: String): Foo =
   parse(bar).fold(error => throw new Exception(error),foo => foo)

  private case class FooHelper(bar: String)

  implicit val decoder: Decoder[Foo] = deriveDecoder[FooHelper].emapTry(helper =>
    Try(parseUsafe(helper.foo))
  )
}

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...