78

case classScalaインスタンスを変換できる良い方法はありますか?

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

ある種のマッピングに、例えば

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

これは、定義済みのものだけでなく、どのケースクラスでも機能します。基になる Product クラスに問い合わせるメソッドを作成することで、ケース クラス名を引き出すことができることがわかりました。

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

だから私は同様の解決策を探していますが、ケースクラスフィールド用です。ソリューションには Java リフレクションを使用する必要があるかもしれないと想像しますが、ケース クラスの基になる実装が変更された場合に、Scala の将来のリリースで壊れる可能性のあるものを書くのは嫌いです。

現在、私は Scala サーバーに取り組んでおり、ケース クラスを使用してプロトコルとそのすべてのメッセージと例外を定義しています。しかし、これらを Java マップに変換して、クライアント実装で使用できるようにメッセージング レイヤーを介して送信する必要があります。私の現在の実装では、各ケース クラスの翻訳を個別に定義しているだけですが、一般化された解決策を見つけられるとよいでしょう。

4

11 に答える 11

45

ケース クラスは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
}

setAccessibleProduct の利点の 1 つは、値を読み取るためにフィールドを呼び出す必要がないことです。もう 1 つは、productIterator がリフレクションを使用しないことです。

この例は、他のクラスを拡張せず、コンストラクターの外部でフィールドを宣言しない単純なケース クラスで機能することに注意してください。

于 2013-09-04T20:56:50.333 に答える
12

誰かが再帰的なバージョンを探している場合は、@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
}

また、ネストされたケースクラスを、ネストの任意のレベルでマップに展開します。

于 2014-09-17T14:59:56.500 に答える
4

形のないものを使用できます。

させて

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 メソッドを提供する 2 つの型クラスを定義する

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 -> 2 番目、a -> 1 番目)

stringMapX = Map(c -> 26, b -> バイク, a -> true)

stringMapY = Map(b -> 2 番目、a -> 1 番目)

ネストされたケース クラス (ネストされたマップ) については、別の回答を確認してください

于 2016-08-18T20:15:16.647 に答える
4

ProductCompletionインタープリターパッケージからのソリューション:

import tools.nsc.interpreter.ProductCompletion

def getCCParams(cc: Product) = {
  val pc = new ProductCompletion(cc)
  pc.caseNames.zip(pc.caseFields).toMap
}
于 2011-09-06T13:09:47.907 に答える
2

私はナイスについては知りません...しかし、少なくともこの非常に基本的な例では、これはうまくいくようです。おそらく多少の作業が必要ですが、始めるには十分でしょうか? 基本的に、ケースクラス(または他のクラス:/)からすべての「既知の」メソッドを除外します

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)
  }
}
于 2009-08-04T12:29:34.163 に答える