Search code examples
jsonscalaplayframeworkplay-json

How to parse a json containing a field that always has a changed name in Scala?


I currently have a very big json response from an API that I want to json parse. My application is using play and in Scala. Doing this I'm using case classes which I will later parse using an implicit. But now I've realized inside one of my case classes I have a field that's always changing name. How would one parse that?

For example I have this part of the json response:

"customerInfo": {
 "number": "123456XXXXXX7891",
    "email": "randomname@gmail.com",
    "billingFirstName": "Random",
    "billingLastName": "Name"
  },

Which I will parse with this:

case class CustomerInfo (
number: String,
email: String,
billingFirstName: String,
billingLastName: String
)

But then I have the changing field inside a json which looks like this:

    "Check": {
      "5c123456":
        "{random numbers inside here}"
    }

This field "5c123456" is always changing and never the same "name". How would I be able to parse it? And if not, can I somehow ignore this case class Check and still have rest of the json parsed? Would appreciate any kind of help.

Edited to add extra information

To clarify, what I want to do is to be able to parse it something like this:

val result = Json.fromJson[RootInterface](res.json).get

This is an example of what the entire response looks like:

    val response = : """{
  "per_page": 50,
  "current_page": 1,
  "next_page_url": "http:\/\/testapi.com\/api\/v3\/testing\/list?page=2",
  "prev_page_url": null,
  "from": 1,
  "to": 50,
  "data": [
    {
      "customerInfo": {
        "number": "1234",
        "email": "felix@ytesting.com",
        "billingFirstName": "Felix",
        "billingLastName": "Testing"
      },
      "updated_at": "2018-12-13 16:10:08",
      "created_at": "2018-12-13 14:06:54",
      "fx": {
        "merchant": {
          "originalAmount": 1234,
          "originalCurrency": "EUR",
          "convertedAmount": 1234,
          "convertedCurrency": "EUR"
        }
      },
      "raw": {
        "Check": {
          "5c1283dad": "{\"id\":\"1234-5678-4da1-a3ea-5aa0c2b8a3f3\",\"status\":\"TEST\",\"risk_score\":0,\"descriptions\":{},\"testRuleId\":null,\"triggeredExceptionRuleId\":null,\"reason\":null}"
        }
      },
      "acquirer": {
        "type": "TESTCARD"
      },
      "transaction": {
        "merchant": {
          "referenceNo": "12345",
          "status": "TEST",
          "operation": "TEST",
          "type": "AUTH",
          "message": "TESTING",
          "customData": "1234",
          "created_at": "2018-12-12 15:10:07",
          "transactionId": "12345"
        }
      },
      "refundable": false,
      "merchant": {
        "id": 1234,
        "name": "Felix Testing",
        "allowPartialRefund": false,
        "allowPartialCapture": false
      },
      "ipn": {
        "sent": true,
        "merchant": {
          "transactionId": "123456789",
          "referenceNo": "1234",
          "amount": 1234,
          "currency": "EUR",
          "date": 1544717407,
          "code": "42",
          "message": "TESTING",
          "operation": "TEST",
          "type": "AUTH",
          "status": "DECLINED",
          "customData": "12345",
          "paymentType": "TESTCARD",
          "authTransactionId": "123456",
          "descriptor": "Felix Testing",
          "token": "123456789token",
          "convertedAmount": 1234,
          "convertedCurrency": "EUR",
          "ipnType": "TESTIP",
          "_queueReadCountKey": 1
        }
      }
    }
  ]
}"""

Which I have these case clases for:

case class Acquirer(
  `type`: String
)

case class CustomerInfo (
  number: String,
  email: String,
  billingFirstName: String,
  billingLastName: String
                        )

case class Data (
    customerInfo: CustomerInfo,
    updated_at: String,
    created_at: String,
    fx: Fx,
    raw: Option[Raw],
    acquirer: Acquirer,
    transaction: transaction,
    refundable: Boolean,
    merchant: Merchant2,
    ipn: Ipn
  )

  case class transaction(
  merchant : Merchant1
                        )

case class Fx (
  merchant: Merchant
)

case class Ipn (
 sent: Boolean,
 merchant: Merchant3
 )

case class Merchant (
originalAmount: Int,
originalCurrency: String,
convertedAmount: Int,
convertedCurrency: String
)

case class Merchant1 (
 referenceNo: String,
 status: String,
 operation: String,
`type`: String,
message: String,
customData: String,
created_at: String,
transactionId: String
)

case class Merchant2 (
   id: Int,
   name: String,
   allowPartialRefund: Boolean,
   allowPartialCapture: Boolean
   )

case class Merchant3 (
   transactionId: String,
   referenceNo: String,
   amount: Int,
   currency: String,
   date: Int,
   code: String,
   message: String,
   operation: String,
   `type`: String,
   status: String,
   customData: String,
   paymentType: String,
   authTransactionId: String,
   descriptor: String,
   token: String,
   convertedAmount: Int,
   convertedCurrency: String,
   ipnType: String,
   _queueReadCountKey: Int
)
                 )

case class Raw(Check: Map[String, String])


case class RootInterface (
   per_page: Int,
   current_page: Int,
   next_page_url: String,
   prev_page_url: String,
   from: Int,
   to: Int,
   data: List[Data]
 )

And these implicits:

implicit val RootInterface: Format[RootInterface] = Json.format[RootInterface]
  implicit val Data: Format[Data] = Json.format[Data]
  implicit val CustomerInfo: Format[CustomerInfo] = Json.format[CustomerInfo]
  implicit val Fx: Format[Fx] = Json.format[Fx]
  implicit val Transaction: Format[transaction] = Json.format[transaction]
  implicit val Acquirer: Format[Acquirer] = Json.format[Acquirer]
  implicit val Merchant: Format[Merchant] = Json.format[Merchant]
  implicit val Merchant1: Format[Merchant1] = Json.format[Merchant1]
  implicit val Merchant2: Format[Merchant2] = Json.format[Merchant2]
  implicit val Merchant3: Format[Merchant3] = Json.format[Merchant3]
  implicit val Ipn: Format[Ipn] = Json.format[Ipn]
  implicit val jsonFormat: Format[Raw] = Json.format[Raw]

Solution

  • As mentioned in the comments dynamic fields are handled with a Map. Here is an example:

    Define the case-class like this:

    import play.api.libs.json._
    
    case class MyObject(Check: Map[String, String])
    

    Define an implicit Format:

    object MyObject {
      implicit val jsonFormat: OFormat[MyObject] = Json.format[MyObject] 
    }
    

    Use validate to create the case-class from the Json

    val json = Json.parse("""{ "Check": {
          "5c123456":
            "123239"
        }}""")
    
    json.validate[MyObject] // > JsSuccess(MyObject(Map(5c123456 -> 123239)),)
    

    Here is the Scalafiddle to check: Scalafiddle

    And here the according documentation: JSON-automated-mapping