如何使用subset2将DBObject解析为case类对象?

时间:2013-05-16 08:57:20

标签: mongodb scala subset implicit-conversion salat

有没有人知道如何使用 subset2 将DBObject解析为案例类对象?超简洁的文档对我没有帮助:(

考虑以下案例类

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()
    }
  )
}

我写了这个解析器

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

如果删除最后一个字段subitems,则有效。但上面显示的版本无法编译,因为MenuItem具有引用自身的字段。它给了我以下错误

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 {
                                                                                                                                                 ^

它显然无法编译,因为上一个get想要隐含Field[MenuItem]。但是,如果我为MenuItem定义它,那么DocParser[MenuItem]的复制粘贴是不是很多?

你会如何优雅地做到这一点?

1 个答案:

答案 0 :(得分:2)

我是Subset的作者(包括1.x和2)。

README表示您需要为每个Field[T]提供T您想要阅读的内容(在“反序列化”部分下)

  

只是旁注。坦率地说,我认为将MenuItem的解串器命名为jodaDateTime并不合理。

无论如何Field[T]必须从香草BSON类型转换为T。 BSON无法本地存储MenuItem,请参阅本机BSON类型here

但当然主要的问题是你有一个递归数据结构,所以你的“序列化器”(BsonWritable)和“反序列化器”(Field)也必须是递归的。 子集具有List[T]的隐式序列化程序/反序列化程序,但它们要求您提供MenuItem:递归。

为了简短起见,我将向您展示如何为更简单的“案例类”编写类似的内容。

假设我们有

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

然后作者可能看起来像

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

此处,当您将rec.children写入“DBObject”时,正在使用BsonWriteable[Rec],并且它依次需要“隐式”Field[Rec]。因此,此序列化程序 是递归的。

从解串器开始,以下将执行

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

这些是相互递归的(记得使用lazy val!)

你会像这样使用它们:

val dbo = DBO("y" -> Rec(123, Some(Rec(234, None) :: Nil))) ()
val Y = DocParser.get[Rec]("y")
dbo match {
  case Y(doc) => doc
}