将哈希映射键/值对映射到Scala中的命名构造函数参数

时间:2011-09-17 15:03:15

标签: scala

是否可以将Map的键值对映射到具有命名参数的Scala构造函数?

即,给定

class Person(val firstname: String, val lastname: String) {
    ...
}

...如何使用像

这样的地图创建Person的实例
val args = Map("firstname" -> "John", "lastname" -> "Doe", "ignored" -> "value")

我最终想要实现的是将Node4J Node对象映射到Scala值对象的好方法。

4 个答案:

答案 0 :(得分:11)

这里的关键见解是构造函数参数名称​​是可用,因为它们是构造函数创建的字段的名称。所以如果构造函数对其参数没有任何作用,只是将它们分配给字段,那么我们可以忽略它并直接使用这些字段。

我们可以使用:

def setFields[A](o : A, values: Map[String, Any]): A = {
  for ((name, value) <- values) setField(o, name, value)
  o
}

def setField(o: Any, fieldName: String, fieldValue: Any) {
  // TODO - look up the class hierarchy for superclass fields
  o.getClass.getDeclaredFields.find( _.getName == fieldName) match {
    case Some(field) => {
      field.setAccessible(true)
      field.set(o, fieldValue)
    }
    case None =>
      throw new IllegalArgumentException("No field named " + fieldName)
  }

我们可以打电话给一个空白的人:

test("test setFields") {
  val p = setFields(new Person(null, null, -1), Map("firstname" -> "Duncan", "lastname" -> "McGregor", "age" -> 44))
  p.firstname should be ("Duncan")
  p.lastname should be ("McGregor")
  p.age should be (44)
}

当然,我们可以通过一点拉皮条来做得更好:

implicit def any2WithFields[A](o: A) = new AnyRef {
  def withFields(values: Map[String, Any]): A = setFields(o, values)
  def withFields(values: Pair[String, Any]*): A = withFields(Map(values :_*))
}

以便您可以致电:

new Person(null, null, -1).withFields("firstname" -> "Duncan", "lastname" -> "McGregor", "age" -> 44)

如果必须调用构造函数很烦人,Objenesis会让你忽略缺少无参数的构造函数:

val objensis = new ObjenesisStd 

def create[A](implicit m: scala.reflect.Manifest[A]): A = 
  objensis.newInstance(m.erasure).asInstanceOf[A]

现在我们可以将两者结合起来写

create[Person].withFields("firstname" -> "Duncan", "lastname" -> "McGregor", "age" -> 44)

答案 1 :(得分:1)

您在评论中提到您正在寻找基于反射的解决方案。看看带有提取器的JSON库,它们可以做类似的事情。例如,lift-json有一些examples

case class Child(name: String, age: Int, birthdate: Option[java.util.Date])

val json = parse("""{ "name": null, "age": 5, "birthdate": null }""")
json.extract[Child] == Child(null, 5, None)

要获得所需内容,可以将Map[String, String]转换为JSON格式,然后运行案例类提取器。或者您可以查看JSON库是如何implemented using reflection

答案 2 :(得分:1)

我猜你有不同arity的域类,所以这是我的建议。 (以下所有内容已准备好进行REPL)

TupleN定义一个提取器类,例如对于Tuple2(您的示例):

class E2(val t: Tuple2[String, String]) {
  def unapply(m: Map[String,String]): Option[Tuple2[String, String]] =
    for {v1 <- m.get(t._1)
         v2 <- m.get(t._2)}
    yield (v1, v2)
}

// class E3(val t: Tuple2[String,String,String]) ...

您可以定义辅助函数以使构建提取器更容易:

def mkMapExtractor(k1: String, k2: String) = new E2( (k1, k2) )
// def mkMapExtractor(k1: String, k2: String, k3: String) = new E3( (k1, k2, k3) )

让我们制作一个提取器对象

val PersonExt = mkMapExtractor("firstname", "lastname")

并构建Person

val testMap = Map("lastname" -> "L", "firstname" -> "F")
PersonExt.unapply(testMap) map {Person.tupled}

testMap match {
  case PersonExt(f,l) => println(Person(f,l))
  case _ => println("err")
}

适应您的口味。

P.S。糟糕,我没有意识到你特别询问了有关命名的论点。虽然我的答案是关于位置论证,但我仍然会留在这里,以防它可以提供一些帮助。

答案 3 :(得分:0)

由于Map基本上只是List个元组,因此您可以将其视为此类。

scala> val person = args.toList match {
   case List(("firstname", firstname), ("lastname", lastname), _) => new Person(firstname, lastname)
   case _ => throw new Exception
}
person: Person = Person(John,Doe)

我为Person创建了一个案例类,以便为我生成toString方法。

相关问题