Matt Foxx Duncan
Matt Foxx Duncan

Reputation: 2094

Serializing case class with trait mixin using json4s

I've got a case class Game which I have no trouble serializing/deserializing using json4s.

case class Game(name: String,publisher: String,website: String, gameType: GameType.Value)

In my app I use mapperdao as my ORM. Because Game uses a Surrogate Id I do not have id has part of its constructor.

However, when mapperdao returns an entity from the DB it supplies the id of the persisted object using a trait.

Game with SurrogateIntId

The code for the trait is

trait SurrogateIntId extends DeclaredIds[Int]
{
    def id: Int
}

trait DeclaredIds[ID] extends Persisted

trait Persisted
{
    @transient
    private var mapperDaoVM: ValuesMap = null
    @transient
    private var mapperDaoDetails: PersistedDetails = null
private[mapperdao] def mapperDaoPersistedDetails = mapperDaoDetails

private[mapperdao] def mapperDaoValuesMap = mapperDaoVM

private[mapperdao] def mapperDaoInit(vm: ValuesMap, details: PersistedDetails) {
    mapperDaoVM = vm
    mapperDaoDetails = details
}
.....
}

When I try to serialize Game with SurrogateIntId I get empty parenthesis returned, I assume this is because json4s doesn't know how to deal with the attached trait.

I need a way to serialize game with only id added to its properties , and almost as importantly a way to do this for any T with SurrogateIntId as I use these for all of my domain objects.

Can anyone help me out?

Upvotes: 3

Views: 1397

Answers (1)

Matt Foxx Duncan
Matt Foxx Duncan

Reputation: 2094

So this is an extremely specific solution since the origin of my problem comes from the way mapperDao returns DOs, however it may be helpful for general use since I'm delving into custom serializers in json4s.

The full discussion on this problem can be found on the mapperDao google group.

First, I found that calling copy() on any persisted Entity(returned from mapperDao) returned the clean copy(just case class) of my DO -- which is then serializable by json4s. However I did not want to have to remember to call copy() any time I wanted to serialize a DO or deal with mapping lists, etc. as this would be unwieldy and prone to errors.

So, I created a CustomSerializer that wraps around the returned Entity(case class DO + traits as an object) and gleans the class from generic type with an implicit manifest. Using this approach I then pattern match my domain objects to determine what was passed in and then use Extraction.decompose(myDO.copy()) to serialize and return the clean DO.

// Entity[Int, Persisted, Class[T]] is how my DOs are returned by mapperDao

class EntitySerializer[T: Manifest] extends CustomSerializer[Entity[Int, Persisted, Class[T]]](formats =>(
  {PartialFunction.empty} //This PF is for extracting from JSON and not needed
,{
  case g: Game => //Each type is one of my DOs
    implicit val formats: Formats = DefaultFormats //include primitive formats for serialization
    Extraction.decompose(g.copy()) //get plain DO and then serialize with json4s
  case u : User =>
    implicit val formats: Formats = DefaultFormats + new LinkObjectEntitySerializer //See below for explanation on LinkObject
    Extraction.decompose(u.copy())
  case t : Team =>
    implicit val formats: Formats = DefaultFormats + new LinkObjectEntitySerializer
    Extraction.decompose(t.copy())
...
}

The only need for a separate serializer is in the event that you have non-primitives as parameters of a case class being serialized because the serializer can't use itself to serialize. In this case you create a serializer for each basic class(IE one with only primitives) and then include it into the next serializer with objects that depend on those basic classes.

class LinkObjectEntitySerializer[T: Manifest] extends CustomSerializer[Entity[Int, Persisted, Class[T]]](formats =>(
  {PartialFunction.empty},{
         //Team and User have Set[TeamUser] parameters, need to define this "dependency"
         //so it can be included in formats
  case tu: TeamUser => 
    implicit val formats: Formats = DefaultFormats
    ("Team" ->                     //Using custom-built representation of object
      ("name" -> tu.team.name) ~
      ("id" -> tu.team.id) ~
      ("resource" -> "/team/") ~
      ("isCaptain" -> tu.isCaptain)) ~
    ("User" ->
      ("name" -> tu.user.globalHandle) ~
      ("id" -> tu.user.id) ~
      ("resource" -> "/user/") ~
      ("isCaptain" -> tu.isCaptain))
}
  ))

This solution is hardly satisfying. Eventually I will need to replace mapperDao or json4s(or both) to find a simpler solution. However, for now, it seems to be the fix with the least amount of overhead.

Upvotes: 1

Related Questions