Как создать пользовательский декодер в Circe, который анализирует значения времени - PullRequest
2 голосов
/ 26 апреля 2019

Я пытаюсь декодировать строку вида «5m» или «5s» или «5ms» в объекты типа FiniteDuration, которые соответственно составляют 5 минут, 5 секунд, 5 миллисекунд.

Я пытаюсь создать собственный декодер и кодер для проекта, который включает класс FiniteDuration.Кодировщик не проблема, поскольку он просто читает поля класса FiniteDuration и генерирует строку.Тем не менее, я испытываю трудности при написании декодера, и мне интересно, возможно ли вообще то, что я делаю.

FiniteDuration - это класс с конструктором следующим образом: FiniteDuration (length: Long, unit: TimeUnit).Scala поставляется с некоторым удобным синтаксическим сахаром, так что класс можно вызывать с использованием обозначений 5.minutes, 5.seconds или 5.milliseconds.В этом случае Scala позаботится о создании класса FiniteDuration для вас.

Идея состоит в том, чтобы преобразовать этот класс FiniteDuration в строку типа «5m», «5s» или «5ms», что легче дляглаза.

  implicit val d2json: Encoder[FiniteDuration] = new Encoder[FiniteDuration] {
    override def apply(a: FiniteDuration): Json = ???
  }

  implicit val json2d: Decoder[FiniteDuration] = new Decoder[FiniteDuration] {
    override def apply(c: HCursor): Decoder.Result[FiniteDuration] = ???
  }

Кодер У меня не должно быть проблем с написанием.Декодер более хитрый.Я не уверен, что делать, так как метод apply ожидает ввод типа HCursor.

Ответы [ 2 ]

3 голосов
/ 26 апреля 2019

Вот базовая реализация, которая работает (может потребоваться настройка в зависимости от того, как вы кодируете FiniteDuration.

По сути, вам нужно получить значение курсора как String, разделить эту строку на длительность и период и попытаться преобразовать обе части в Long и TimeUnit соответственно (потому что FiniteDuration конструктор принимает их как параметры).

Обратите внимание, что эти преобразования должны возвращать Either[DecodingFailure, _] для выравнивания с типом возврата cursor.as[_], чтобы вы могли использовать их для простоты понимания.

Я использовал неявные методы расширения для этих преобразований, потому что я считаю их удобными, но вы могли бы написать основные функции.

implicit class StringExtended(str: String) {
    def toLongE: Either[DecodingFailure, Long] = {
      Try(str.toLong).toOption match {
        case Some(value) => Right(value)
        case None => Left(DecodingFailure("Couldn't convert String to Long", List.empty))
      }
    }

    def toTimeUnitE: Either[DecodingFailure, TimeUnit] = str match {
      case "ms" => Right(TimeUnit.MILLISECONDS)
      case "m" => Right(TimeUnit.MINUTES)
      // add other cases in the same manner
      case _ => Left(DecodingFailure("Couldn't decode time unit", List.empty))
    }
}

implicit val decoder: Decoder[FiniteDuration] = (c: HCursor) =>
  for {
    durationString <- c.as[String]
    duration <- durationString.takeWhile(_.isDigit).toLongE
    period = durationString.dropWhile(_.isDigit)
    timeUnit <- period.toTimeUnitE
  } yield {
    FiniteDuration(duration, timeUnit)
  }

println(decode[FiniteDuration]("5ms".asJson.toString)) 
// Right(5 milliseconds)
2 голосов
/ 26 апреля 2019

Полагаю, вы хотите, чтобы ваш парсер соответствовал HOCON?Затем вы можете просто повторно использовать или скопировать синтаксический анализатор, который используется в библиотеке com.typesafe.config.Вам нужен метод

public static long parseDuration(String input, ConfigOrigin originForException, String pathForException)

...