78 votes

Classe de cas à mapper en Scala

Personne ne sait si il y a une belle façon de me convertir un Scala cas instance de classe, par exemple

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

Dans un mappage d'un certain type, par exemple

getCCParams(x) returns "param1" -> "hi", "param2" -> "3"

Ce qui fonctionne pour tous les cas de classe, et pas seulement prédéfinis. J'ai trouvé que vous pouvez tirer de l'affaire nom de la classe par l'écriture d'une méthode qui interroge le Produit sous-jacent de classe, par exemple

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

Donc, je suis à la recherche d'une solution similaire, mais pour le cas des champs de classe. J'imagine une solution peut avoir à utiliser de réflexion Java, mais je déteste écrire quelque chose qui pourrait les briser dans une future version de Scala si le sous-jacent de la mise en œuvre des classes de cas de changements.

Actuellement, je travaille sur un Scala de serveur et de définir le protocole et tous ses messages et des exceptions à l'aide des classes de cas, comme ils sont une belle et concise, de construire pour cela. Mais je puis avoir besoin de les traduire en Java carte à envoyer sur la messagerie de la couche pour toute mise en œuvre du client à utiliser. Mon implémentation actuelle ne définit une traduction pour chaque classe de cas séparément, mais il serait bien de trouver une solution universelle.

98voto

Walter Chang Points 7041

Cela devrait fonctionner:

 def getCCParams(cc: AnyRef) =
  (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
    f.setAccessible(true)
    a + (f.getName -> f.get(cc))
  }
 

49voto

Andrejs Points 4235

Parce que les classes de prolonger Produit , on peut tout simplement utiliser .productIterator pour obtenir des valeurs de champ:

def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names
                .zip( cc.productIterator.to ).toMap // zipped with all values

Ou sinon:

def getCCParams(cc: Product) = {          
      val values = cc.productIterator
      cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap
}

Un des avantages du Produit, c'est que vous n'avez pas besoin d'appeler setAccessible sur le terrain pour lire sa valeur. Une autre est que productIterator ne pas utiliser la réflexion.

Notez que cet exemple fonctionne avec de simples cas des classes qui ne s'étendent pas à d'autres classes et de ne pas déclarer des champs à l'extérieur du constructeur.

7voto

ShawnFumo Points 445

Voici une variante simple si vous ne voulez pas en faire une fonction générique:

 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)
 

4voto

Stefan Endrullis Points 1217

Solution avec ProductCompletion du package interprète:

 import tools.nsc.interpreter.ProductCompletion

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

2voto

André Laszlo Points 3783

Je ne sais pas pour Nice ... mais cela semble fonctionner, du moins pour cet exemple très très basique. Cela nécessite probablement un peu de travail mais pourrait être suffisant pour vous aider à démarrer? Fondamentalement, il filtre toutes les méthodes "connues" d'une classe de cas (ou de toute autre classe: /)

 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)
  }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X