3

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]
GamingFelix
  • 239
  • 2
  • 10
  • Can you consider it like a staring to string Map? – mkUltra Dec 13 '18 at 15:57
  • What do you suggest? Can I do that without breaking my current parsing using case classes and implicits? It seems like it should be possible to do a big mapping for each "element" but it's a pretty big json response with many fields and nested fields. – GamingFelix Dec 13 '18 at 16:03
  • 2
    I suggest use something like: case class Customer(check: Map[String, String]) for case """ {"check": {"5c123456" : "{random numbers inside here}"}} """ – mkUltra Dec 13 '18 at 16:11
  • 3
    On a side note, I feel a sternly-worded letter is in order for whomever wrote that JSON API... – Lasf Dec 13 '18 at 16:27
  • Thank you @mkUltra I'll try that. – GamingFelix Dec 13 '18 at 17:48

1 Answers1

4

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

pme
  • 14,156
  • 3
  • 52
  • 95
  • This doesn't seem to work for me. I don't quite understand what you're doing in the last part of the answer. To clarify my issue, my first case class, where everything is rooted from or the rest of the json/case classes looks like this: `case class RootInterface ( per_page: Int, current_page: Int, next_page_url: String, prev_page_url: String, from: Int, to: Int, data: Seq[Data]` ) . and my implicit that I use for it is like this: `implicit val RootInterface: Format[RootInterface] = Json.format[RootInterface] ` – GamingFelix Dec 14 '18 at 09:46
  • And I try to parse it all doing this: `val result = Json.fromJson[RootInterface](res.json).get` where res.json is the response from my api – GamingFelix Dec 14 '18 at 09:49
  • 1
    See my updated answer. I added an example in Scalafiddle and the according doc. If it is still not clear just ask again;) – pme Dec 14 '18 at 11:22
  • I updated the question again with an example response, perhaps you understand my question better now. It's not that I want to just parse that changing field. It's also that it's nested within a bigger json. – GamingFelix Dec 14 '18 at 12:03
  • Put the formatter in the companion object as I did it. Otherwise the order is essential! – pme Dec 14 '18 at 13:12
  • I updated https://scalafiddle.io/sf/iM9uTvK/1 - now there is an path exception - but the rest works – pme Dec 14 '18 at 13:14
  • I appreciate your help @pme but it's still not working. It seems I can't declare my implicits the same way you have. It won't let me make them OFormat. Perhaps my Play is an older version. But even if I had everything working like your code shows, that error would also still be a problem. I did declare the implicits now in an object and now it says that it can't find the implicits: *No Json deserializer found for type com.devcode.accountiq.integrations.kluwp.RootInterface. Try to implement an implicit Reads or Format for this type*. Anyway, thanks for trying but it seems I'm stuck still. – GamingFelix Dec 14 '18 at 13:58
  • create a Scalafiddle with your Code - so I can check - adding an Option solves the parse problem: https://scalafiddle.io/sf/iM9uTvK/2 – pme Dec 14 '18 at 13:59
  • I will. Maybe it's because I have an old version of play that I can't declare the implicits the same way you do. I will try put it in the Scalafiddle. I just have a lot of surrounding code, so might take a while. Is there a reason to use json.Parse and validate over instead of just doing Json.FromJson? – GamingFelix Dec 14 '18 at 14:33
  • nvm, read up on validate. It seems to do the exact same thing. – GamingFelix Dec 14 '18 at 14:46
  • Yes it seems I can't use OFormat because I'm on an old version of play. https://stackoverflow.com/questions/37342498/how-to-create-oformat-from-format-in-scala-and-play-framework. I don't think I can upgrade my play – GamingFelix Dec 14 '18 at 15:10
  • 1
    try to change them to Format - it still works here: https://scalafiddle.io/sf/iM9uTvK/3 – pme Dec 14 '18 at 15:29
  • Yes, now the validation isn't throwing a random exception anymore Now I'm getting JsError(List((/data(0)/raw/Check,List(ValidationError(List(error.path.missing),WrappedArray()))))). I think I'm getting closer. Thank you! Seems, using validate was a lot better approach. – GamingFelix Dec 14 '18 at 15:35
  • I added : case class Raw(Check: Option[Map[String, String]]) and now I'm getting JsSuccess. So I guess I'm done. Awesome! Thank you so much for the help :) :) – GamingFelix Dec 14 '18 at 15:45