【问题标题】:Prevent empty values in an array being inserted into Mongo collection防止将数组中的空值插入到 Mongo 集合中
【发布时间】:2017-12-14 16:52:32
【问题描述】:

我试图防止将空值插入我的 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))
}

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

【问题讨论】:

  • "但这不起作用" ...是什么意思?错误?正如已经提供的那样,定义Writes[List[String]] 对我来说似乎没用。 scala> implicitly[Writes[List[String]]].writes(List("foo", "bar")) res1: play.api.libs.json.JsValue = ["foo","bar"]
  • 在插入 MongoDB 之前只过滤列表。
  • @cchantep - 是的,除非您需要删除如上所述插入的空值,否则它是没用的。奇怪的是不是错误,而是挂起。
  • @AlexeyRomanov - 我正在使用如上所示的隐式值过滤列表
  • 顺便说一句,我现在有 def writes(list: List[String]): JsValue = Json.arr(list.filterNot(_.isEmpty).toIndexedSeq) 的方法,但这会插入一个数组作为值 - 这意味着我有一个嵌套数组。越来越近

标签: mongodb scala playframework


【解决方案1】:

如果不知道您正在使用的特定版本或库,很难给您答案,但由于您链接到播放 2.6 文档,我会假设这就是您在那里使用的。我要做的另一个假设是您使用的是响应式 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,那么您只需定义 play json ReadsWrites 就可以了。如果您查看文档,您会发现 reads trait 有一个有用的 map 函数,您可以使用它来将一个 Reads 转换为另一个。

所以,你有:

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

再一次,因为只有 clean Reads 是隐式的,所以 mongo 的收集方法会使用它。


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

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

主要是因为出乎意料,form 有办法处理这个问题。具体来说,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]

表示通过在Mapping[T] 类型的某些映射上调用transform,您可以创建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)

请注意,当我过去这样做时,如果我不想完全指定所有类型,有时我不得不编写一个单独的方法并将其传入,但你应该能够从这里给出了映射转换方法的文档和类型签名。

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

【讨论】:

  • 是的,抱歉 - 我正在使用插件/模块进行 play。那里有一些逻辑推理;只是无法正确转换。不确定我是否完全了解这些参数中发生的情况。
  • @jesusg_forceHarris 我已经更新了有关 transform 方法的答案以解释更多
  • 好的,所以我了解 transform 的函数签名以及原因(正如您提到的填写表格),但我仍然无法让 "stadiumArr" -> optional(list(text).transform(l => l.filter(_.nonEmpty), l => l.filter(_.nonEmpty))) 工作。 IntelliJ 在第二个参数上给了我这个警告:Type mismatch: expected: (NotInferedB) => List[String], actual: (Nothing) => Any
  • 我刚刚注意到,当放入您提到的方法时,这确实有效。奇怪,但我认为这个解决方案可能是最经济的性能明智
【解决方案2】:

我已经为此努力了(在编写和测试时似乎总是很明显):

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

但我很想知道如何

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

正如@cchantep 建议的那样

【讨论】:

  • RE:cchantep 的建议,你用什么来读/写 mongo?反应式 mongo BSON 或反应式 mongo 播放模块/插件?如果你能告诉我更多关于你正在使用哪个库的信息,我可以为你写一些关于映射现有读取的内容
猜你喜欢
  • 2016-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-12
  • 1970-01-01
相关资源
最近更新 更多