Scala circe DeriveUnwrapped 值类对丢失的成员不起作用

问题描述

我正在尝试解码一个字符串值类,如果字符串为空,我需要得到一个 None 否则一个 Some。我有以下菊石脚本示例:

import $ivy.`io.circe::circe-generic:0.13.0`,io.circe._,io.circe.generic.auto._,io.circe.syntax._,io.circe.generic.JsonCodec
import $ivy.`io.circe::circe-generic-extras:0.13.0`,io.circe.generic.extras._,io.circe.generic.extras.semiauto._
import $ivy.`io.circe::circe-parser:0.13.0`,io.circe.parser._

final case class CustomString(value: Option[String]) extends AnyVal
final case class TestString(name: CustomString)

implicit val customStringDecoder: Decoder[CustomString] =
    deriveUnwrappedDecoder[CustomString].map(ss => CustomString(ss.value.flatMap(s => Option.when(s.nonEmpty)(s))))

implicit val customStringEncoder: Encoder[CustomString] = deriveUnwrappedEncoder[CustomString]
implicit val testStringCodec: Codec[TestString] = io.circe.generic.semiauto.deriveCodec

val testString = TestString(CustomString(Some("test")))
val emptyTestString = TestString(CustomString(Some("")))
val noneTestString = TestString(CustomString(None))
val nullJson = """{"name":null}"""
val emptyJson = """{}"""

assert(testString.asJson.noSpaces == """{"name":"test"}""")
assert(emptyTestString.asJson.noSpaces == """{"name":""}""")
assert(noneTestString.asJson.noSpaces == nullJson)
assert(noneTestString.asJson.dropNullValues.noSpaces == emptyJson)

assert(decode[TestString](nullJson).exists(_ == noneTestString)) // this passes
assert(decode[TestString](emptyJson).exists(_ == noneTestString)) // this fails

解决方法

据我所知,这没有自动化功能。

我会直接使用circe cursor api来解决:

import $ivy.`io.circe::circe-generic:0.13.0`,io.circe._,io.circe.generic.auto._,io.circe.syntax._,io.circe.generic.JsonCodec
import $ivy.`io.circe::circe-generic-extras:0.13.0`,io.circe.generic.extras._,io.circe.generic.extras.semiauto._
import $ivy.`io.circe::circe-parser:0.13.0`,io.circe.parser._

final case class CustomString(value: Option[String]) extends AnyVal
final case class TestString(name: CustomString)

implicit val testStringDecoder: Decoder[TestString] =  (c: HCursor) =>{
     c.downField("name").as[Option[String]].map(string => TestString(CustomString(string)))
}

implicit val customStringEncoder: Encoder[CustomString] = deriveUnwrappedEncoder[CustomString]
implicit val testStringCodec: Encoder[TestString] = io.circe.generic.semiauto.deriveEncoder

val testString = TestString(CustomString(Some("test")))
val emptyTestString = TestString(CustomString(Some("")))
val noneTestString = TestString(CustomString(None))
val nullJson = """{"name":null}"""
val emptyJson = """{}"""

assert(testString.asJson.noSpaces == """{"name":"test"}""")
assert(emptyTestString.asJson.noSpaces == """{"name":""}""")
assert(noneTestString.asJson.noSpaces == nullJson)
assert(noneTestString.asJson.dropNullValues.noSpaces == emptyJson)

assert(decode[TestString](nullJson).exists(_ == noneTestString)) // this passes
assert(decode[TestString](emptyJson).exists(_ == noneTestString)) // this fails
,

您可以交替使用不同的编码,以便更清晰地表达意图,并且当您需要使用字符串时,您不需要在嵌套的 case 类上进行模式匹配。

final case class TestString(name: Option[NonEmptyString])
object TestString {
  implicit val decoder: Decoder[TestString] = deriveDecoder
}

sealed trait NonEmptyString {
  def value: String
}
object NonEmptyString {
  private case class NonEmptyStringImpl(value: String) extends NonEmptyString

  def apply(value: String): Either[NonEmptyStringRequiredException,NonEmptyString] = {
    if (value.nonEmpty) Right(NonEmptyStringImpl(value))
    else Left(new NonEmptyStringRequiredException)
  }

  implicit val encoder: Encoder[NonEmptyString] = Encoder[String].contramap(_.value)

  implicit val decoder: Decoder[Option[NonEmptyString]] = Decoder.withReattempt {
    case h: HCursor =>
      if (h.value.isNull) Right(None)
      else h.value.asString match {
        case Some(string) => Right(apply(string).toOption)
        case None => Left(DecodingFailure("Not a string.",h.history))
      }
    case _: FailedCursor =>
      Right(None)
  }
}
,

现有的答案并不能解决问题,所以这里是解决方案。如果您不想使用 refined,您可以像这样定义解码器:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<div class="container-fluid">
  <div class="row">
    <div class="col">
      <div class="card mb-3">
        <div class="card-header justify-content-between d-flex align-items-center text-white bg-info">
          <h4>Wildlife</h4>
          <a class="category_carat"><i class="fa fa-caret-right"></i></a>
        </div>
        <div class="card-body">
          <div class="row">
            <div class="col-md order-2 order-md-1 mt-4 mt-md-0">
              <div class="collapse categories-show">
                <div class="row">
                  <div class="col-md order-2 order-md-1 mt-4 mt-md-0">
                    <h5 class="card-title"> <a href="#subCategories-show" class="subcategory_carat" data-toggle="collapse"><i class="fa fa-plus-circle"></i></a> Mammal Biodiversity</h5>
                  </div>
                </div>
                <div class="row ml-4">
                  <div class="col">
                    <div class="collapse subCategories-show">
                      <h5><a href="#table-show" class="table_carat" data-toggle="collapse"><i class="fa fa-plus-circle"></i></a> Brook Trout</h5>
                    </div>
                    <div class="collapse table-show">
                      something inside should be collapsed

                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

但是,如果您使用精炼类型(我推荐),使用 implicit val customStringDecoder: Decoder[CustomString] = Decoder .decodeOption(deriveUnwrappedDecoder[CustomString]) .map(ssOpt => CustomString(ssOpt.flatMap(_.value.flatMap(s => Option.when(s.nonEmpty)(s))))) 可以更简单,并且具有更好的类型安全性的好处(即您知道您的 String 不为空)。这是用于测试的完整菊石脚本:

circe-refined

相关问答

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