Декодирование класса case с `require` в теле класса с помощью circe вызывает исключение вместо возврата ‘Left’

#json #scala #circe

#json #scala #circe

Вопрос:

У меня есть класс case с некоторыми логическими ограничениями, реализованными как require s в теле класса case. При попытке декодировать этот класс case из JSON, представляющего синтаксически правильный, но логически недопустимый экземпляр, исключение фактически генерируется в вызывающем потоке, а не возвращается как Left from 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")
}
 

Возможно ли вернуть это исключение как Left from decode без выбрасывания? Любые идеи / предложения приветствуются…

TIA

M.

Комментарии:

1. Ну require , выдает исключения, так что это все. Также на практике никто его не использует. — Для этого варианта использования было бы лучше использовать уточненный тип.

Ответ №1:

Используйте refined для автоматического получения этих кодеков с ограничениями.

Или, если вы хотите проверить 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))
  )
}