案例类在Scala中映射

有谁知道是否有一个很好的方式,我可以转换一个Scala案例类实例,例如

case class MyClass(param1: String, param2: String) val x = MyClass("hello", "world") 

进入某种映射,例如

 getCCParams(x) returns "param1" -> "hi", "param2" -> "3" 

哪些适用于任何案例类,而不仅仅是预定义类。 我发现你可以通过编写一个询问底层产品类的方法,例如,

 def getCCName(caseobj: Product) = caseobj.productPrefix getCCName(x) returns "MyClass" 

所以我正在寻找类似的解决scheme,但案例类字段。 我可以想象一个解决scheme可能需要使用Javareflection,但是如果case类的底层实现发生变化,我不愿意写一些可能会在未来的Scala版本中破坏的东西。

目前我正在Scala服务器上工作,并使用案例类定义协议及其所有消息和exception,因为它们是如此美丽,简洁的构造。 但是,我需要将它们翻译成Java映射,以便通过消息传递层为任何客户端实现使用。 我目前的实现只是分别为每个案例类定义一个翻译,但是find一个通用的解决scheme将是很好的。

这应该工作:

 def getCCParams(cc: AnyRef) = (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) => f.setAccessible(true) a + (f.getName -> f.get(cc)) } 

因为case类扩展Product可以简单地使用.productIterator来获取字段值:

 def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names .zip( cc.productIterator.to ).toMap // zipped with all values 

或者:

 def getCCParams(cc: Product) = { val values = cc.productIterator cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap } 

产品的一个优点是你不需要在字段上调用setAccessible来读取它的值。 另一个就是productIterator不使用reflection。

请注意,此示例适用于不扩展其他类并且不在构造函数之外声明字段的简单案例类。

如果有人寻找recursion版本,这里是@ Andrejs的解决scheme的修改:

 def getCCParams(cc: Product): Map[String, Any] = { val values = cc.productIterator cc.getClass.getDeclaredFields.map { _.getName -> (values.next() match { case p: Product if p.productArity > 0 => getCCParams(p) case x => x }) }.toMap } 

它还将嵌套的case-classes扩展到任何嵌套级别的地图。

这是一个简单的变化,如果你不关心使它成为一个通用的function:

 case class Person(name:String, age:Int) def personToMap(person: Person): Map[String, Any] = { val fieldNames = person.getClass.getDeclaredFields.map(_.getName) val vals = Person.unapply(person).get.productIterator.toSeq fieldNames.zip(vals).toMap } scala> println(personToMap(Person("Tom", 50))) res02: scala.collection.immutable.Map[String,Any] = Map(name -> Tom, age -> 50) 

来自解释器包的ProductCompletion解决scheme:

 import tools.nsc.interpreter.ProductCompletion def getCCParams(cc: Product) = { val pc = new ProductCompletion(cc) pc.caseNames.zip(pc.caseFields).toMap } 

我不知道好的…但这似乎工作,至less对于这个非常基本的例子。 这可能需要一些工作,但可能足以让你开始? 基本上它从一个案例类(或任何其他类:/)过滤出所有“已知”方法,

 object CaseMappingTest { case class MyCase(a: String, b: Int) def caseClassToMap(obj: AnyRef) = { val c = obj.getClass val predefined = List("$tag", "productArity", "productPrefix", "hashCode", "toString") val casemethods = c.getMethods.toList.filter{ n => (n.getParameterTypes.size == 0) && (n.getDeclaringClass == c) && (! predefined.exists(_ == n.getName)) } val values = casemethods.map(_.invoke(obj, null)) casemethods.map(_.getName).zip(values).foldLeft(Map[String, Any]())(_+_) } def main(args: Array[String]) { println(caseClassToMap(MyCase("foo", 1))) // prints: Map(a -> foo, b -> 1) } } 
 commons.mapper.Mappers.Mappers.beanToMap(caseClassBean) 

详情: https : //github.com/hank-whu/common4s

你可以用无形的。

 case class X(a: Boolean, b: String,c:Int) case class Y(a: String, b: String) 

定义一个LabelledGeneric表示

 import shapeless._ import shapeless.ops.product._ import shapeless.syntax.std.product._ object X { implicit val lgenX = LabelledGeneric[X] } object Y { implicit val lgenY = LabelledGeneric[Y] } 

定义两个types类来提供toMap方法

 object ToMapImplicits { implicit class ToMapOps[A <: Product](val a: A) extends AnyVal { def mkMapAny(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, Any] = a.toMap[Symbol, Any] .map { case (k: Symbol, v) => k.name -> v } } implicit class ToMapOps2[A <: Product](val a: A) extends AnyVal { def mkMapString(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, String] = a.toMap[Symbol, Any] .map { case (k: Symbol, v) => k.name -> v.toString } } } 

那么你可以像这样使用它。

 object Run extends App { import ToMapImplicits._ val x: X = X(true, "bike",26) val y: Y = Y("first", "second") val anyMapX: Map[String, Any] = x.mkMapAny val anyMapY: Map[String, Any] = y.mkMapAny println("anyMapX = " + anyMapX) println("anyMapY = " + anyMapY) val stringMapX: Map[String, String] = x.mkMapString val stringMapY: Map[String, String] = y.mkMapString println("anyMapX = " + anyMapX) println("anyMapY = " + anyMapY) } 

打印

anyMapX = Map(c – > 26,b – > bike,a – > true)

anyMapY = Map(b – > second,a – > first)

stringMapX = Map(c – > 26,b – > bike,a – > true)

stringMapY = Map(b – > second,a – > first)

对于嵌套的case类,(因此嵌套的地图)检查另一个答案

如果您碰巧使用Json4s,您可以执行以下操作:

 import org.json4s.{Extraction, _} case class MyClass(param1: String, param2: String) val x = MyClass("hello", "world") Extraction.decompose(x)(DefaultFormats).values.asInstanceOf[Map[String,String]]