json4 秒部分解析 json

json4s parse json partially

我有一个 json 模型,其中某些属性的内容取决于另一个属性。像这样:

"paymentMethod": "CREDIT_CARD",
"metaData": {
    "cardType": "VISA",
    "panPrefix": "",
    "panSuffix": "",
    "cardHolder": "",
    "expiryDate": ""
}

因此,当 paymentMethod 等于 CREDIT_CARD 时,metadata 对象将包含所描述的属性。如果是其他支付方式,会有不同的元数据。

我想以面向未来的方式处理这种情况。我想要做的是不立即解析 metadata 字段,而是以某种方式保留它 "unparsed" 直到我解析了 paymentMethod 字段。然后我会获取元数据并应用适当的解析方法。

但是我不知道对于此类 "late parsed" 属性的 Scala class 字段使用哪种类型。 StringJsonInputJObject都试过了,都不合适(要么不编译,要么解析不出来)。我可以使用哪种类型的任何想法?或者,换句话说:

case class CreditCardMetadata(
  cardType: String,
  panPrefix: String,
  panSuffix: String,
  cardHolder: String,
  expiryDate: String)

case class PaypalMetadata(...) // etc.

case class PaymentGatewayResponse(
  paymentMethod: String,
  metadata: ???)

您可以使用 Map[String, String]。 它将包含您可能需要的任何内容。

您可以创建一个 CustomSerializer 来直接解析元数据。像 :

case class PaymentResponse(payment: Payment, otherField: String)

sealed trait Payment
case class CreditCardPayment(cardType: String, expiryDate: String) extends Payment
case class PayPalPayment(email: String) extends Payment

object PaymentResponseSerializer extends CustomSerializer[PaymentResponse]( format => ( 
  {
    case JObject(List(
           JField("paymentMethod", JString(method)),
           JField("metaData", metadata),
           JField("otherField", JString(otherField))
         )) =>
      implicit val formats = DefaultFormats
      val payment = method match {
        case "CREDIT_CARD" => metadata.extract[CreditCardPayment]
        case "PAYPAL" => metadata.extract[PayPalPayment]
      }
      PaymentResponse(payment, otherField)
  },
  { case _ => throw new UnsupportedOperationException } // no serialization to json
))

可用作:

implicit val formats = DefaultFormats + PaymentResponseSerializer

val json = parse("""
      {
        "paymentMethod": "CREDIT_CARD",
        "metaData": {
            "cardType": "VISA",
            "expiryDate": "2015"
        },
        "otherField": "hello"
      }
      """)

val json2 = parse("""
    {
      "paymentMethod": "PAYPAL",
      "metaData": {
          "email": "foo@bar.com"
      },
      "otherField": "world"        
    }
    """)

val cc =  json.extract[PaymentResponse]
// PaymentResponse(CreditCardPayment(VISA,2015),hello)
val pp =  json2.extract[PaymentResponse]
// PaymentResponse(PayPalPayment(foo@bar.com),world)

Peter Neyens 的回答启发了我实施自己的解决方案。它不像他的那样通用,但就我而言,我需要一些非常简单和临时的东西。这是我所做的:

可以定义一个案例class,未知类型的字段由JObject类型表示。像这样:

case class PaymentGatewayResponse(
  default: Boolean,
  paymentMethod: String,
  visibleForCustomer: Boolean,
  active: Boolean,
  metaData: JObject)

当这样的json被解析成这样的caseclass时,这个字段并没有立即被解析,而是包含了所有必要的信息。然后可以在单独的步骤中解析它:

case class CreditCardMetadata(
  cardType: String,
  cardObfuscatedNumber: String,      
  cardHolder: String,
  expiryDate: String)

val response: PaymentGatewayResponse = doRequest(...)
response.map { r =>
      r.paymentMethod match {
        case "CREDIT_CARD" => r.metaData.extract[CreditCardMetadata]
        case unsupportedType: String => throw new UnsupportedPaymentMethodException(unsupportedType)
      }
    }