Reputation: 1459
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
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
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