带有内部案例类的case类的字符串列表

时间:2018-03-01 18:36:44

标签: scala shapeless

假设我有2个案例类别:

case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)

有一种很好的方式可以翻译"人类的字符串列表?像是:

def superMethod[A](params: List[String]): A = ???

val params: List[Any] = List("john", 100, "dollar")
superMethod(params) // => Human("john", Money(100, "dollar"))

基本上我只知道运行时的类型A

更新:我找到了〜我在找什么。看来我可以通过无形来做到这一点。 example我在github中找到了

2 个答案:

答案 0 :(得分:3)

这是一个适用于泛型类A的实现。

它依赖于运行时反射(也就是说,可以在运行时将不同的TypeTag传递给方法)。必须满足以下明显条件才能使用此方法:

  • A必须位于类路径上,否则可以由使用的类加载器
  • 加载
  • TypeTag必须在呼叫网站A处可用。

实际实现在Deserializer对象中。然后是一个小小的演示。

反序列化器:

import scala.reflect.runtime.universe.{TypeTag, Type}

object Deserializer {

  /** Extracts an instance of type `A` from the 
    * flattened `Any` constructor arguments, and returns 
    * the constructed instance together with the remaining
    * unused arguments.
    */
  private def deserializeRecHelper(
    flattened: List[Any], 
    tpe: Type
  ): (Any, List[Any]) = {
    import scala.reflect.runtime.{universe => ru}

    // println("Trying to deserialize " + tpe + " from " + flattened)

    // println("Constructor alternatives: ")
    // val constructorAlternatives = tpe.
    //   member(ru.termNames.CONSTRUCTOR).
    //   asTerm.
    //   alternatives.foreach(println)

    val consSymb = tpe.
      member(ru.termNames.CONSTRUCTOR).
      asTerm.
      alternatives(0).
      asMethod

    val argsTypes: List[Type] = consSymb.paramLists(0).map(_.typeSignature)
    if (tpe =:= ru.typeOf[String] || argsTypes.isEmpty) {
      val h :: t = flattened
      (h, t)
    } else {
      val args_rems: List[(Any, List[Any])] = argsTypes.scanLeft(
        (("throwaway-sentinel-in-deserializeRecHelper": Any), flattened)
      ) { 
        case ((_, remFs), t) => 
        deserializeRecHelper(remFs, t)
      }.tail

      val remaining: List[Any] = args_rems.last._2
      val args: List[Any] = args_rems.unzip._1

      val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
      val classMirror = runtimeMirror.reflectClass(tpe.typeSymbol.asClass)
      val cons = classMirror.reflectConstructor(consSymb)

      // println("Build constructor arguments array for " + tpe + " : " + args)

      val obj = cons.apply(args:_*)
      (obj, remaining)
    }
  }

  def deserialize[A: TypeTag](flattened: List[Any]): A = {
    val (a, rem) = deserializeRecHelper(
      flattened, 
      (implicitly: TypeTag[A]).tpe
    )

    require(
      rem.isEmpty, 
      "Superfluous arguments remained after deserialization: " + rem
    )

    a.asInstanceOf[A]
  }
}

演示:

case class Person(id: String, money: Money, pet: Pet, lifeMotto: String)
case class Money(num: Int, currency: String)
case class Pet(color: String, species: Species)
case class Species(description: String, name: String)

object Example {
  def main(args: Array[String]): Unit = {
    val data = List("Bob", 42, "USD", "pink", "invisible", "unicorn", "what's going on ey?")
    val p = Deserializer.deserialize[Person](data)
    println(p)
  }
}

输出:

Person(Bob,Money(42,USD),Pet(pink,Species(invisible,unicorn)),what's going on ey?)
讨论

此实现不仅限于case类,但它要求每个“Tree-node-like”类只有一个接受

的构造函数。
  • 原始类型(Int,Float)或
  • 字符串或
  • 其他“树节点类”。

请注意,该任务有点不合理:说所有构造函数参数在单个列表中展平是什么意思?鉴于类Person(name: String, age: Int)List[Any]是否将name的每个字节都包含在一个单独的条目中?可能不是。因此,字符串由反序列化器以特殊方式处理,并且出于同样的原因不支持所有其他类似集合的实体(不清楚在何处停止解析,因为集合的大小未知)。

答案 1 :(得分:2)

如果A不是泛型类型,但实际上是Human,则可以使用案例类的伴随对象Human:

object Human {
  def fromList(list: List[String]): Human = list match {
    case List(name, amount, currency) => Human(name, Money(amount.toInt, currency))
    case _ => handle corner case
  }
}

您可以致电:

Human.fromList(List("john", "100", "dollar"))

为了确保安全,请不要忘记处理大小不会超过3的列表;和第二个元素不能转换为Int的列表:

import scala.util.Try

object Human {
  def fromList(list: List[String]): Option[Human] = list match {
    case List(name, amount, currency) =>
      Try(Human(name, Money(amount.toInt, currency))).toOption
    case _ => None
  }
}

编辑:根据您的上一条评论,您可能会发现这很有用:

case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)
case class SomethingElse(whatever: Double)

object Mapper {
  def superMethod(list: List[String]): Option[Any] =
    list match {
      case List(name, amount, currency) =>
        Try(Human(name, Money(amount.toInt, currency))).toOption
      case List(whatever) => Try(SomethingElse(whatever.toDouble)).toOption
      case _ => None
    }
}

println(Mapper.superMethod(List("john", 100, "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod(List(17d)))
> Some(SomethingElse(17.0))

或者:

object Mapper {
  def superMethod[A](list: List[String]): Option[A] =
    (list match {
      case List(name, amount, currency) =>
        Try(Human(name, Money(amount, currency))).toOption
      case List(whatever) =>
        Try(SomethingElse(whatever.toDouble)).toOption
      case _ => None
    }).map(_.asInstanceOf[A])
}

println(Mapper.superMethod[Human](List("john", "100", "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod[SomethingElse](List("17.2")))
> Some(SomethingElse(17.0))