Circe 解码器 - 如果失败则回退到另一个解码器

Circe Decoder - Fallback to another decoder if fails

我正在使用 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 添加多个解析器并对其进行处理,但是,我想知道 Circe 中是否有一种方法可以将多个解码器链接起来一个接一个地尝试直到成功或者到达链的末端?

您可以使用 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" """))

请注意,EncoderDecoderDecoder#or returns 和 Decoder 以来已分开,这不适用于组合 class(即 Encoder[DateTime] with Decoder[DateTime])。

此外,DateTime.parse 调用已用 Decoder#emapTry 包装,因为 or 组合器(通常所有 Decoder 组合器)期望处理 Either 值,没有例外。