簡體   English   中英

案例class到map在Scala

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

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

使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM