Frage

Does anyone know how to parse DBObject to case class object using subset2 ? Super concise documentation doesn't help me :(

Consider following case class

case class MenuItem(id : Int, name: String, desc: Option[String], prices: Option[Array[String]], subitems: Option[Array[MenuItem]])

object MenuItem {
  implicit val asBson = BsonWritable[MenuItem](item =>
    {
      val buf: DBObjectBuffer = DBO("id" -> item.id, "name" -> item.name)
      item.desc match { case Some(value) => buf.append("desc" -> value) case None => }
      item.prices match { case Some(value) => buf.append("prices" -> value) case None => }
      item.subitems match { case Some(value) => buf.append("subitems" -> value) case None => }
      buf()
    }
  )
}

and I wrote this parser

val menuItemParser: DocParser[MenuItem] = int("id") ~ str("name") ~ str("desc").opt ~ get[Array[String]]("prices").opt ~ get[Array[MenuItem]]("subitems").opt map {
  case id ~ name ~ desc_opt ~ prices_opt ~ subitems => {
    MenuItem(id, name, desc_opt, prices_opt, subitems)
  }
}

It works if I remove last field subitems. But version shown above doesn't compile because MenuItem has field that references itself. It gives me following error

Cannot find Field for Array[com.borsch.model.MenuItem]
    val menuItemParser: DocParser[MenuItem] = int("id") ~ str("name") ~ str("desc").opt ~ get[Array[String]]("prices").opt ~ get[Array[MenuItem]]("subitems").opt map {
                                                                                                                                                 ^

It obviously doesn't compile because last get wants Field[MenuItem] implicit. But if I define it for MenuItem wouldn't it be pretty much copy-paste of DocParser[MenuItem] ?

How would you do it elegantly ?

War es hilfreich?

Lösung

I am an author of Subset (both 1.x and 2).

The README states that you need to have Field[T] per every T you would like to read (it's under "deserialization" section)

Just a side note. Frankly I don't find very logical to name a deserializer for MenuItem to be jodaDateTime.

Anyway Field[T] must translate from vanilla BSON types to your T. BSON cannot store MenuItem natively, see native BSON types here

But certainly the main problem is that you have a recursive data structure, so your "serializer" (BsonWritable) and "deserializer" (Field) must be recursive as well. Subset has implicit serializer/deserializer for List[T], but they require you to provide those for MenuItem : recursion.

To keep things short, I shall demonstrate you how you would write something like that for simpler "case class".

Suppose we have

case class Rec(id: Int, children: Option[List[Rec]])

Then the writer may look like

object Rec {
  implicit object asBson extends BsonWritable[Rec] {
    override def apply(rec: Rec) =
      Some( DBO("id" -> rec.id, "children" -> rec.children)() )
  }

Here, when you are writing rec.children into "DBObject", BsonWriteable[Rec] is being used and it requires "implicit" Field[Rec] in turn. So, this serializer is recursive.

As of the deserializer, the following will do

  import DocParser._

  implicit lazy val recField = Field({ case Doc(rec) => rec })
  lazy val Doc: DocParser[Rec] =
    get[Int]("id") ~ get[List[Rec]]("children").opt map {
      case id ~ children => new Rec(id, children)
    }
}

These are mutually recursive (remember to use lazy val!)

You would use them like so:

val dbo = DBO("y" -> Rec(123, Some(Rec(234, None) :: Nil))) ()
val Y = DocParser.get[Rec]("y")
dbo match {
  case Y(doc) => doc
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top