marshaling a Set of polymorphic types

869 views
Skip to first unread message

Adrian Rodriguez

unread,
Jul 2, 2013, 9:55:10 PM7/2/13
to spray...@googlegroups.com
Hi everyone,
     I was working on trying to marshal one of my classes that has a Set[Device].

Here is some sample code with stuff stripped out for clarity.

abstract class Device(val available: Boolean, val owner: Option[User])

case class PhoneDevice(available: Boolean, owner: Option[User], phoneNumber: String) extends Device(available, owner)

case class SIPDevice(available: Boolean, owner: Option[User], sipUri: String) extends Device(available, owner)

case class User(id: Option[UUID] = None, devices: Set[Device] = Set.empty)

val user = User(someId, Set(PhoneDevice(true, None, "+12132221111"), SIPDevice(true, None, "sip:user:password@host:port")))

I have a JsonFormat for User without the devices piece and I can obviously make a format for PhoneDevice and SIPDevice. My question is how do I tell spray-json to marshal this Set of polymorphic types so that all Device objects in the devices Set use the appropriate format for their specific type? Somewhere in the UserFormat write(ojb: User): JsValue method I have JsObject(Map(........,"devices" -> obj.devices.toJson,........)), but I'm having trouble getting that to work. Is it even possible?



Mathias

unread,
Jul 3, 2013, 4:32:11 AM7/3/13
to spray...@googlegroups.com
Adrian,

the thing to remember when working with type classes (like the spray-json `JsonFormat`) is that
all the resolution of what type class instance to supply for which method call is done by the
*compiler* at *compile time*!

In your case a user is receiving a set of `Device` instances, whose exact types are only known at *runtime*.
Therefore you need to provide a custom `JsonFormat[Device]` and write the logic for how to determine *at runtime* whether an incoming piece of JSON contains a `PhoneDevice` or a `SIPDevice`. The compiler cannot do this for you (after all there might be even more subclasses of `Device` written at a later time).

Note that, if you model the user like this:

case class User(id: Option[UUID] = None, devices: Set[Either[PhoneDevice, SIPDevice] = Set.empty)

things would be different. This way you make it explicit which exact types are allowed in the set, thereby enabling the compiler to make use of the `eitherFormat` that is already available with spray-json. When reading JSON this `JsonFormat` will simply try deserializing into both types and succeed only if exactly one of the two alternatives worked out.

HTH and cheers,
Mathias

---
mat...@spray.io
http://spray.io
> --
> You received this message because you are subscribed to the Google Groups "spray-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to spray-user+...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Adrian Rodriguez

unread,
Jul 3, 2013, 5:51:56 PM7/3/13
to spray...@googlegroups.com
Mathias,
     Thank you for the feedback. Yesterday night I came to that realisation (note to self: take a break when the brain doesn't work) that it's compile-time not runtime. So I started thinking about what to do in this case. I saw some answers on Stackoverflow recommending a switch/case style on the write method

def write(obj: Device) = obj match {
  case phoneDevice: PhoneDevice => phoneDevice.toJson
  case sipDevice: SIPDevice => sipDevice.toJson
}

I'm not exactly too fond of that solution. I defeats the purpose of polymorphism having to match on types so I suppose I just have to look into how the PimpedAny works (I think that's where I saw the toJson function). Ideally I would like to do something like:

def write(obj: Device) = {
  obj.toJson // or whatever function name that invokes the correct polymorphic behavior at runtime
}

I'll look into this today. Thanks again!

Mathias

unread,
Jul 3, 2013, 6:01:54 PM7/3/13
to spray...@googlegroups.com
Adrian,

> I saw some answers on Stackoverflow recommending a switch/case style
> on the write method
>
> def write(obj: Device) = obj match {
> case phoneDevice: PhoneDevice => phoneDevice.toJson
> case sipDevice: SIPDevice => sipDevice.toJson
> }
>
> I'm not exactly too fond of that solution. I defeats the purpose of
> polymorphism…


Check out Martin Odersky's Scala Days 2013 keynote for some thoughts on this:
http://parleys.com/play/51c1994ae4b0d38b54f4621b/chapter0/about

Among other things he also talks about dynamic dispatch vs. pattern matching (at about 90%, 1:19:07).
Applying his recommendations to your case I wouldn't say that the pattern match is a bad solution!

Cheers,

Adrian Rodriguez

unread,
Jul 3, 2013, 6:22:28 PM7/3/13
to spray...@googlegroups.com
I just watched that portion of the keynote. Thanks for the pointer! I should say that we don't just have those 2 types of devices. There are 10 types. So I just sat and thought about it. I suppose it would make sense to use the dynamic dispatch solution if, like Odersky said, we wanted to be very extensible (I'm thinking adding new device types constantly or even dynamically like plugins). However, even if we have 10 device types, there is no way we are going to add more than 2 other types in the next two years so I see your point. That piece of the code is very isolated (the json marshaling in the write function of the format class) so I think it makes sense in my case to just pattern match and avoid any further thinking that is taking me away from doing more valuable work :). 

Thanks for the input Mathias!
Reply all
Reply to author
Forward
0 new messages