在 Scala 中映射的案例类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1226555/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:34:15  来源:igfitidea点击:

Case class to map in Scala

scalacase-class

提问by Will

Is there a nice way I can convert a Scala case classinstance, e.g.

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

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

into a mapping of some kind, e.g.

成某种映射,例如

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

Which works for any case class, not just predefined ones. I've found you can pull the case class name out by writing a method that interrogates the underlying Product class, e.g.

这适用于任何案例类,而不仅仅是预定义的。我发现您可以通过编写一个询问底层 Product 类的方法来提取案例类名,例如

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

So I'm looking for a similar solution but for the case class fields. I'd imagine a solution might have to use Java reflection, but I'd hate to write something that might break in a future release of Scala if the underlying implementation of case classes changes.

所以我正在寻找一个类似的解决方案,但对于案例类字段。我想一个解决方案可能必须使用 Java 反射,但是如果 case 类的底层实现发生变化,我不想写一些可能会在 Scala 的未来版本中中断的东西。

Currently I'm working on a Scala server and defining the protocol and all its messages and exceptions using case classes, as they are such a beautiful, concise construct for this. But I then need to translate them into a Java map to send over the messaging layer for any client implementation to use. My current implementation just defines a translation for each case class separately, but it would be nice to find a generalised solution.

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

回答by Walter Chang

This should work:

这应该有效:

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

回答by Andrejs

Because case classes extend Productone can simply use .productIteratorto get field values:

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

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

Or alternatively:

或者:

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

One advantage of Product is that you don't need to call setAccessibleon the field to read its value. Another is that productIterator doesn't use reflection.

Product 的一个优点是你不需要调用setAccessible字段来读取它的值。另一个是 productIterator 不使用反射。

Note that this example works with simple case classes that don't extend other classes and don't declare fields outside the constructor.

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

回答by Xavier Guihot

Starting Scala 2.13, case classes (as implementations of Product) are provided with a productElementNamesmethod which returns an iterator over their field's names.

开始Scala 2.13case classes (作为 的实现Product)提供了一个productElementNames方法,该方法返回一个迭代器在它们的字段名称上。

By zipping field names with field values obtained with productIteratorwe can generically obtain the associated Map:

通过使用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")

回答by Piotr Krzemiński

If anybody looks for a recursive version, here is the modification of @Andrejs's solution:

如果有人寻找递归版本,这里是@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
}

It also expands the nested case-classes into maps at any level of nesting.

它还将嵌套的 case-classes 扩展到任何嵌套级别的映射中。

回答by ShawnFumo

Here's a simple variation if you don't care about making it a generic 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)

回答by Stefan Endrullis

Solution with ProductCompletionfrom interpreter package:

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

import tools.nsc.interpreter.ProductCompletion

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

回答by harrylaou

You could use shapeless.

你可以使用无形。

Let

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

Define a LabelledGeneric representation

定义一个 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]
}

Define two typeclasses to provide the toMap methods

定义两个类型类来提供 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 }
  }
}

Then you can use it like this.

然后你可以像这样使用它。

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

which prints

哪个打印

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)

anyMapX = Map(c -> 26, b -> 自行车,a -> 真)

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

stringMapX = Map(c -> 26, b -> 自行车,a -> 真)

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

For nested case classes, (thus nested maps) check another answer

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

回答by Barak BN

If you happen to be using Json4s, you could do the following:

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

回答by André Laszlo

I don't know about nice... but this seems to work, at least for this very very basic example. It probably needs some work but might be enough to get you started? Basically it filters out all "known" methods from a case class (or any other class :/ )

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

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

回答by Kai Han

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

Details: https://github.com/hank-whu/common4s

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