Универсальная функция для типов данных Spary JSON, выдает ошибку несоответствия типов - PullRequest
0 голосов
/ 26 ноября 2018

Я использую 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)

1 Ответ

0 голосов
/ 28 ноября 2018

Ваш пример все еще не совсем понятен, потому что он не показывает роль parse и на самом деле выглядит противоречащим другому коду: в частности, вы задаете универсальный параметр S как JsString в readFieldWithValidation[JsString, String], но с учетом текущего(borken) readFieldWithValidation реализация Ваш аргумент parse, вероятно, будет иметь тип String => String, потому что jsString.value равен String.

В любом случае, здесь есть фрагмент кода, который, кажется, реализует что-то, чтонадеюсь, достаточно близко к тому, что вы хотите:

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 для ошибок, но это ваше дело.

...