[英]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.