vkt
vkt

Reputation: 1459

Conditional filtering of JSON before deserialisation to case class model

How to parse a JSON conditionally before deserialisation to the following case class:

case class UserInfo(id: String, startDate: String, endDate: String)

I have an implicit reads

object UserInfo {
    implicit val reads: Reads[UserInfo] = (
        (__ \ "id").read[String] and
        (__ \ "startDate").read[String] and
        (__ \ "endDate").read[String]
    )(UserInfo.apply _)
   }

I can parse the following json using above implicit reads

 val jsonString = """
{
       "users":[
          {
             "id":"123",
             "startDate":"2019-06-07",
             "endDate":"2019-06-17"
          },
          {
             "id":"333",
             "startDate":"2019-06-07",
             "endDate":"2019-06-27"
          }
       ]
    }"""

val userInfoList = (Json.parse(jsonString) \ "users").as[List[UserInfo]]

but sometimes the web service returns a JSON with no startDate and endDate, for example:

{
   "users":[
      {
         "id":"123",
         "startDate":"2019-06-07",
         "endDate":"2019-06-17"
      },
      {
         "id":"333",
         "startDate":"2019-06-07"
      },
      {
         "id":"444"
      }
   ]
}

How to conditionally parse json to ignore objects that don't have startDate or endDate without making those fields optional in UserInfo model?

Upvotes: 3

Views: 188

Answers (2)

Mario Galic
Mario Galic

Reputation: 48410

To avoid changing the model to optional fields we could define coast-to-coast transformer which filters out users with missing dates like so

    val filterUsersWithMissingDatesTransformer = (__ \ 'users).json.update(__.read[JsArray].map {
      case JsArray(values) => JsArray(values.filter { user =>
        val startDateOpt = (user \ "startDate").asOpt[String]
        val endDateOpt = (user \ "endDate").asOpt[String]
        startDateOpt.isDefined && endDateOpt.isDefined
      })
    })

which given

    val jsonString =
      """
        |{
        |   "users":[
        |      {
        |         "id":"123",
        |         "startDate":"2019-06-07",
        |         "endDate":"2019-06-17"
        |      },
        |      {
        |         "id":"333",
        |         "startDate":"2019-06-07"
        |      },
        |      {
        |         "id":"444"
        |      }
        |   ]
        |}
      """.stripMargin

    val filteredUsers = Json.parse(jsonString).transform(filterUsersWithMissingDatesTransformer)
    println(filteredUsers.get)

outputs

{
  "users": [
    {
      "id": "123",
      "startDate": "2019-06-07",
      "endDate": "2019-06-17"
    }
  ]
}

meaning we can deserialise to the existing model without making startDate and endDate optional.

case class UserInfo(id: String, startDate: String, endDate: String)

Upvotes: 3

Valy Dia
Valy Dia

Reputation: 2851

You can use Option for this:

case class UserInfo(id: String, startDate: Option[String], endDate: Option[String])

object UserInfo {
    implicit val reads: Reads[UserInfo] = (
        (__ \ "id").read[String] and
        (__ \ "startDate").readNullable[String] and
        (__ \ "endDate").readNullable[String]
    )(UserInfo.apply _)
   } 

This would work when startDate and endDate are not provided.

Upvotes: 1

Related Questions