防止插入Mongo集合的数组中的空值

时间:2017-12-14 16:52:32

标签: mongodb scala playframework

我正在尝试阻止将空值插入到我的 mongoDB集合中。有问题的字段如下所示:

MongoDB字段

   "stadiumArr" : [
     "Old Trafford",
     "El Calderon",
     ...
   ]

(已映射)案例类的示例

   case class FormData(_id: Option[BSONObjectID], stadiumArr: Option[List[String]], ..)

Scala表单示例

  object MyForm {

     val form = Form(
        mapping(
          "_id" -> ignored(Option.empty[BSONObjectID]),
          "stadiumArr" -> optional(list(text)),
          ...
        )(FormData.apply)(FormData.unapply)
     )

  }

我也正在使用Repeated Values functionality in Play Framework

播放模板

   @import helper._
   @(myForm: Form[models.db.FormData])(implicit request: RequestHeader, messagesProvider: MessagesProvider)

   @repeatWithIndex(myForm("stadiumArr"), min = 5) { (stadium, idx) =>
       @inputText(stadium, '_label -> ("stadium #" + (idx + 1)))
   }

这可以确保数组中是否至少有5个值;仍然会创建(至少)5个输入框。但是,如果在提交表单时一个(或多个)输入框为空,则仍会在数组中将空字符串添加为值,例如

   "stadiumArr" : [
     "Old Trafford",
     "El Calderon",
     "",
     "",
     ""
   ]

基于从/向数据库转换类型的一些其他方法;我试过玩几个解决方案;如:

implicit val arrayWrite: Writes[List[String]] = new Writes[List[String]] {
  def writes(list: List[String]): JsValue = Json.arr(list.filterNot(_.isEmpty))
}

..但这不起作用。有关如何防止空值插入数据库集合的任何想法?

2 个答案:

答案 0 :(得分:0)

我已经为此而去了(当它被编写和测试时,它总是显而易见的):

    implicit val arrayWrite: Writes[List[String]] = new Writes[List[String]] {
      def writes(list: List[String]): JsValue = Json.toJson(list.filterNot(_.isEmpty).toIndexedSeq)
    }

但我很想知道如何

  

。映射现有的Read而不是从头开始重新定义

as @cchantep建议

答案 1 :(得分:0)

在不知道您使用它的特定版本或库的情况下很难给出答案,但是由于您链接到2.6文档,我会假设您是' ;在那里使用。我要做的另一个假设是你正在使用reactive-mongo库。无论您是否正在使用该库的播放插件,我都会在这里给您两个不同的答案:

在该库中,没有插件,您已为您的案例类定义了BSONDocumentReaderBSONDocumentWriter。这可能是使用宏自动为您生成的,但无论如何获得它,这两个类都有可用于将读取/写入转换为另一个类的有用方法。所以,让我们说我为你定义了一个读者和作家:

import reactivemongo.bson._

case class FormData(_id: Option[BSONObjectID], stadiumArr: Option[List[String]])

implicit val formDataReaderWriter = new BSONDocumentReader[FormData] with BSONDocumentWriter[FormData] {
    def read(bson: BSONDocument): FormData = {
        FormData(
            _id = bson.getAs[BSONObjectID]("_id"),
            stadiumArr = bson.getAs[List[String]]("stadiumArr").map(_.filterNot(_.isEmpty))
        )
    }
    def write(formData: FormData) = {
        BSONDocument(
            "_id" -> formData._id,
            "stadiumArr" -> formData.stadiumArr
        )
    }
}

很棒,你说,有效!您可以在读取中看到我前进并过滤掉任何空字符串。因此,即使它在数据中,也可以清理它。这很好,但是请注意,我没有为写入做同样的事情。我这样做了,所以我可以告诉你如何使用一个名为afterWrite的有用方法。所以假装读者/作者不是同一个班级并且是分开的,那么我可以这样做:

val initialWriter = new BSONDocumentWriter[FormData] {
    def write(formData: FormData) = {
        BSONDocument(
            "_id" -> formData._id,
            "stadiumArr" -> formData.stadiumArr
        )
    }
}

implicit val cleanWriter = initialWriter.afterWrite { bsonDocument =>
    val fixedField = bsonDocument.getAs[List[String]]("stadiumArr").map(_.filterNot(_.isEmpty))
    bsonDocument.remove("stadiumArr") ++ BSONDocument("stadiumArr" -> fixedField)
}

请注意cleanWriter是隐式的,这意味着当对集合进行插入调用时,它将是选择使用的那个。

现在,这是一大堆工作,如果您正在使用插件/模块进行游戏,可以让您使用JSONCollection,那么您可以通过定义play json { {3}}和Reads。如果您查看文档,您会看到读取特征具有有用的map函数,您可以使用它将一个Read转换为另一个。

所以,你有:

val jsonReads = Json.reads[FormData]
implicit val cleanReads = jsonReads.map(formData => formData.copy(stadiumArr = formData.stadiumArr.map(_.filterNot(_.isEmpty))))

同样,因为只有干净的Reads是隐式的,mongo的收集方法才会使用它。

现在,所有这些都说,在数据库级别执行此操作是一回事,但实际上,我个人认为您应该在表单级别处理此问题。

val form = Form(
    mapping(
      "_id" -> ignored(Option.empty[BSONObjectID]),
      "stadiumArr" -> optional(list(text)),
      ...
    )(FormData.apply)(FormData.unapply)
 )

主要是因为,惊喜,表格有办法解决这个问题。具体来说,Writes类本身。如果你看那里,你会找到一个transform方法,你可以用来轻松过滤掉空值。只需在需要修改的映射上调用它,例如:

"stadiumArr" -> optional(
            list(text).transform(l => l.filter(_.nonEmpty), l => l.filter(_.nonEmpty))
        )

如果您不习惯阅读scaladoc中的签名,请详细了解此方法。

def
transform[B](f1: (T) ⇒ B, f2: (B) ⇒ T): Mapping[B]

说通过在类型transform的某些映射上调用Mapping[T],您可以创建类型为Mapping[B]的新映射。为此,您必须提供从一个转换为另一个的函数。所以上面的代码导致列表映射(Mapping[List[String]])成为Mapping[List[String]](这里的类型没有改变),但是当它这样做时它会删除所有空元素。如果我稍微破解这段代码,可能会更清楚:

def convertFromTtoB(list: List[String]): List[String] = list.filter(_.nonEmpty)
def convertFromBtoT(list: List[String]): List[String] = list.filter(_.nonEmpty)
...
list(text).transform(convertFromTtoB, convertFromBtoT)

您可能想知道为什么需要同时提供这两个,原因是当您调用Form.fill并且表单填充了值时,将调用第二个方法,以便数据进入播放表单的格式期待。如果类型实际发生变化,则更为明显。例如,如果您有一个文本区域,人们可以在其中输入CSV,但您想将其映射到具有正确List [String]的表单模型,您可能会执行以下操作:

def convertFromTtoB(raw: String): List[String] = raw.split(",").filter(_.nonEmpty)
    def convertFromBtoT(list: List[String]): String = list.mkString(",")
    ...
    text.transform(convertFromTtoB, convertFromBtoT)

请注意,当我过去做过这个时,有时候我必须编写一个单独的方法,如果我不想完全指定所有类型,只需将其传入,但是你应该能够在这里工作,给出映射变换方法的文档和类型签名。

我建议在表单绑定中执行此操作的原因是因为表单/控制器应该是一个关注处理用户数据和清理的问题。但是你总是可以有多层清洁等等,安全性也不错!