2017-02-17 19 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% процессора на обработку 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 методов, которые просто чувствуют себя как лучший способ нарубить проблему для меня (от верхней части моей головы, я не уверен, какой подход будет меньше распределений). Это, вероятно, в основном вопрос вкуса.

+0

Да, я подумал о том, чтобы вернуться прямо к 'CreditCard' из' PaymentType'. Моя первоначальная реакция заключалась не в том, чтобы сделать это, потому что он делает родительский класс осведомленным о своих детях, что не является хорошей практикой. Но поскольку это все запечатанные типы, это, вероятно, не так уж плохо. И это, безусловно, лучше, чем в настоящее время альтернатива. – Haspemulator

+0

@ Haspemulator Да, поскольку вы уже застряли с '.or (CreditCard.decoder)' Я не вижу большой разницы между ними в этом отношении. –

+0

Правильно, то, что я уже разместил, содержало такую ​​обратную связь в любом случае. Тем не менее до этого у меня не было резервной копии вообще, и она просто потерпела неудачу во время выполнения с 'MatchError', когда были встречены некоторые строки' CreditCard'. – Haspemulator