Json Circe:高效解码多级ADT
我想用Circe解码以下ADT:Json Circe:高效解码多级ADT,json,scala,algebraic-data-types,circe,Json,Scala,Algebraic Data Types,Circe,我想用Circe解码以下ADT: sealed trait PaymentType object PaymentType extends EnumEncoder[PaymentType] { case object DebitCard extends PaymentType case object Check extends PaymentType case object Cash extends PaymentType case object Mobile
sealed trait PaymentType
object PaymentType extends EnumEncoder[PaymentType] {
case object DebitCard extends PaymentType
case object Check extends PaymentType
case object Cash extends PaymentType
case object Mobile extends PaymentType
}
sealed trait CreditCard extends PaymentType
object CreditCard extends EnumEncoder[CreditCard] {
case object UNKNOWN_CREDIT_CARD extends CreditCard
case object NOT_ACCEPTED extends CreditCard
case object VISA extends CreditCard
case object MASTER_CARD extends CreditCard
case object DINERS_CLUB extends CreditCard
case object AMERICAN_EXPRESS extends CreditCard
case object DISCOVER_CARD extends CreditCard
}
如您所见,有一个父类型PaymentType
,它有一些直接继承者和另一个密封的trait家族信用卡
。现在解码是这样完成的:
object CreditCard {
implicit val decoder: Decoder[CreditCard] = Decoder.instance[CreditCard] {
_.as[String].map {
case "NOT_ACCEPTED" => NOT_ACCEPTED
case "VISA" => VISA
case "MASTER_CARD" => MASTER_CARD
case "DINERS_CLUB" => DINERS_CLUB
case "AMERICAN_EXPRESS" => AMERICAN_EXPRESS
case "DISCOVER_CARD" => DISCOVER_CARD
case _ => UNKNOWN_CREDIT_CARD
}
}
object PaymentType {
implicit val decoder: Decoder[PaymentType] = Decoder.instance[PaymentType] {
_.as[String].flatMap {
case "DebitCard" => Right(DebitCard)
case "Check" => Right(Check)
case "Cash" => Right(Cash)
case "Mobile" => Right(Mobile)
case _ => Left(DecodingFailure("", List()))
}
}.or(CreditCard.decoder.widen)
}
我不喜欢的是
PaymentType
解码器,尤其是在完全正常的情况下,当遇到基于信用卡的支付类型时,我需要创建一个额外且不必要的DecodingFailure
实例。我们已经在JSON处理上花费了99.9%的CPU,这看起来不太对劲。要么是糟糕的ADT设计,要么Circe应该有更好的方法来处理这个问题。有什么想法吗?您可以将回退到信用卡
解码器中的PaymentType
解码器案例中,这样可以避免失败:
implicit val decoder: Decoder[PaymentType] = Decoder.instance[PaymentType] { c =>
c.as[String].flatMap {
case "DebitCard" => Right(DebitCard)
case "Check" => Right(Check)
case "Cash" => Right(Cash)
case "Mobile" => Right(Mobile)
case _ => CreditCard.decoder(c)
}
}
不过,在这种情况下,我可能会将字符串解析分解成不同的方法:
sealed trait PaymentType
object PaymentType extends EnumEncoder[PaymentType] {
case object DebitCard extends PaymentType
case object Check extends PaymentType
case object Cash extends PaymentType
case object Mobile extends PaymentType
private val nameMapping = List(DebitCard, Check, Cash, Mobile).map(pt =>
pt.productPrefix -> pt
).toMap
def fromString(input: String): Option[PaymentType] = nameMapping.get(input)
}
sealed trait CreditCard extends PaymentType
object CreditCard extends EnumEncoder[CreditCard] {
case object UNKNOWN_CREDIT_CARD extends CreditCard
case object NOT_ACCEPTED extends CreditCard
case object VISA extends CreditCard
case object MASTER_CARD extends CreditCard
case object DINERS_CLUB extends CreditCard
case object AMERICAN_EXPRESS extends CreditCard
case object DISCOVER_CARD extends CreditCard
private val nameMapping = List(
NOT_ACCEPTED,
VISA,
MASTER_CARD,
DINERS_CLUB,
AMERICAN_EXPRESS,
DISCOVER_CARD
).map(pt => pt.productPrefix -> pt).toMap
def fromString(input: String): CreditCard =
nameMapping.getOrElse(input, UNKNOWN_CREDIT_CARD)
}
然后你可以用
fromString
方法编写解码器,这对我来说是解决问题的更好方法(我不知道哪种方法会涉及更少的分配)。不过,这在很大程度上可能是一个品味问题。是的,我考虑过从PaymentType
直接回到CreditCard
。我最初的反应是不这样做,因为这样做会让家长知道自己的孩子,这不是一个好的做法。但由于它都是密封式的,所以可能没那么糟糕。当然,这比目前使用的替代方案要好。@Haspemulator是的,因为您已经被或(CreditCard.decoder)
卡住了。在这方面,我看不出两者有什么大的区别。对,我已经发布的内容包含了这种反向耦合。然而在此之前,我根本没有回退,当遇到一些信用卡字符串时,它只是在运行时失败,出现MatchError
。