I'm creating Reads
and Writes
for my Java classes to make use of the Play Framework's JSON library.
One of my classes has an abstract class field.
ConcreteObj.java
public class ConcreteObj {
private AbstractObj someField;
public ConcreteObj(AbstractObj someField) {
this.someField = someField;
}
public AbstractObj getSomeField() { return this.someField };
...
Reads & Writes
implicit val ConcreteObjReads: Reads[ConcreteObj] =
(JsPath \ "someField").read[AbstractObj].map{x: AbstractObj => new ConcreteObj(x)}
implicit val ConcreteObjWrites: Writes[ConcreteObj] =
(JsPath \ "someField").write[AbstractObj].contramap{x: ConcreteObj => x.getField}
However the next step, creating a Reads[AbstractObj]
, doesn't make sense to me since an abstract class cannot be instantiated.
I suppose that the Writes[AbstractObj]
would look like:
implicit val AbstractObjWrites: Writes[AbstractObj] =
(JsPath \ "otherField").write[String].contramap{x: AbstractObj => x.getOtherField}
But what about the Reads[AbstractObj]
?
Since the concrete type is not available until runtime you will have to type check/parse type at runtime. It is possible you can do it with the functional syntax api but I've resorted to actually implementing Reads/Writes/Format for those cases, something along the lines:
implicit object Example extends Reads[AbstractThing] {
def reads(json: JsValue) = {
// somehow figure out the concrete subclass of AbstractThing
// based on the json
(json \ "type").as[String] match {
case "type1" => Json.fromJson[Concrete1](json)
case "type2" => Json.fromJson[Concrete2](json)
case t => JsError(s"Unknown concrete type of AbstractThing: $t")
}
}
}
This way you can still create re-usable formats/reads/writes for the concrete types that you can use where you do know at compile time what kind of object you are serializing/deserializing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With