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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了防止插入Mongo集合的数组中的空值相关的知识,希望对你有一定的参考价值。

我试图阻止空值插入我的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.6文档,我会假设您正在使用它。我要做的另一个假设是你正在使用reactive-mongo库。你是否使用该库的播放插件是我在这里给你两个不同答案的原因:

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

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是隐式的,这意味着当对集合进行插入调用时,它将被选择使用。


现在,这一切都是一堆工作,如果你使用插件/模块播放让你使用JSONCollections然后你可以通过定义播放json ReadsWrites。如果你查看文档,你会看到read trait有一个有用的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)
 )

主要是因为,惊喜,形式有办法对付这个。具体来说,mapping类本身。如果你看那里你会找到一个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)

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

我建议在表单绑定中这样做的原因是因为表单/控制器应该是关注处理用户数据和清理的问题。但你总是可以有多层清洁等等,安全起见并不坏!

另一答案

我已经离开了这个(当它被编写和测试时总是显而易见的):

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

但我有兴趣知道如何

.map现有的Read而不是从头开始重新定义

正如@cchantep建议的那样

以上是关于防止插入Mongo集合的数组中的空值的主要内容,如果未能解决你的问题,请参考以下文章

SQL触发器用INSERT上的当前日期替换表中的空值?

Apache Spark:如何使用 Java 在 dataFrame 中的空值列中插入数据

将数据插入 MySQL 数据库时使用 PHP 替换 CSV 文件中的空值

去除数组中的空值

php怎么去除数组中的空值

如何序列化数组中的空值?