2017-02-17 63 views
4

我想解碼與瑟茜以下ADT:瑟茜:解碼多層次的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 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,其中有一些直接的傳承者和另一個密封特質家庭CreditCard。現在,解碼像這樣做:

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額外和不必要的情況下,當人們遇到信用卡 - 基於付款方式。我們已經將99.9%的CPU用於JSON處理,而且看起來不正確。要麼ADT設計不好,要麼Circe更好地處理這個問題。有任何想法嗎?

回答

3

可以回退移動到CreditCard解碼器到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方法,這感覺就像拿刀砍問題給我一個更好的方法(從我的頭頂,我不是條款確定哪種方法會涉及e分配更少)。雖然這可能很大程度上是品味的問題。

+0

是的,我想直接從'PaymentType'回落到'CreditCard'。我最初的反應不是這樣做,因爲它讓父母階層意識到他們的孩子,這不是好習慣。但是因爲它們都是密封類型,所以可能並不是那麼糟糕。它當然比當前使用的替代方案更好。 – Haspemulator

+0

@Haspemulator是的,因爲你已經被'.or(CreditCard.decoder)'卡住了''在這方面我沒有看到兩者之間的巨大差異。 –

+0

沒錯,我已經發布了這樣的反向耦合。然而在此之前,我根本沒有後備功能,而且在遇到一些'CreditCard'字符串時,它在運行時失敗了'MatchError'。 – Haspemulator