質問

Scalaの caseクラスインスタンスを変換する良い方法はありますか、例えば

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マップに変換して、使用するクライアント実装のメッセージングレイヤーを介して送信する必要があります。私の現在の実装では、各ケースクラスの翻訳を個別に定義しているだけですが、一般化されたソリューションを見つけられると便利です。

役に立ちましたか?

解決

これは動作するはずです:

def getCCParams(cc: AnyRef) =
  (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(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の利点の1つは、フィールドで setAccessible を呼び出して値を読み取る必要がないことです。もう1つは、productIteratorはリフレクションを使用しないことです。

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

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

シェイプレスを使用できます。

レット

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-&gt; 26、b-&gt; bike、a-&gt; true)

     

anyMapY = Map(b-&gt; second、a-&gt; first)

     

stringMapX = Map(c-&gt; 26、b-&gt; bike、a-&gt; true)

     

stringMapY = Map(b-&gt; second、a-&gt; first)

ネストされたケースクラスの場合(ネストされたマップ) 別の回答

を確認してください

インタープリターパッケージの ProductCompletion を使用したソリューション:

import tools.nsc.interpreter.ProductCompletion

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

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]]

Scala 2.13 の開始、 caseクラス es( 製品 )には、 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")

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

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top