【问题标题】:22 fields limit in Scala 2.11 + Play Framework 2.3 Case classes and functionsScala 2.11 + Play Framework 2.3 案例类和函数中的 22 个字段限制
【发布时间】:2014-06-27 14:39:21
【问题描述】:

Scala 2.11 已发布,案例类的 22 个字段限制似乎已修复(Scala IssueRelease Notes)。

这对我来说一直是个问题,因为我使用案例类对 Play + Postgres Async 中具有超过 22 个字段的数据库实体进行建模。我在 Scala 2.10 中的解决方案是将模型分解为多个案例类,但我发现这个解决方案很难维护和扩展,我希望在切换到 Play 2.3.0-RC1 + Scala 2.11 后我可以实现如下所述的内容。 0:

package entities

case class MyDbEntity(
  id: String,
  field1: String,
  field2: Boolean,
  field3: String,
  field4: String,
  field5: String,
  field6: String,
  field7: String,
  field8: String,
  field9: String,
  field10: String,
  field11: String,
  field12: String,
  field13: String,
  field14: String,
  field15: String,
  field16: String,
  field17: String,
  field18: String,
  field19: String,
  field20: String,
  field21: String,
  field22: String,
  field23: String,
) 

object MyDbEntity {
  import play.api.libs.json.Json
  import play.api.data._
  import play.api.data.Forms._

  implicit val entityReads = Json.reads[MyDbEntity]
  implicit val entityWrites = Json.writes[MyDbEntity]
}

上面的代码编译失败,“读取”和“写入”都显示以下消息:

No unapply function found

将“读取”和“写入”更新为:

  implicit val entityReads: Reads[MyDbEntity] = (
    (__ \ "id").read[Long] and
    (__ \ "field_1").read[String]
    ........
  )(MyDbEntity.apply _)  

  implicit val postWrites: Writes[MyDbEntity] = (
    (__ \ "id").write[Long] and
    (__ \ "user").write[String]
    ........
  )(unlift(MyDbEntity.unapply))

也不行:

  implementation restricts functions to 22 parameters

  value unapply is not a member of object models.MyDbEntity

我的理解是 Scala 2.11 在功能上仍然存在一些限制,并且像我上面描述的那样还不可能。这对我来说似乎很奇怪,因为如果一个主要用户案例仍然不受支持,我看不到解除案例类限制的好处,所以我想知道我是否遗漏了什么。

非常欢迎指出问题或实施细节!谢谢!

【问题讨论】:

  • 看看relevant pull request description:作为限制提到的第一件事是缺少unapply 用于> 22 个类,这样做是有原因的(AFAIR,在类文件大小)
  • 具有 >22 参数的案例类不能有 unapply,因为它必须转换为 X>22 的 TupleX,并且元组仍然限制为 22。可悲的是Json.format[MyCaseClass](基于宏的解决方案)不需要有这个限制(它可以意识到它是一个案例类并直接提取字段,而不像模式匹配那样取消应用),但它目前正在寻找 @987654334 @ 并失败...
  • 对于那些感兴趣的人,这里有一个取消此限制的票证跟踪:github.com/playframework/playframework/issues/3174
  • 票仍然开放。 Scala 2.11.1 在 2.3 中 >22 是否仍然存在问题?为一个新项目研究不同的堆栈并想知道这是否会成为一个问题。谢谢

标签: scala playframework-2.3


【解决方案1】:

在 dotty (Scala 3) 中,现在您可以在 Case 类中使用超过 22 个字段。

