Wolfsblvt
Wolfsblvt

Reputation: 1099

Scala Play - How to format Generics for JSON conversion

I am learning more and more about Scala and that nice playframework. But there are some things that bother me and that I can't get to work.

I like using Generics for some kind of collections, for example. But I need those to be stored in our database, in JSON. There is this cool auto conversion thing, but it does not work for generics, in no way I have tried :-/

Okay, to be concrete, code first:

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]
}

Item is a basic abstract class of different items that can be put in that inventory. It doesn't 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 an object of type AItem, not just Item. (That's the reason why I am using generics here)

So the problem

The implicit format for Inventory does not work, obviously. Item does, also with all special items, I can post the code for it below, and InventorySlot should work as well.

The error when compiling is:

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 the read and write explicitly, like

implicit val fmt = (
  (__ \ "slots").format[Vector[InventorySlot]]
  )(Inventory.apply, unlift(Inventory.unapply))

wich is not even working in my IDE, and I can't find the problem. I am confused. I don't know where my error lies, or if I am doing something wrong, or if I just miss something.

Any help will be appreciated.

I am so helpless, I even have considered doing a class for all possible inventory types, like

case class AItemInventory(protected var slots: Vector[InventorySlot]) extends Inventory[AItem](slots)

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

wich works. No problems, everything fine. So... I don't understand. Why is this working if it seems to be exactly the same, just hardcoded?

Appendix

The item formatter, wich 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)
  }
}

Upvotes: 3

Views: 3138

Answers (1)

bjfletcher
bjfletcher

Reputation: 11518

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 it for reads and writes, and what I've done here is to combine these two for the format.

Upvotes: 3

Related Questions