简体   繁体   English

json4s部分解析json

[英]json4s parse json partially

I have a json model, where contents of certain attribute depend on the other attribute. 我有一个json模型,其中某些属性的内容取决于其他属性。 Something like this: 像这样:

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

So when paymentMethod equals to CREDIT_CARD , the metadata object will contain attributes as described. 因此,当paymentMethod等于CREDIT_CARDmetadata对象将包含所描述的属性。 In case of other payment method, there'll be different metadata. 如果使用其他付款方式,则元数据会有所不同。

I want to handle this situation in a future-proof way. 我想以一种面向未来的方式来处理这种情况。 What I'm trying to do is to not parse the metadata field right away, but keep it somehow "unparsed" until I've parsed the paymentMethod field. 我想做的是不立即解析metadata字段,而是以某种方式使其“未解析”,直到我解析了paymentMethod字段。 Then I'd take the metadata and applied appropriate parsing approach. 然后,我将采用元数据并应用适当的解析方法。

However I don't know which type to use for a Scala class field for such "late parsed" attributes. 但是我不知道对于这种“后期解析”属性的Scala类字段使用哪种类型。 I've tried String , JsonInput , JObject , and they all are not suitable (either don't compile or can't be parsed). 我试过了StringJsonInputJObject ,它们都不适合(要么不编译,要么无法解析)。 Any ideas which type can I use? 我可以使用哪种类型的想法? Or, in other words: 或者,换句话说:

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

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

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

You could create a CustomSerializer to parse the metadata directly. 您可以创建一个CustomSerializer来直接解析元数据。 Something like : 就像是 :

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

Which can be used as: 可以用作:

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)

You can use a Map[String, String] . 您可以使用Map[String, String] It will contain anything you may need. 它将包含您可能需要的任何内容。

The answer by Peter Neyens has inspired me to implement my own solution. Peter Neyens的回答启发了我实施自己的解决方案。 It's not as generic as his, but in my case I needed something really simple and ad-hoc. 它不像他那样通用,但就我而言,我需要一些非常简单且特别的东西。 Here's what I've done: 这是我所做的:

It's possible to define a case class with the field of unknown type is represented by a JObject type. 可以定义一个用JObject类型表示的未知类型字段的JObject类。 Something like this: 像这样:

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

When such json is parsed into such case class, this field is not parsed immediately, but contains all the necessary information. 将此类json解析为此类case类时,不会立即解析此字段,而是包含所有必要的信息。 Then it's possible parse it in a separate step: 然后可以在一个单独的步骤中对其进行解析:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM