Circe Decoder - Откат к другому декодеру в случае сбоя - PullRequest
0 голосов
/ 23 октября 2018

Я использую Circe для операций json.Я добавил пользовательские кодеры и декодеры для обработки некоторых типов, таких как Joda Time.

При анализе DateTime я хочу разрешить передачу нескольких форматов.Например,dd-MM-yyyy'T'HH:mm:ss'Z' и dd-MM-yyyy'T'HH:mm:ss.SSS'Z'

Я определил свой декодер, как показано ниже:

val dateTimeFormat = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
val dateTimeFormatWithMillis = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
implicit val jodaDateTimeFormat: Encoder[DateTime] with Decoder[DateTime] = new Encoder[DateTime] with Decoder[DateTime] {
    override def apply(a: DateTime): Json = Encoder.encodeString(a.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"))

    override def apply(c: HCursor): Result[DateTime] = Decoder.decodeString.map { x =>
      DateTime.parse(x, dateTimeFormat)
    }.apply(c)
  }

Теперь, если я введу строку даты и времени, соответствующую dateTimeFormat, то декодирование будет работать, ноесли я передам дату и время в dateTimeFormatWithMillis, он не будет обработан.

Я знаю, что могу использовать DateTimeFormatterBuilder, чтобы добавить несколько парсеров и обработать их, однако мне было интересно, есть ли способ в Цирцее объединить несколько декодеров, чтобы они пытались один за другим, пока он не достигнет успеха или не достигнетконец цепи?

1 Ответ

0 голосов
/ 23 октября 2018

Вы можете использовать Decoder#or, чтобы объединить декодеры, чтобы попытаться использовать второй в случае сбоя первого.

Вот рабочий пример:

import org.joda.time.DateTime
import org.joda.time.format.{DateTimeFormat, DateTimeFormatter}
import io.circe.{Decoder, Encoder}
import io.circe.parser.decode
import scala.util.Try


val dateTimeFormat = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
val dateTimeFormatWithMillis = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")


/** Creates a decoder that decodes a [[DateTime]] using the provided format. */
def dateTimeFormatDecoder(format: DateTimeFormatter): Decoder[DateTime] =
  Decoder[String].emapTry(str => Try(DateTime.parse(str, format)))

/** [[Decoder]] for the first format (without milliseconds). */
val dateTimeWithoutMillisDecoder: Decoder[DateTime] =
  dateTimeFormatDecoder(dateTimeFormat)

/** [[Decoder]] for the second format (with milliseconds). */
val dateTimeWithMillisDecoder: Decoder[DateTime] =
  dateTimeFormatDecoder(dateTimeFormatWithMillis)

/** Encodes a [[DateTime]] using `Encoder[String].contramap(...)`, which is
  * perhaps a slightly more idiomatic version of 
  * `Encoder.encodeString(a.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"))` */
implicit val jodaDateTimeEncoder: Encoder[DateTime] =
  Encoder[String].contramap(_.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"))

implicit val jodaDateTimeDecoder: Decoder[DateTime] =
  dateTimeWithoutMillisDecoder or dateTimeWithMillisDecoder

println(decode[DateTime](""" "2001-02-03T04:05:06Z" """))
println(decode[DateTime](""" "2001-02-03T04:05:06.789Z" """))

Обратите внимание, что Encoder и Decoder были разделены, поскольку Decoder#or возвращает Decoder, который не будет работать с объединенным классом (т. Е. Encoder[DateTime] with Decoder[DateTime]).

Также,DateTime.parse вызовы были заключены в Decoder#emapTry, потому что комбинатор or (и вообще все комбинаторы Decoder) предполагает обработку значений Either, а не исключений.

...