Circe: efficiently decodes multi-level ADT

I want to decode the following ADT with Circe:

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
}

As you can see, there is a parent type PaymentTypethat has some direct descendants and another family of closed qualities CreditCard. Now decoding is performed as follows:

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)
}

I do not like the decoder PaymentType, especially the fact that I need to create an additional and unnecessary instance DecodingFailurein a completely normal scenario when you are faced with a credit card-based payment type. We have already spent 99.9% of the processor processing JSON, and it just doesn't look right. Either it's a bad ADT design, or there should be a way in Circe to deal with this better. Any ideas?

+4
1

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, , ( , , ). , , . .

+3

Source: https://habr.com/ru/post/1670078/


All Articles