问题描述
我正在尝试解码一个字符串值类,如果字符串为空,我需要得到一个 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