Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Case class to map in Scala

Is there a nice way I can convert a Scala case class instance, e.g.

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

into a mapping of some kind, e.g.

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

Which works for any case class, not just predefined ones. I've found you can pull the case class name out by writing a method that interrogates the underlying Product class, e.g.

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

So I'm looking for a similar solution but for the case class fields. I'd imagine a solution might have to use Java reflection, but I'd hate to write something that might break in a future release of Scala if the underlying implementation of case classes changes.

Currently I'm working on a Scala server and defining the protocol and all its messages and exceptions using case classes, as they are such a beautiful, concise construct for this. But I then need to translate them into a Java map to send over the messaging layer for any client implementation to use. My current implementation just defines a translation for each case class separately, but it would be nice to find a generalised solution.

like image 431
Will Avatar asked Aug 04 '09 09:08

Will


2 Answers

This should work:

def getCCParams(cc: AnyRef) =   cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (a, f) =>     f.setAccessible(true)     a + (f.getName -> f.get(cc))   } 
like image 74
Walter Chang Avatar answered Sep 24 '22 12:09

Walter Chang


Because case classes extend Product one can simply use .productIterator to get field values:

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

Or alternatively:

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

One advantage of Product is that you don't need to call setAccessible on the field to read its value. Another is that productIterator doesn't use reflection.

Note that this example works with simple case classes that don't extend other classes and don't declare fields outside the constructor.

like image 22
Andrejs Avatar answered Sep 25 '22 12:09

Andrejs