案例映射类斯卡拉(Case class to map in Scala)

2019-06-18 00:36发布

有没有一个很好的办法,我可以转换Scala的case class的实例,例如,

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

到某种类型的映射,如

getCCParams(x) returns "param1" -> "hello", "param2" -> "world"

这适用于任何情况下类,而不只是预先定义的。 我发现,你可以通过编写询问底层的产品类中的方法,如拉的情况下的类名了

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

所以我在寻找一个类似的解决方案,但对于案件类字段。 我想像的解决方案可能需要使用Java反射,但我讨厌写一些东西,可能会在未来的Scala的版本,如果打破了底层实现的情况下,类的变化。

目前,我正在Scala的服务器上,并定义了协议,利用其所有的消息和异常情况下的类,因为它们是这个这样一个美丽,简洁的构造。 但我这时就需要将其转化为一个Java地图通过消息发送层对任何客户端实现使用。 我目前的实现只是定义了一个翻译为每个个案类别单独,但它会很高兴找到一个广义的解决方案。

Answer 1:

这应该工作:

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


Answer 2:

因为case类延伸产品一个可以简单地使用.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不使用反射。

注意,这个例子使用不扩展其他类和不申报的构造函数外场简单的例子类。



Answer 3:

如果有人寻找一个递归版本,这里是@安德烈斯的解决方案的修改:

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
}

它还扩展了巢式病例类成地图的任何级别的嵌套。



Answer 4:

下面是如果你不关心使其成为一个通用的函数的简单变化:

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)


Answer 5:

你可以使用不成形。

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]
}

定义两个类型类提供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 =地图(三 - > 26,B - >自行车,一 - >真)

anyMapY =地图(二 - >第二,一 - >第一)

stringMapX =地图(三 - > 26,B - >自行车,一 - >真)

stringMapY =地图(二 - >第二,一 - >第一)

对于巢式病例类,(因此嵌套映射)检查另一个答案



Answer 6:

解决方案与ProductCompletion从翻译包:

import tools.nsc.interpreter.ProductCompletion

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


Answer 7:

如果你碰巧使用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]]


Answer 8:

我不知道好...但是这似乎工作,至少在这个非常非常基本的例子。 它可能需要一些工作,但让你开始可能就足够了? 基本上,它过滤掉所有的“已知的”从壳体类的方法(或任何其他类:/)

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)
  }
}


Answer 9:

commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)

详细信息: https://github.com/hank-whu/common4s



Answer 10:

开始Scala 2.13case class ES(为实现Product )提供有productElementNames方法,它返回了自己的字段名称的迭代器。

通过压缩和解与获得的字段值的字段名productIterator我们可以笼统获得相关Map

// case class MyClass(param1: String, param2: String)
// val x = MyClass("hello", "world")
(x.productElementNames zip x.productIterator).toMap
// Map[String,Any] = Map("param1" -> "hello", "param2" -> "world")


文章来源: Case class to map in Scala