Scala 2.11 + Play Framework 2.3中的22个字段限制案例类别和function
斯卡拉2.11已经出来,22个字段限制的案例类似乎是固定的( 斯卡拉问题 , 发行说明 )。
这个问题对我来说已经有一段时间了,因为我使用案例类来模拟Play + Postgres Async中具有超过22个字段的数据库实体。 我在Scala 2.10中的解决scheme是将模型分解成多个case类,但是我发现这个解决scheme很难维护和扩展,我希望在切换到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] }
上面的代码无法编译“Reads”和“Writes”的以下消息:
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
我的理解是,斯卡拉2.11仍然有一些function的限制,像我上面描述的东西是不可能的。 这似乎很奇怪,因为我没有看到解除对案例类的限制的好处,如果它的主要用户案件仍然不被支持,所以我想知道如果我失去了一些东西。
指出问题或实施细节是值得欢迎的! 谢谢!
这是不可能的,出于以下几个原因:
-
首先,正如gourlaysama指出的那样,play-json库使用了scalamacros来避免粗体代码 ,而现在的代码依赖于
unapply
和apply
方法来获取字段。 这解释了你的问题中的第一个错误信息。 -
其次,play-json库依赖于一个函数库 ,它只能使用与之前的case class字段arity limit相对应的固定数量的参数 。 这解释了你的问题中的第二个错误信息。
但是可以通过以下两种方法绕过第二点:
-
使用无形的 自动Typeclass派生function。 纳文·加图已经写了一个很好的要求,所以做的exaclty。
-
覆盖默认的函数生成器
首先,创build缺less的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)
最后,关于第一点,我发出了一个拉请求 ,试图简化当前的实施。
我们也把我们的模型分成多个案例类,但是这很快变得难以pipe理。 我们使用Slick作为我们的对象关系映射器,而Slick 2.0附带了一个代码生成器 ,我们使用这个生成器来生成类(与apply方法和复制构造函数一起来模仿case类)以及从Json实例化模型的方法(我们不会自动生成方法将模型转换为Json,因为我们有太多的特殊情况需要处理)。 使用Slick代码生成器不需要使用Slick作为对象关系映射器。
这是代码生成器的input的一部分 – 这个方法需要一个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]连接exception消息。 不会生成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)) } }) }
你可以使用jackson的Scala模块。 Play的jsonfunctionbuild立在jacksonscala上。 我不知道为什么他们把22个字段限制在这里,而jackson支持超过22个字段。 一个函数调用永远不能使用超过22个参数,但是在一个DB实体中可以有数百个列,所以这里的限制是荒谬的,并且使玩起来不那么有效率的玩具。 看一下这个:
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)
案件类别可能不起作用的案件; 其中一种情况是案例类不能超过22个字段。 另一种情况可能是您事先不了解架构。 在这种方法中,数据作为行对象的RDD加载。 使用StructType和StructField对象(分别表示一个表和一个字段)分别创build模式。 将Schema应用于行RDD以在Spark中创buildDataFrame 。
我在做一个图书馆。 请尝试这个https://github.com/xuwei-k/play-twenty-ree
我试着在另一个答案中提出无形的“自动Typeclass派生”的解决scheme,并没有为我们的模型工作 – 抛出StackOverflowexception(案例类约30字段和4个嵌套集合的案件类4-10字段)。
所以,我们已经采用了这个解决scheme,它的工作完美无瑕。 证实通过编写ScalaChecktesting。 注意,它需要Play Json 2.4。