Is there a way for the this
keyword in a super class to refer to that class's subclass? Specifically, I am trying to do the following (the Json refers to Play's Json library):
abstract class A() {
def toJson[T](implicit writes: Writes[T]): JsValue = Json.toJson(this)
}
case class B(myProperty: String) extends A
object B { implicit val bFormat = Json.format[B] }
This gives the error No Json serializer found for type A. Try to implement an implicit Writes or Format for this type.
. So it's saying it can't serialize an object of type A
, which makes sense. The goal, however, is for the this
in Json.toJson(this)
to refer to the subclass (which, in this instance, is B
).
Is there any way to accomplish this? If not, is there any other way I can implement the Json.toJson(...)
method in the superclass without having to implement in the subclass (and all other subclasses of A
)?
The common trick to refer to the current subclass from the parent, is to use F-bounded polymorphism:
// Here `T` refers to the type of the current subclass
abstract class A[T <: A[T]]() {
this: T =>
def toJson(implicit writes: Writes[T]): JsValue = Json.toJson(this)
}
// We have to specify the current subclass in `extends A[B]`
case class B(myProperty: String) extends A[B]
object B { implicit val bFormat = Json.format[B] }
println(B("foo").toJson)
This won't allow you to call toJson
for any generic A
though:
val a: A[_] = B("foo")
println(a.toJson) // Doesn't compile with:
// No Json serializer found for type _$1.
// Try to implement an implicit Writes or Format for this type.
To fix this you would have to save Writes
for the subtype at the point of object creation:
abstract class A[T <: A[T]](implicit writes: Writes[T]) {
this: T =>
def toJson: JsValue = Json.toJson(this)
}
Or alternatively using the context bound notation:
abstract class A[T <: A[T] : Writes] {
this: T =>
def toJson: JsValue = Json.toJson(this)
}
And since this F-bounded polymorphism thing is just an implementation detail and always refering to a generic A
as A[_]
is quite boilerplate-y, you can move this code to an intermediate abstract class
.
So a full example looks like this:
abstract class A() {
def toJson: JsValue
}
abstract class AImpl[T <: AImpl[T] : Writes] extends A {
this: T =>
def toJson: JsValue = Json.toJson(this)
}
case class B(myProperty: String) extends AImpl[B]
object B { implicit val bFormat: Format[B] = Json.format[B] }
val a: A = B("foo")
println(a.toJson)