Spary JSON数据类型的通用函数,引发类型不匹配错误

时间:2018-11-26 19:36:48

标签: scala generics spray-json

我正在使用spray-json,并且需要解析给定的请求正文(PATCH, POST),请求正文属性可以具有以下由Either[Unit.type, Option[A]]表示的可能性

  • value未给出Left[Unit.type]
  • value=nullRight[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类型。在这里,我只关心JsStringJsNumber

上面的代码行给出了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)

1 个答案:

答案 0 :(得分:2)

您的示例仍然不太清楚,因为它没有显示parse的作用,并且实际上看起来与其他代码矛盾:特别是您在以下示例中将通用参数S指定为JsString readFieldWithValidation[JsString, String],但是在当前(失败的)readFieldWithValidation实现的情况下,您的parse自变量可能是String => String类型,因为jsString.valueString。 / 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更具体的错误信息,但这取决于您。