2

I have two nested case classes:

case class InnerClass(param1: String, param2: String)
case class OuterClass(myInt: Int, myInner: InnerClass)
val x = OuterClass(11, InnerClass("hello", "world"))

Which I want to convert to nested Maps of type Map[String,Any] so that I get something like this:

Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))

Of course, the solution should be generic and work for any case class.

Note: This discussion gave a good answer on how to map a single case class to a Map. But I couldn't adapt it to nested case classes. Instead I get:

Map(myInt -> 11, myInner -> InnerClass(hello,world)
Community
  • 1
  • 1
jans
  • 1,768
  • 3
  • 17
  • 22
  • This question isn't very clear. What are `myInt` and `myInner` in the context of a Map? Are they taken from the `OuterClass` instance, or is it a `String` to be used as a key? In any case, using `Any` indicates you're probably doing something wrong in a statically typed language like Scala. Clarify what you're trying to do and you'll get some useful suggestions. – Luigi Plinge Mar 10 '13 at 17:04
  • Check `productIterator`, a method that iterates over all values of `Product`s. All case classes are `Product`s. – pedrofurla Mar 10 '13 at 18:13
  • @luigi-plinge Not sure if I get your question right. In the context of a Map both myInt and myInner are taken from the OuterClass instance and are used as keys. This is meant to be generic. To clarify the background, I use nested case classes for the main objects within my application. Furthermore I have a [Bencode](https://en.wikipedia.org/wiki/Bencode) [Encoder](https://github.com/pyronicide/scala-bencode/blob/master/src/main/scala/org/saunter/bencode/Bencode.scala#L105) which accepts Strings, Ints and Maps. My intend is to convert my objects to nested Maps and feed it to the Bencode encoder – jans Mar 10 '13 at 23:52
  • I think I understand: you want to use the field name from the case class as a String key in the Map. The only way to do this is with reflection, since variable names aren't data that are supposed to be available at runtime. Don't use reflection unless you really need it. If you need a String to be available as a key, have a `String` field in your case class for this purpose. It should then be easy. – Luigi Plinge Mar 11 '13 at 00:18

3 Answers3

2

As Luigi Plinge notes in a comment above, this is a very bad idea—you're throwing type safety out the window and will be stuck with a lot of ugly casts and runtime errors.

That said, it's pretty easy to do what you want with the new Scala 2.10 Reflection API:

def anyToMap[A: scala.reflect.runtime.universe.TypeTag](a: A) = {
  import scala.reflect.runtime.universe._

  val mirror = runtimeMirror(a.getClass.getClassLoader)

  def a2m(x: Any, t: Type): Any = {
    val xm = mirror reflect x

    val members = t.declarations.collect {
      case acc: MethodSymbol if acc.isCaseAccessor =>
        acc.name.decoded -> a2m((xm reflectMethod acc)(), acc.typeSignature)
    }.toMap

    if (members.isEmpty) x else members
  }

  a2m(a, typeOf[A])
}

And then:

scala> println(anyToMap(x))
Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))

Do not do this, though. In fact you should do your absolute best to avoid runtime reflection altogether in Scala—it's really almost never necessary. I'm only posting this answer because if you do decide that you must use runtime reflection, you're better off using the Scala Reflection API than Java's.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
0

Just call it recursively. So

def getCCParams(cc: AnyRef) =
  (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
    f.setAccessible(true)
    val value = f.get(cc) match {
      // this covers tuples as well as case classes, so there may be a more specific way
      case caseClassInstance: Product => getCCParams(caseClassInstance)
      case x => x
    }
    a + (f.getName -> value)
  }
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

Here is a more principled solution based on shapeless. https://github.com/yongjiaw/datacrafts

class NoSchemaTest extends FlatSpec with ShapelessProduct.Implicits {

"Marshalling and unmarshalling with Map" should "be successful" in {

val op = NoSchema.of[TestClass]

assert(
  op.operator.marshal(
    Map(
      "v1" -> 10,
      "v5" -> Map("_2" -> 12),
      "v3" -> Iterable(Seq("v21" -> 3)),
      "v6" -> TestClass3(v31 = 5)
    )) == TestClass(
    v1 = 10,
    v5 = (null, 12),
    v3 = Some(Seq(Some(
      TestClass2(
        v21 = 3,
        v22 = null
      )))),
    v6 = Some(TestClass3(v31 = 5)),
    v2 = None,
    v4 = null
  )
)

assert(
  op.operator.unmarshal(
    TestClass(
      v1 = 1,
      v2 = null
    )
  ) == Map(
    "v1" -> 1,
    "v2" -> null,
    // the rest are default values
    "v6" -> null,
    "v5" -> Map("_2" -> 2, "_1" -> "a"),
    "v4" -> null,
    "v3" -> Seq(
      Map(
        "v21" -> 3,
        "v22" -> Map("v" -> Map(), "v32" -> Seq(12.0), "v31" -> 0)
      )
    )
  )
)

 }
}

object NoSchemaTest {

case class TestClass(v1: Int,
v2: Option[Seq[Option[Double]]] = None,
v3: Option[Seq[Option[TestClass2]]] = Some(Seq(Some(TestClass2()))),
v4: Seq[Int] = null,
v5: (String, Int) = ("a", 2),
v6: Option[TestClass3] = None
)

case class TestClass2(v21: Int = 3,
v22: TestClass3 = TestClass3(0)
)

case class TestClass3(v31: Int,
v32: Iterable[Double] = Seq(12),
v: Map[String, Int] = Map.empty
)

}

trait DefaultRule extends Operation.Rule {

override def getOperator[V](operation: Operation[V]): Operation.Operator[V] = {

operation.context.noSchema match {

  case _: Primitive[V] => new PrimitiveOperator[V](operation)

  case shapeless: ShapelessProduct[V, _] =>
    new ShapelessProductMapper[V](operation, shapeless)

  case option: OptionContainer[_] =>
    new OptionOperator[option.Elem](
      option.element, operation.asInstanceOf[Operation[Option[option.Elem]]])
      .asInstanceOf[Operation.Operator[V]]

  case map: MapContainer[_] =>
    new MapOperator[map.Elem](
      map.element, operation.asInstanceOf[Operation[Map[String, map.Elem]]])
      .asInstanceOf[Operation.Operator[V]]

  case seq: SeqContainer[_] =>
    new SeqOperator[seq.Elem](
      seq.element, operation.asInstanceOf[Operation[Seq[seq.Elem]]])
      .asInstanceOf[Operation.Operator[V]]

  case iterable: IterableContainer[_] =>
    new IterableOperator[iterable.Elem](
      iterable.element, operation.asInstanceOf[Operation[Iterable[iterable.Elem]]])
      .asInstanceOf[Operation.Operator[V]]
}}}
gerry
  • 81
  • 6