首页 文章

要在Scala中映射的案例类

提问于
浏览
69

有没有一种很好的方法可以转换Scala case class 实例,例如

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

进入某种映射,例如

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

适用于任何案例类,而不仅仅是预定义的类 . 我发现你可以通过编写一个询问底层Product类的方法来拉出case类名,例如:

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

所以我正在寻找类似的解决方案,但对于案例类字段 . 我想象一个解决方案可能不得不使用Java反射,但是如果案例类的底层实现发生变化,我讨厌在未来的Scala版本中编写一些内容 .

目前我正在使用Scala服务器并使用案例类定义协议及其所有消息和异常,因为它们是如此美观,简洁的构造 . 但是,我需要将它们转换为Java映射,以通过消息传递层发送以供任何客户端实现使用 . 我当前的实现只是分别为每个案例类定义一个翻译,但是找到一个通用的解决方案会很好 .

10 回答

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

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

  • 2

    启动 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")
    
  • 5

    这应该工作:

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

    因为案例类扩展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
    }
    

    Product的一个优点是您不需要在字段上调用 setAccessible 来读取其值 . 另一个是productIterator不使用反射 .

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

  • 82

    如果有人寻找递归版本,这里是@ Andrejs解决方案的修改:

    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扩展为任何嵌套级别的映射 .

  • 3

    如果您不关心将其作为通用函数,这是一个简单的变体:

    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)
    
  • 12

    你可以使用无形 .

    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 = 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类,(因此嵌套的map)检查another answer

  • 5

    来自解释器包的 ProductCompletion 解决方案:

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

    如果您碰巧使用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]]
    
  • 2

    我不知道好......但这似乎有用,至少对于这个非常基本的例子 . 它可能需要一些工作,但可能足以让你入门?基本上它从case类(或任何其他类:)过滤掉所有“已知”方法

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

相关问题