【讨论】:

    【解决方案2】:

    看起来这一切都很好。

    +22 字段案例类格式化程序和更多用于 play-json https://github.com/xdotai/play-json-extensions

    支持 Scala 2.11.x、2.12.x 和 2.13.x 并播放 2.3、2.4、2.5 和 2.7

    并且在play-json issue 中被引用为首选解决方案(但尚未合并)

    【讨论】:

      【解决方案3】:

      我尝试了另一个答案中提出的基于 Shapeless “自动类型类派生”的解决方案,但它不适用于我们的模型 - 引发 StackOverflow 异常(具有约 30 个字段的案例类和 4 个具有 4-10 的案例类的嵌套集合字段)。

      所以,我们采用了this 解决方案,它完美无缺。通过编写 ScalaCheck 测试确认了这一点。请注意,它需要 Play Json 2.4。

      【讨论】:

        【解决方案4】:

        案例类可能不起作用的案例;其中一种情况是案例类不能超过 22 个字段。另一种情况可能是您事先不了解模式。在这种方法中,数据作为行对象的 RDD 加载。 Schema 是使用 StructType 和 StructField 对象分别创建的,它们分别代表一个表和一个字段。 Schema应用于行RDD以创建DataFrame in Spark

        【讨论】:

          【解决方案5】:

          您可以使用 Jackson 的 Scala 模块。 Play 的 json 功能建立在 Jackson scala 之上。我不知道为什么他们在这里设置了 22 个字段限制,而杰克逊支持超过 22 个字段。一个函数调用永远不能使用超过 22 个参数可能是有道理的,但是我们可以在一个 DB 实体中拥有数百个列,所以这里的这个限制是荒谬的,并且使 Play 成为一个生产力较低的玩具。 看看这个:

          import com.fasterxml.jackson.databind.ObjectMapper
          import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
          import com.fasterxml.jackson.module.scala.DefaultScalaModule
          
          object JacksonUtil extends App {
            val mapper = new ObjectMapper with ScalaObjectMapper
            mapper.registerModule(DefaultScalaModule)
          
          
            val t23 = T23("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w")
          
            println(mapper.writeValueAsString(t23))
           }
          case class T23(f1:String,f2:String,f3:String,f4:String,f5:String,f6:String,f7:String,
              f8:String,f9:String,f10:String,f11:String,f12:String,f13:String,f14:String,f15:String,
              f16:String,f17:String,f18:String,f19:String,f20:String,f21:String,f22:String,f23:String)
          

          【讨论】:

            【解决方案6】:

            我正在制作一个图书馆。请试试这个https://github.com/xuwei-k/play-twenty-three

            【讨论】:

            • 没有阅读所有代码,但我至少看到了对 252 个字段的引用!!!大声笑:)
            • "640K 应该对每个人都足够了"
            【解决方案7】:

            这是不可能的,开箱即用,有几个原因:

            但是可以通过以下任一方式绕过第二点:

            首先,创建缺失的FunctionalBuilder

            class CustomFunctionalBuilder[M[_]](canBuild: FunctionalCanBuild[M]) extends FunctionalBuilder {
            
                class CustomCanBuild22[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22](m1: M[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21], m2: M[A22]) {
            def ~[A23](m3: M[A23]) = new CustomCanBuild23[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23](canBuild(m1, m2), m3)
            
              def and[A23](m3: M[A23]) = this.~(m3)
            
              def apply[B](f: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => B)(implicit fu: Functor[M]): M[B] =
              fu.fmap[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22, B](canBuild(m1, m2), { case a1 ~ a2 ~ a3 ~ a4 ~ a5 ~ a6 ~ a7 ~ a8 ~ a9 ~ a10 ~ a11 ~ a12 ~ a13 ~ a14 ~ a15 ~ a16 ~ a17 ~ a18 ~ a19 ~ a20 ~ a21 ~ a22 => f(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) })
            
              def apply[B](f: B => (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22))(implicit fu: ContravariantFunctor[M]): M[B] =
              fu.contramap(canBuild(m1, m2), (b: B) => { val (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) = f(b); new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(a1, a2), a3), a4), a5), a6), a7), a8), a9), a10), a11), a12), a13), a14), a15), a16), a17), a18), a19), a20), a21), a22) })
            
              def apply[B](f1: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => B, f2: B => (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22))(implicit fu: InvariantFunctor[M]): M[B] =
              fu.inmap[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22, B](
                canBuild(m1, m2), { case a1 ~ a2 ~ a3 ~ a4 ~ a5 ~ a6 ~ a7 ~ a8 ~ a9 ~ a10 ~ a11 ~ a12 ~ a13 ~ a14 ~ a15 ~ a16 ~ a17 ~ a18 ~ a19 ~ a20 ~ a21 ~ a22 => f1(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) },
                (b: B) => { val (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) = f2(b); new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(a1, a2), a3), a4), a5), a6), a7), a8), a9), a10), a11), a12), a13), a14), a15), a16), a17), a18), a19), a20), a21), a22) }
              )
            
              def join[A >: A1](implicit witness1: <:<[A, A1], witness2: <:<[A, A2], witness3: <:<[A, A3], witness4: <:<[A, A4], witness5: <:<[A, A5], witness6: <:<[A, A6], witness7: <:<[A, A7], witness8: <:<[A, A8], witness9: <:<[A, A9], witness10: <:<[A, A10], witness11: <:<[A, A11], witness12: <:<[A, A12], witness13: <:<[A, A13], witness14: <:<[A, A14], witness15: <:<[A, A15], witness16: <:<[A, A16], witness17: <:<[A, A17], witness18: <:<[A, A18], witness19: <:<[A, A19], witness20: <:<[A, A20], witness21: <:<[A, A21], witness22: <:<[A, A22], fu: ContravariantFunctor[M]): M[A] =
              apply[A]((a: A) => (a: A1, a: A2, a: A3, a: A4, a: A5, a: A6, a: A7, a: A8, a: A9, a: A10, a: A11, a: A12, a: A13, a: A14, a: A15, a: A16, a: A17, a: A18, a: A19, a: A20, a: A21, a: A22))(fu)
            
              def reduce[A >: A1, B](implicit witness1: <:<[A1, A], witness2: <:<[A2, A], witness3: <:<[A3, A], witness4: <:<[A4, A], witness5: <:<[A5, A], witness6: <:<[A6, A], witness7: <:<[A7, A], witness8: <:<[A8, A], witness9: <:<[A9, A], witness10: <:<[A10, A], witness11: <:<[A11, A], witness12: <:<[A12, A], witness13: <:<[A13, A], witness14: <:<[A14, A], witness15: <:<[A15, A], witness16: <:<[A16, A], witness17: <:<[A17, A], witness18: <:<[A18, A], witness19: <:<[A19, A], witness20: <:<[A20, A], witness21: <:<[A21, A], witness22: <:<[A22, A], fu: Functor[M], reducer: Reducer[A, B]): M[B] =
              apply[B]((a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) =>  reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.unit(a1: A), a2: A), a3: A), a4: A), a5: A), a6: A), a7: A), a8: A), a9: A), a10: A), a11: A), a12: A), a13: A), a14: A), a15: A), a16: A), a17: A), a18: A), a19: A), a20: A), a21: A), a22: A))(fu)
            
              def tupled(implicit v: VariantExtractor[M]): M[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)] =
              v match {
                case FunctorExtractor(fu) => apply { (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) }(fu)
                case ContravariantFunctorExtractor(fu) => apply[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)] { (a: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)) => (a._1, a._2, a._3, a._4, a._5, a._6, a._7, a._8, a._9, a._10, a._11, a._12, a._13, a._14, a._15, a._16, a._17, a._18, a._19, a._20, a._21, a._22) }(fu)
                case InvariantFunctorExtractor(fu) => apply[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)]({ (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) }, { (a: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)) => (a._1, a._2, a._3, a._4, a._5, a._6, a._7, a._8, a._9, a._10, a._11, a._12, a._13, a._14, a._15, a._16, a._17, a._18, a._19, a._20, a._21, a._22) })(fu)
                }
            
              }
            
              class CustomCanBuild23[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23](m1: M[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22], m2: M[A23]) {
              }
            
            }
            

            然后提供您自己的FunctionalBuilderOps 实例:

            implicit def customToFunctionalBuilderOps[M[_], A](a: M[A])(implicit fcb: FunctionalCanBuild[M]) = new CustomFunctionalBuilderOps[M, A](a)(fcb)
            

            最后,关于第一点,我发了pull request 来尝试简化当前的实现。

            【讨论】:

            【解决方案8】:

            我们还将模型分解为多个案例类,但这很快变得难以管理。我们使用Slick 作为我们的对象关系映射器,Slick 2.0 带有一个code generator,我们用它来生成类(它带有应用方法和复制构造函数来模仿案例类)以及从 Json 实例化模型的方法(我们不要自动生成将模型转换为 Json 的方法,因为我们有太多特殊情况需要处理)。使用 Slick 代码生成器不需要您使用 Slick 作为对象关系映射器。

            这是代码生成器输入的一部分 - 此方法采用 JsObject 并使用它来实例化新模型或更新现有模型。

            private def getItem(original: Option[${name}], json: JsObject, trackingData: TrackingData)(implicit session: scala.slick.session.Session): Try[${name}] = {
              preProcess("$name", columnSet, json, trackingData).flatMap(updatedJson => {
                ${indent(indent(indent(entityColumnsSansId.map(c => s"""val ${c.name}_Parsed = parseJsonField[${c.exposedType}](original.map(_.${c.name}), "${c.name}", updatedJson, "${c.exposedType}")""").mkString("\n"))))}
                val errs = Seq(${indent(indent(indent(indent(entityColumnsSansId.map(c => s"${c.name}_Parsed.map(_ => ())").mkString(", ")))))}).condenseUnit
                for {
                  _ <- errs
                  ${indent(indent(indent(indent(entityColumnsSansId.map(c => s"${c.name}_Val <- ${c.name}_Parsed").mkString("\n")))))}
                } yield {
                  original.map(_.copy(${entityColumnsSansId.map(c => s"${c.name} = ${c.name}_Val").mkString(", ")}))
                    .getOrElse(${name}.apply(id = None, ${entityColumnsSansId.map(c => s"${c.name} = ${c.name}_Val").mkString(", ")}))
                }
              })
            }
            

            例如,使用我们的 ActivityLog 模型会生成以下代码。如果“original”是None,那么这是从“createFromJson”方法调用的,我们实例化一个新模型;如果“original”是 Some(activityLog),那么这是从“updateFromJson”方法调用的,我们更新现有模型。在“val errs = ...”行上调用的“condenseUnit”方法需要一个 Seq[Try[Unit]] 并产生一个 Try[Unit];如果 Seq 有任何错误,则 Try[Unit] 连接异常消息。不会生成 parseJsonField 和 parseField 方法 - 它们只是从生成的代码中引用。

            private def parseField[T](name: String, json: JsObject, tpe: String)(implicit r: Reads[T]): Try[T] = {
              Try((json \ name).as[T]).recoverWith {
                case e: Exception => Failure(new IllegalArgumentException("Failed to parse " + Json.stringify(json \ name) + " as " + name + " : " + tpe))
              }
            }
            
            def parseJsonField[T](default: Option[T], name: String, json: JsObject, tpe: String)(implicit r: Reads[T]): Try[T] = {
              default match {
                case Some(t) => if(json.keys.contains(name)) parseField(name, json, tpe)(r) else Try(t)
                case _ => parseField(name, json, tpe)(r)
              }
            }
            
            private def getItem(original: Option[ActivityLog], json: JsObject, trackingData: TrackingData)(implicit session: scala.slick.session.Session): Try[ActivityLog] = {
              preProcess("ActivityLog", columnSet, json, trackingData).flatMap(updatedJson => {
                val user_id_Parsed = parseJsonField[Option[Int]](original.map(_.user_id), "user_id", updatedJson, "Option[Int]")
                val user_name_Parsed = parseJsonField[Option[String]](original.map(_.user_name), "user_name", updatedJson, "Option[String]")
                val item_id_Parsed = parseJsonField[Option[String]](original.map(_.item_id), "item_id", updatedJson, "Option[String]")
                val item_item_type_Parsed = parseJsonField[Option[String]](original.map(_.item_item_type), "item_item_type", updatedJson, "Option[String]")
                val item_name_Parsed = parseJsonField[Option[String]](original.map(_.item_name), "item_name", updatedJson, "Option[String]")
                val modified_Parsed = parseJsonField[Option[String]](original.map(_.modified), "modified", updatedJson, "Option[String]")
                val action_name_Parsed = parseJsonField[Option[String]](original.map(_.action_name), "action_name", updatedJson, "Option[String]")
                val remote_ip_Parsed = parseJsonField[Option[String]](original.map(_.remote_ip), "remote_ip", updatedJson, "Option[String]")
                val item_key_Parsed = parseJsonField[Option[String]](original.map(_.item_key), "item_key", updatedJson, "Option[String]")
                val created_at_Parsed = parseJsonField[Option[java.sql.Timestamp]](original.map(_.created_at), "created_at", updatedJson, "Option[java.sql.Timestamp]")
                val as_of_date_Parsed = parseJsonField[Option[java.sql.Timestamp]](original.map(_.as_of_date), "as_of_date", updatedJson, "Option[java.sql.Timestamp]")
                val errs = Seq(user_id_Parsed.map(_ => ()), user_name_Parsed.map(_ => ()), item_id_Parsed.map(_ => ()), item_item_type_Parsed.map(_ => ()), item_name_Parsed.map(_ => ()), modified_Parsed.map(_ => ()), action_name_Parsed.map(_ => ()), remote_ip_Parsed.map(_ => ()), item_key_Parsed.map(_ => ()), created_at_Parsed.map(_ => ()), as_of_date_Parsed.map(_ => ())).condenseUnit
                for {
                  _ <- errs
                  user_id_Val <- user_id_Parsed
                  user_name_Val <- user_name_Parsed
                  item_id_Val <- item_id_Parsed
                  item_item_type_Val <- item_item_type_Parsed
                  item_name_Val <- item_name_Parsed
                  modified_Val <- modified_Parsed
                  action_name_Val <- action_name_Parsed
                  remote_ip_Val <- remote_ip_Parsed
                  item_key_Val <- item_key_Parsed
                  created_at_Val <- created_at_Parsed
                  as_of_date_Val <- as_of_date_Parsed
                } yield {
                  original.map(_.copy(user_id = user_id_Val, user_name = user_name_Val, item_id = item_id_Val, item_item_type = item_item_type_Val, item_name = item_name_Val, modified = modified_Val, action_name = action_name_Val, remote_ip = remote_ip_Val, item_key = item_key_Val, created_at = created_at_Val, as_of_date = as_of_date_Val))
                    .getOrElse(ActivityLog.apply(id = None, user_id = user_id_Val, user_name = user_name_Val, item_id = item_id_Val, item_item_type = item_item_type_Val, item_name = item_name_Val, modified = modified_Val, action_name = action_name_Val, remote_ip = remote_ip_Val, item_key = item_key_Val, created_at = created_at_Val, as_of_date = as_of_date_Val))
                }
              })
            }
            

            【讨论】:

              猜你喜欢
              • 2013-12-28
              • 1970-01-01
              • 1970-01-01
              • 2015-03-25
              • 1970-01-01
              • 1970-01-01
              • 2017-03-21
              相关资源
              最近更新 更多