[英]Case class to map in Scala
有没有一种很好的方法可以转换 Scala case class
实例,例如
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
进入某种映射,例如
getCCParams(x) returns "param1" -> "hello", "param2" -> "world"
这适用于任何情况 class,而不仅仅是预定义的。 我发现你可以通过编写一个询问底层产品 class 的方法来提取案例 class 名称,例如
def getCCName(caseobj: Product) = caseobj.productPrefix
getCCName(x) returns "MyClass"
所以我正在寻找类似的解决方案,但适用于案例 class 字段。 我想象一个解决方案可能必须使用 Java 反射,但如果案例类的底层实现发生变化,我不想写一些可能会在 Scala 的未来版本中破坏的东西。
目前我正在 Scala 服务器上工作,并使用案例类定义协议及其所有消息和异常,因为它们是如此漂亮、简洁的结构。 但我随后需要将它们转换为 Java map 以通过消息传递层发送给任何客户端实现使用。 我当前的实现只是分别为每个案例 class 定义了一个翻译,但最好能找到一个通用的解决方案。
这应该有效:
def getCCParams(cc: AnyRef) =
cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (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
}
Product 的一个优点是您不需要在字段上调用setAccessible
来读取它的值。 另一个是 productIterator 不使用反射。
请注意,此示例适用于不扩展其他类且不在构造函数之外声明字段的简单案例类。
从Scala 2.13
开始, case 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")
如果有人寻找递归版本,这里是@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 扩展为任何嵌套级别的映射。
如果您不关心使其成为通用函数,这里有一个简单的变体:
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)
如果您碰巧使用 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]]
你可以使用无形。
让
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 -> 自行车,a -> true)
anyMapY = Map(b -> second, a -> first)
stringMapX = Map(c -> 26, b -> 自行车,a -> 真)
stringMapY = Map(b -> second, a -> first)
对于嵌套案例类,(因此嵌套映射)检查另一个答案
来自解释器包的ProductCompletion
解决方案:
import tools.nsc.interpreter.ProductCompletion
def getCCParams(cc: Product) = {
val pc = new ProductCompletion(cc)
pc.caseNames.zip(pc.caseFields).toMap
}
我不知道 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)
}
}
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)
使用Java反射,但访问级别没有变化。 将Product
和 case 类转换为Map[String, String]
:
def productToMap[T <: Product](obj: T, prefix: String): Map[String, String] = {
val clazz = obj.getClass
val fields = clazz.getDeclaredFields.map(_.getName).toSet
val methods = clazz.getDeclaredMethods.filter(method => fields.contains(method.getName))
methods.foldLeft(Map[String, String]()) { case (acc, method) =>
val value = method.invoke(obj).toString
val key = if (prefix.isEmpty) method.getName else s"${prefix}_${method.getName}"
acc + (key -> value)
}
}
Scala 3 的现代变体也可能有点简化,如下面的示例类似于上面Walter Chang发布的答案。
def getCCParams(cc: AnyRef): Map[String, Any] =
cc.getClass.getDeclaredFields
.tapEach(_.setAccessible(true))
.foldLeft(Map.empty)((a, f) => a + (f.getName -> f.get(cc)))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.