Scala Play - How to Format Generics for JSON Conversion
I am learning more and more about Scala and what a glorious playframework is. But there are some things that bother me and that I cannot work.
I like to use Generics for some collections, for example. But I need the ones that will be stored in our database, in JSON. There is such a cool thing for automatic converting, but it doesn't work for generics, I have not tried it in any way: - /
Ok, to be specific, first the code:
case class InventorySlot(id: Long, item: Option[Item])
object InventorySlot {
implicit val fmt = Json.format[InventorySlot]
}
case class Inventory[T <: Item](slots: Vector[InventorySlot]) {
def length = slots.length
def items: Vector[T] = slots.map(slot => slot.item).flatten.asInstanceOf[Vector[T]]
def item(id: Long): Option[T] = {
slots.find(_.id == id) match {
case Some(slot: InventorySlot) =>
Some(slot.item.asInstanceOf[T])
case None =>
Logger.warn(s"slot with id $id not found")
None
}
}
}
object Inventory {
implicit val fmt = Json.format[Inventory]
}
An item is a basic abstract class of various items that can be placed in this inventory. It does not matter. But sometimes I want to have an inventory that just works for ItemType A, lets call it AItem
. So I want to create my inventory with something like this:
val myInventory = Inventory[AItem]("content of vector here")
and when I call myInventory.item(2)
then I want to get the item in slot 2 and it should be a type object AItem
, not just Item
. (This is why I am using generics here)
So the problem is
The implicit format for Inventory
doesn't work, obviously.
Item
does, also with all the special elements, I can post the code for it below and InventorySlot
should work too.
Compilation error:
Error:(34, 34) Play 2 Compiler:
C:\depot\mars\mainline\server\app\models\Test.scala:34: class Inventory takes type parameters
implicit val fmt = Json.format[Inventory]
^
I tried to write read and write explicitly like
implicit val fmt = (
(__ \ "slots").format[Vector[InventorySlot]]
)(Inventory.apply, unlift(Inventory.unapply))
which doesn't even work in my IDE and I can't seem to find the problem. I'm confused. I don't know where my mistake is, or if I am doing something wrong, or just missing something.
Any help would be appreciated.
I am so helpless that I even thought about making a class for all possible types of inventory, for example
case class AItemInventory(protected var slots: Vector[InventorySlot]) extends Inventory[AItem](slots)
object AItemInventory {
implicit val fmt = Json.format[AItemInventory]
}
which is working. No problem, it's okay. So ... I don't understand. Why does this work if it seems exactly the same, just hardcoded?
application
The formatter element that works:
implicit val itemFormat = new Format[Item] {
override def reads(json: JsValue): JsResult[Item] = {
(json \ "itemType").as[ItemType] match {
case ItemType.AITEM => fmtAItem.reads(json)
}
}
override def writes(item: Item): JsValue = item match {
case subItem: AItem => fmtAItem.writes(subItem)
case _ => JsNumber(item.itemType.id)
}
}
source to share
object Inventory {
implicit def fmt[T <: Item](implicit fmt: Format[T]): Format[Inventory[T]] = new Format[Inventory[T]] {
def reads(json: JsValue): Inventory[T] = new Inventory[T] (
(json \ "blah").as[String]
)
def writes(i: Inventory[T]) = JsObject(Seq(
"blah" -> JsString(i.blah)
))
}
}
Source: documentation explains how to do this for read and write, and what I've done here is combine the two for format.
source to share