0

我有一个 json 对象:

{"fieldA": "valueForA", "fieldB":"valueForB", "fieldC":"valueForC"}

我想将它映射到我的案例类:

case class BetterNamed(gloriousAlpha:String, repugnantBeta:String, ingratiousCharlie:String) {}

但我不想写所有:

val read:Reads[BetterNamed] = (
  (__ \ "fieldA").read[String] and
  (__ \ "fieldB").read[String] and
  (__ \ "fieldC").read[String]
)(BetterNamed.apply _)

因为我重复自己太多了。

我想写的是:

val read:Reads[BetterNamed] = (Seq(
  "fieldA",
  "fieldB",
  "fieldC").map( (__ \ _).read[String] ).reduce(_ and _)(BetterNamed.apply _)

我的系统中有许多应用程序用于这种形式的字段访问,但是上面编写的代码无法编译,告诉我 BetterNamed 不接受参数。

谁能告诉我如何更正上述代码以进行编译?
我的第一个猜测是它需要某个地方的类型提示......

如果不可能,我将非常感谢解释为什么不可能

4

1 回答 1

2

为什么它不起作用?

类型的 reduce 方法Seq有如下声明:

reduce[A1 >: A](op: (A1, A1) => A1): A1

这意味着减少功能op需要有一个类型

(A1, A1) => A1)

对于一些A1. 现在让我们看看我们想要执行的归约的中间结果的类型(x 是 type 的值Reads[String]

x and x : FunctionalBuilder[Reads]#CanBuild2[String,String]
x and x and x : FunctionalBuilder[Reads]#CanBuild3[String,String,String]

您可以在此处找到定义的这些类型:http ://www.playframework.com/documentation/api/2.1.x/scala/index.html#play.api.libs.functional.FunctionalBuilder

事实是,该and方法没有很好的多态类型 reduce 方法的需求,所以我们需要自己找一些其他的方法来解决我们的问题。

这个问题与处理元组而不是列表的问题非常相似——没有非常简单的方法可以编写一个通用地处理任意长度元组的函数。但是,我们仍然可以做很多事情。

使用隐式

解决这个问题的一种方法是对每个大小的元组使用专门的隐式声明。如果您不必处理很多不同的情况,这是一个不错且干净的惯用 scala 解决方案:

trait Reducer[T] {
  def reduce(t : T) : Reads[T]
}

object TupleReducer {
  implicit val reducer1 = new Reducer[String] {
    def reduce(s : String) = (__ \ s).read[String]
  }

  implicit val reducer2 = new Reducer[(String, String)] {
    def reduce(s : (String, String)) = (
        (__ \ s._1).read[String] and
        (__ \ s._2).read[String]
      ).tupled
    }

    implicit val reducer3 = new Reducer[(String, String, String)] {
        def reduce(s : (String, String, String)) = (
            (__ \ s._1).read[String] and 
            (__ \ s._2).read[String] and 
            (__ \ s._3).read[String]
          ).tupled
    }

    def make[T, Res](t : T, f : T => Res)(implicit reducer : Reducer[T]) = {           
      reducer.reduce(t).map(f)
    }
}

在上面的代码中,我们为不同的元组大小创建了一个不同的 reducer,将其作为隐式参数注入我们的make函数,最后一步使用 map 来创建Reads[(String,String,String)]一个Reads[BetterNamed].

这是一个使用示例:

import TupleReducer._
implicit val read : Reads[BetterNamed] = TupleReducer.make(
  ("fieldA", "fieldB", "fieldC"),   
  BetterNamed.apply _ tupled
)

更棘手的方式

但是,如果我们真的想要 Lists(或其他序列类型,我刚刚以列表为例)的自由呢?如何做到这一点的想法取自这个stackoverflow线程:Is there way to create tuple from list(without codegeneration)? 我从中借用了以通用方式从列表中创建元组的方法。有了这个装备,我们可以写ListReducer

object ListReducer {
  def map(xs : List[String]) : List[Reads[String]] = xs.map(x => (__ \ x).read[String])

  def reduce(xs : List[Reads[String]]) : Reads[List[String]] = xs match {
    case Nil => Reads[List[String]] { _ => JsSuccess(Nil) }
    case (head::rest) => for {
      h <- head
      r <- reduce(rest)
    } yield(h::r)
  }

  def toTuple[A <: Object,Product](as:List[A]) : Product = {
    val tupleClass = Class.forName("scala.Tuple" + as.size)
    tupleClass.getConstructors.apply(0).newInstance(as:_*).asInstanceOf[Product]
  }

  def createTransformer[X,Y](f : X => Y)(xs : List[String]) : Y = {
    f(toTuple[String, X](xs))
  }

  def make[X,Y](xs : List[String], fn : X => Y) : Reads[Y] = {   
    reduce(map(xs)).map(createTransformer[X,Y](fn))
  }
}

这段代码将问题分为几个步骤:

  1. 首先,它List[Reads[String]]从字符串列表中创建,在方法中分别提取每个字段map
  2. 接下来,它Reads[List[String]]通过List[Reads[String]]分别应用每个读取和合并结果来创建。
  3. 它使用toTuple方法将其打包List[String]成相应的元组类型,可以将其输入到构造 BetterNamed 对象的 BetterNamed.apply 函数中。

这是一个使用示例:

implicit val read = ListReducer.make(
  List("fieldA", "fieldB", "fieldC"), 
  BetterNamed.apply _ tupled
)

我希望这会有所帮助。

于 2013-07-30T15:55:42.843 回答