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