Search code examples
jsonscalaplayframeworktraversal

How to parse an array of json in scala play framework?


I have an array of json objects like this

[
  {
    "events": [
      {
        "type": "message",
        "attributes": [
          {
            "key": "action",
            "value": "withdraw_reward"
          },
          {
            "key": "sender",
            "value": "bob"
          },
          {
            "key": "module",
            "value": "distribution"
          },
          {
            "key": "sender",
            "value": "bob"
          }
        ]
      },
      {
        "type": "credit",
        "attributes": [
          {
            "key": "recipient",
            "value": "ross"
          },
          {
            "key": "sender",
            "value": "bob"
          },
          {
            "key": "amount",
            "value": "100"
          }
        ]
      },
      {
        "type": "rewards",
        "attributes": [
          {
            "key": "amount",
            "value": "100"
          },
          {
            "key": "validator",
            "value": "sarah"
          }
        ]
      }
    ]
  },
  {
    "events": [
      {
        "type": "message",
        "attributes": [
          {
            "key": "action",
            "value": "withdraw_reward"
          },
          {
            "key": "sender",
            "value": "bob"
          },
          {
            "key": "module",
            "value": "distribution"
          },
          {
            "key": "sender",
            "value": "bob"
          }
        ]
      },
      {
        "type": "credit",
        "attributes": [
          {
            "key": "recipient",
            "value": "ross"
          },
          {
            "key": "sender",
            "value": "bob"
          },
          {
            "key": "amount",
            "value": "100"
          }
        ]
      },
      {
        "type": "rewards",
        "attributes": [
          {
            "key": "amount",
            "value": "200"
          },
          {
            "key": "validator",
            "value": "Ryan"
          }
        ]
      }
    ]
  }
]

How to traverse through the types, check if it's type equals to rewards and then go through the attributes and verify if the validator equals to sarah and fetch the value of the key amount? Pretty new to scala and play framework. Any help would be great. Thanks


Solution

  • You could parse your JSON into a structure of case classes for easier handling and then extract the wanted field like so:

    val json = 
    """[
     {"events":[
                {
                  "type":"message","attributes":[
                       {"key":"action","value":"withdraw_reward"}, 
                       {"key":"sender","value":"bob"}, 
                       {"key":"module","value":"distribution"}, 
                       {"key":"sender","value":"bob"}
                ]},
               {
                 "type":"credit","attributes":[
                      {"key":"recipient","value":"ross"},
                      {"key":"sender","value":"bob"},
                      {"key":"amount","value":"100"}
               ]},
               {
                 "type":"rewards","attributes":[
                      {"key":"amount","value":"100"}, 
                      {"key":"validator","value":"sara"}
               ]}
            ]
     },
       {"events":[
                {
                  "type":"message","attributes":[
                            {"key":"action","value":"withdraw_reward"}, 
                       {"key":"sender","value":"bob"}, 
                       {"key":"module","value":"distribution"}, 
                       {"key":"sender","value":"bob"}
                ]},
               {
                 "type":"credit","attributes":[
                      {"key":"recipient","value":"ross"},
                      {"key":"sender","value":"bob"},
                      {"key":"amount","value":"100"}
               ]},
               {
                 "type":"rewards","attributes":[
                      {"key":"amount","value":"200"}, 
                      {"key":"validator","value":"Ryan"}
               ]}
            ]
     }
    ]
    """
    
    case class EventWrapper(events: Seq[Event])
    case class KeyValue(key: String, value: String)
    case class Event(`type`: String, attributes: Seq[KeyValue])
    
     import play.api.libs.json._
    
        implicit val kvReads: Reads[KeyValue] = Json.reads[KeyValue]
        implicit val eventReads: Reads[Event] = Json.reads[Event]
        implicit val eventWrapperReads: Reads[EventWrapper] = Json.reads[EventWrapper]
    
        val rewardAmountsValidatedBySara = Json
          .parse(json)
          .as[Seq[EventWrapper]]
          .flatMap {
            _.events.collect {
              case Event(t, attributes) if t == "rewards" && attributes.contains(KeyValue("validator", "sara")) =>
                attributes.collect {
                  case KeyValue("amount", value) => value
                }
            }.flatten
          }
    
        val amount = rewardAmountsValidatedBySara.head
    

    For your example, rewardAmountsValidatedBySara would yield a List of Strings containing only the String "100". Which you could retrieve (potentially unsafe) with .head as shown above.

    Normally you would not do this, as it could throw an exception on an empty List, so it would be better to use .headOption which returns an Option which you can then handle safely.

    Note that the implicit Reads are Macros, which automatically translate into Code, that instructs the Play Json Framework how to read the JsValue into the defined case classes, see the documentation for more info.