我正在使用spray-json,并且需要解析给定的请求正文(PATCH, POST
),请求正文属性可以具有以下由Either[Unit.type, Option[A]]
表示的可能性
value
未给出Left[Unit.type]
value=null
空Right[None]
value=XXX
提供了一些值Right[Some(value)]
使用上述可能性,我需要从请求主体创建一个实体。解析时,我需要使用一些业务逻辑(字符串长度,整数范围...)来验证每个字段。
我具有以下用于业务逻辑验证的功能。
def validateValue[T](fieldName: String,
maybeValue: Try[T],
businessValidation: T => Boolean): Option[T] = {
maybeValue match {
case Success(value) if businessValidation(value) => Some(value)
case _ => None
}
}
类似地,另一个功能readFieldWithValidation
,在这里我将基于输入类型来解析每个属性并应用业务验证。
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)(
parse: S => T
): Option[T] = {
fields.get(fieldName) match {
case None => None
case Some(jsValue) =>
jsValue match {
case jsString: JsString =>
validateValue(fieldName, Try(parse(jsString.value)), businessValidation)
case JsNumber(jsNumber) =>
validateValue(fieldName, Try(parse(jsNumber.intValue)), businessValidation)
case _ => None
}
}
}
我有S ( Source )
和T ( Target )
,用于给定JsValue
返回T
类型。在这里,我只关心JsString
和JsNumber
。
上面的代码行给出了type mismatch
错误,
<console>:112: error: type mismatch; found : jsString.value.type (with underlying type String) required: S validateValue(fieldName, Try(parse(jsString.value)), businessValidation) ^ <console>:114: error: type mismatch; found : Int required: S validateValue(fieldName, Try(parse(jsNumber.intValue)), businessValidation)
有人可以帮助我克服这个错误吗?
这就是我可以使用上述功能的方式
val attributes = Map("String" -> JsString("ThisIsString"), "Int" -> JsNumber(23))
def stringLengthConstraint(min: Int, max: Int)(value: String) = value.length > min && value.length < max
readFieldWithValidation[JsString, String](attributes, "String", stringLengthConstraint(1, 10))(_.toString)
答案 0 :(得分:2)
您的示例仍然不太清楚,因为它没有显示parse
的作用,并且实际上看起来与其他代码矛盾:特别是您在以下示例中将通用参数S
指定为JsString
readFieldWithValidation[JsString, String]
,但是在当前(失败的)readFieldWithValidation
实现的情况下,您的parse
自变量可能是String => String
类型,因为jsString.value
是String
。 / p>
无论如何,这里有一段代码似乎实现了一些与您想要的东西足够接近的东西:
trait JsValueExtractor[T] {
def getValue(jsValue: JsValue): Option[T]
}
object JsValueExtractor {
implicit val decimalExtractor = new JsValueExtractor[BigDecimal] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber)
case _ => None
}
}
implicit val intExtractor = new JsValueExtractor[Int] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber.intValue)
case _ => None
}
}
implicit val doubleExtractor = new JsValueExtractor[Double] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber.doubleValue)
case _ => None
}
}
implicit val stringExtractor = new JsValueExtractor[String] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsString(string) => Some(string)
case _ => None
}
}
}
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)(parse: S => T)(implicit valueExtractor: JsValueExtractor[S]) = {
fields.get(fieldName)
.flatMap(jsValue => valueExtractor.getValue(jsValue))
.flatMap(rawValue => Try(parse(rawValue)).toOption)
.filter(businessValidation)
}
和用法示例:
def test(): Unit = {
val attributes = Map("String" -> JsString("ThisIsString"), "Int" -> JsNumber(23))
def stringLengthConstraint(min: Int, max: Int)(value: String) = value.length > min && value.length < max
val value = readFieldWithValidation[String, String](attributes, "String", stringLengthConstraint(1, 10))(identity)
println(value)
}
您当前的代码使用Option[T]
作为返回类型。如果我使用的是这样的代码,我可能会添加一些错误记录和/或处理,以防代码包含错误,而attributes
确实包含键fieldName
的值,但有些不同,意外类型(例如JsNumber
而不是JsString
)。
从您的评论中尚不清楚您是否对我的原始答案感到满意或想要添加一些错误处理。如果您要报告类型不匹配错误,并且由于您使用的是猫,那么ValidatedNel
之类的选择是显而易见的选择:
type ValidationResult[A] = ValidatedNel[String, A]
trait JsValueExtractor[T] {
def getValue(jsValue: JsValue, fieldName: String): ValidationResult[T]
}
object JsValueExtractor {
implicit val decimalExtractor = new JsValueExtractor[BigDecimal] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[BigDecimal] = jsValue match {
case JsNumber(jsNumber) => jsNumber.validNel
case _ => s"Field '$fieldName' is expected to be decimal".invalidNel
}
}
implicit val intExtractor = new JsValueExtractor[Int] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[Int] = jsValue match {
case JsNumber(jsNumber) => Try(jsNumber.toIntExact) match {
case scala.util.Success(intValue) => intValue.validNel
case scala.util.Failure(e) => s"Field $fieldName is expected to be int".invalidNel
}
case _ => s"Field '$fieldName' is expected to be int".invalidNel
}
}
implicit val doubleExtractor = new JsValueExtractor[Double] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[Double] = jsValue match {
case JsNumber(jsNumber) => jsNumber.doubleValue.validNel
case _ => s"Field '$fieldName' is expected to be double".invalidNel
}
}
implicit val stringExtractor = new JsValueExtractor[String] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[String] = jsValue match {
case JsString(string) => string.validNel
case _ => s"Field '$fieldName' is expected to be string".invalidNel
}
}
}
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)
(parse: S => T)(implicit valueExtractor: JsValueExtractor[S]): ValidationResult[T] = {
fields.get(fieldName) match {
case None => s"Field '$fieldName' is required".invalidNel
case Some(jsValue) => valueExtractor.getValue(jsValue, fieldName)
.andThen(rawValue => Try(parse(rawValue).validNel).getOrElse("".invalidNel))
.andThen(parsedValue => if (businessValidation(parsedValue)) parsedValue.validNel else s"Business validation for field '$fieldName' has failed".invalidNel)
}
}
与test
示例相同。可能在您的真实代码中,您想使用比String
更具体的错误信息,但这取决于您。