I have a class model like this
final case class Appointment(id: Option[String] = None,
service: Option[String] = None,
start_at: Option[String] = None,
end_at: Option[String] = None,
entity: Option[String] = None,
status: Option[String] = None,
beneficiary: Option[String] = None,
)
I get responses from two diferent APIs that give JSONs with different structure, the first one have the same structure as my class, so to get it's data it's enough with this:
object Appointment {
implicit val jsonFormat: OFormat[Appointment] = Json.format[Appointment]
}
But the second one it's different like this:
{
data[
{
"id": 101643,
"establishment": "some stablishment",
"id_client": 125,
"reservation_date": "2023-01-23",
"date_start": "2023-01-23T05:00:00.000000Z",
"date_end": "2023-01-23T05:00:00.000000Z",
"service": "some service",
}]
}
Here I found a solution when it's one way or another but I need a way to combine both methods. I can't change my class attributes and also can't do this:
object User {
implicit val jsonReads: Reads[User] = (
(JsPath \ "id").read[String] and
(JsPath \ "username").read[String] and
(JsPath \ "profile_picture").read[String] and
(JsPath \ "full_name").read[String]
)(User.apply _)
}
because I will brake the implementation already existing.
Note I need Seq[Appointment]
# Summarizing
I need create a Seq[Appointment]
either with the info gived to from the first API or from the second, both APIs give responsens with different structures, and the second one give a structure different to my class Appointment
model
Take advantage of orElse
It's a method on the Reads
trait which allows you to combine multiple Reads
in a sort of "fallback" chain. If the first one fails, it tries the second one.
Make a separate Reads
for each JSON format you want to support, then orElse
them together to create a single Reads
that supports all formats.
The details of doing so will depend on how your classes are designed. I.e. do they share a common supertype? Are they completely separate, but have a conversion from one type to the other?
For example you might do
implicit val theOtherTypeReads: Reads[TheOtherAppointmentType] = /* ... */
implicit val appointmentReads: Reads[Appointment] = {
val defaultReads = Json.reads[Appointment]
val otherTypeReads = theOtherTypeReads.map(_.convertToAppointment)
defaultTypeReads.orElse(otherTypeReads)
}
or
implicit val appointmentReads: Reads[Appointment] = {
val subtype1Reads = Json.reads[AppointmentSubtype1]
val subtype2Reads = Json.reads[AppointmentSubtype2]
subtype1Reads orElse subtype2Reads
// note, the Json.reads macro might already do this for you,
// if `Appointment` is a sealed trait...
}