preston.m.price
preston.m.price

Reputation: 666

Scala play JSON mapping doesn't work with inheritance

I'm coming from a Java background where mapping POJOs is really, really simple. I expect that the same is true for scala/play (and it is for case classes) but things really seem to soil the sheets when I try to add a bit of hierarchy.

So, my specific problem is that I'm trying to send various messages over a websocket and JSON is just really good for this. But, I don't want to repeat common attributes in every class for every type of message, hence the inheritance.

So, I'm trying something like this:

class WSMessage(messageType:String)
class EventMessage(eventType:String) extends WSMessage("event")
class AlertMessage(alertType:String) extends WSMessage("alert")

and there may be further specificity....

class LoginEventMessage(login:Login) extends EventMessage("login")
class OrderEventMessage(order:Order) extends EventMessage("order")
class TerrainAlertMessage(terrain:Terrain) extends AlertMessage("terrain")

Now, assuming I've put something like:

object LoginEventMessage { implicit val fmt = Json.format[LoginEventMessage] }

AND

object Login { implicit val fmt = Json.format[Login] }

on every object at every step of the hierarchy (which is really annoying) I still only get the lowest level attributes converted to JSON.

For example

Json.toJson(new LoginEventMessage(theLogin))

yields something like

{ "login": { "username": "foo", "timestamp": "0000000" } }

rather than

{ "messageType": "event", "eventType": "login", "login": { "username": "foo", "timestamp": "0000000" } }

Any guidance on how to accomplish this without repeating all attributes at the lowest level would be welcome!

Cheers.

Edited To Add Attempt #2 I tried modifying things a bit based on Zoltán's comment. Here is the result:

object Login { implicit val fmt = Json.format[Login] }
case class Login(username:String, timestamp:DateTime)

object WSMessage { implicit val fmt = Json.format[WSMessage] }
class WSMessage(val messageType:String)
object EventMessage { implicit val fmt = Json.format[EventMessage] }
class EventMessage(val eventType:String) extends WSMessage("event")
object LoginEventMessage { implicit val fmt = Json.format[LoginEventMessage] }
class LoginEventMessage(val login:Login) extends EventMessage("login")

We get compile errors because these aren't case classes (they don't like inheritance).

[error] /app/model/dto/WSMessage.scala:15: No unapply or unapplySeq function found
[error] object WSMessage { implicit val fmt = Json.format[WSMessage] }
[error]                                                  ^
[error] /app/model/dto/WSMessage.scala:17: No unapply or unapplySeq function found
[error] object EventMessage { implicit val fmt = Json.format[EventMessage] }
[error]                                                     ^
[error] /app/model/dto/WSMessage.scala:19: No unapply or unapplySeq function found
[error] object LoginEventMessage { implicit val fmt = Json.format[LoginEventMessage] }
[error]                                                          ^
[error] three errors found
[error] (compile:compileIncremental) Compilation failed
[error] application - 

Upvotes: 3

Views: 439

Answers (1)

Zoltán
Zoltán

Reputation: 22166

Remember that when declaring classes, as opposed to case classes, all the parameters of the constructor are protected fields, unless declared with val.

So in order to expose all the fields to the JSON formatter, you need to declare them with val:

class WSMessage(val messageType:String)
class EventMessage(val eventType:String) extends WSMessage("event")
class AlertMessage(val alertType:String) extends WSMessage("alert")

class LoginEventMessage(val login:Login) extends EventMessage("login")
class OrderEventMessage(val order:Order) extends EventMessage("order")
class TerrainAlertMessage(val terrain:Terrain) extends AlertMessage("terrain")

Upvotes: 1

Related Questions