Creating a case class for an embedded MongoDB Array with Salat and Scala

1k views Asked by At

I am trying to figure out how to properly serialize a document from MongoDB in my Scala project. The problem I have here is I'm not sure what to do when I have an Array field in my document and how to treat it in Scala. Here's what the document looks like in MongoDB:

> db.injuries.findOne()
{
    "_id" : ObjectId("5220ef71bbf8af333d000001"),
    "team_id" : 86,
    "league" : "NFC",
    "team_name" : "Arizona Cardinals",
    "players" : [
        {
            "player_id" : 9864,
            "date" : "8/26/2013",
            "position" : "TE",
            "name" : "Rob Housler",
            "injury" : "is doubtful for 9/8 against St. Louis",
            "status" : "Doubtful",
            "fantasy" : "",
            "injured" : "True",
            "type" : "ankle"
        },
        {
            "player_id" : 11610,
            "date" : "8/25/2013",
            "position" : "G",
            "name" : "Jonathan Cooper",
            "injury" : "may be placed on injured reserve",
            "status" : "Out",
            "fantasy" : "",
            "injured" : "True",
            "type" : "leg"
        },
        {
            "player_id" : 9126,
            "date" : "4/3/2013",
            "position" : "LB",
            "name" : "Daryl Washington",
            "injury" : "will be eligible to return on 10/6 against Carolina",
            "status" : "Suspended",
            "fantasy" : "",
            "injured" : "True",
            "type" : "four-game suspension"
        }
    ],
    "updated_at" : ISODate("2013-08-30T19:16:01.466Z"),
    "created_at" : ISODate("2013-08-30T19:16:01.466Z")
}
> 

Now I need to create a case class so that I can create a custom serializer for this document and deliver it to the client. I started building a case class like the following:

case class Injury(_id: ObjectId = new ObjectId, team_id: Int, team_name: String, league: String, players: List[????], created_at: Option[Date] = None, updated_at: Option[Date] = None, id: Option[ObjectId] = None )

I don't necessarily want to create a player case class because player hashes look different in other collections depending on context. I might have a players array for say a "schedules" collection and I'm not going to list injury data there. It's not an actual reference to a player collection, it's just a mere list with hashes where the field is named 'players'. Ideally I can then figure out how to write a serialize that will simply output this when this team's ID is requested:

{
  "team_id": 86,
  "team_name": "Arizona Cardinals",
  "players": [
    {
      "player_id": 9864,
      "date": "8/26/2013",
      "position": "TE",
      "name": "Rob Housler",
      "injury": "is doubtful for 9/8 against St. Louis",
      "status": "Doubtful",
      "fantasy": "",
      "injured": "True",
      "type": "ankle"
    },
    {
      "player_id": 11610,
      "date": "8/25/2013",
      "position": "G",
      "name": "Jonathan Cooper",
      "injury": "may be placed on injured reserve",
      "status": "Out",
      "fantasy": "",
      "injured": "True",
      "type": "leg"
    },
    {
      "player_id": 9126,
      "date": "4/3/2013",
      "position": "LB",
      "name": "Daryl Washington",
      "injury": "will be eligible to return on 10/6 against Carolina",
      "status": "Suspended",
      "fantasy": "",
      "injured": "True",
      "type": "four-game suspension"
    }
  ]
}

What else do I have to do in order to be able to derive to that final JSON document? I know Salat can handle the serialize to the case class.. but I'm not sure how to handle the players attribute here. Here's the start of a serializer I began to work on, but still clueless how to fit the players map into here:

class InjurySerializer extends CustomSerializer[Injury](format => ({
  case JObject(
  ("id", JString(id)) ::
    ("team_id", JString(team_id)) ::
    ("team_name" , JString(team_name)) ::
    ("league" , JString(league)) :: Nil) =>
    Injury(new ObjectId, team_id.asInstanceOf[Int], team_name.asInstanceOf[String], league.asInstanceOf[String])
}, {
  case injury: Injury =>
    JObject.apply(
      "team_id" -> JInt(injury.team_id),
      "team_name" -> JString(injury.team_name),
      "league" -> JString(injury.league)
    )
}))

And then I have a simple helper to retrieve all documents:

object Injury {

  def findAll = {
    val results = InjuryDAO.findAll
    results.map(grater[Injury].asObject(_)).toList
  }

}

This works fine, but doesn't include the players map as suggested above.

2

There are 2 answers

1
prasinous On

Arrays are not supported. Make it a list, a seq, nearly any other type of collection, and your document will serialize and deserialize correctly.

https://github.com/novus/salat/wiki/Collections

Make a player case class with all the possible fields and use default arguments to cover all the cases.

If there are too many different cases to cover, that's when things get really ugly. You are essentially trying to deserialize data that has no expected structure as ... what, exactly? I haven't added Map[String, Any] support to mainstream Salat although there is an outstanding pull request.

1
winash On

You probably have to use some custom serializer here, I assume you have a handle to the parent object - lets call that injury

 val playerRefs = injury.get("palyers")
 var obj = MongoDBObject("list" -> playerRefs )
 obj.as[MongoDBList]("list").toList.foreach {
   value =>
        val playerDef = value.asInstanceOf[BasicDBObject]
        // Access values from player -
        val name = playerDef.get("name")asInstanceOf[String]
        // Build your case class 
  }