1

If I have a Scala Map

val map = Map("a" -> true,
              "b" -> "hello"
              "c" -> 5)

Is there a way I can convert this to an object (case class, regular class, anything really) such that I can access the field like this:

val obj = map.toObj
println(obj.a)
println(obj.b)

Without knowing what the parameters will be ahead of time?

Or even better, can it be an instance variable of the class I'm currently working with?

So I could actually just have

println(a)
println(b)
mdenton8
  • 619
  • 5
  • 13
  • Do you really not know anything about the parameters ahead of time? Or are you going to get one of a limited set of options? I don't see how you can not know the parameter names and then expect to go `println(a)` or `obj.a`, since that seems to require you know it is `a`! – The Archetypal Paul Jul 08 '14 at 10:12

2 Answers2

2

Have a look at Dynamic:

import scala.language.dynamics

class Wrapper(m: Map[String, Any]) extends Dynamic {
  def selectDynamic(name: String) = {
    m(name)
  }
}

object Demo {
  def main(args: Array[String]) {
    val map = Map("a" -> true,
      "b" -> "hello",
      "c" -> 5)

    val w = new Wrapper(map)
    println(w.a)
    println(w.b)
  }
}

Dynamic gives you "properties on demand".

But it's not type-safe. In the snippet above, if you try to access a non-existing key in the map, a NoSuchElementException will be thrown. And you get the Any type, so you have to use asInstanceOf at the caller's side, for example.

Dynamic has further methods, so have a closer look at the documentation of Dynamic

Beryllium
  • 12,808
  • 10
  • 56
  • 86
2

I hope you understand by asking this question that you will loose all compile time guarantees when you rely on runtime data. If this is really what you want then you can rely on Dynamic:

object X extends App {

  import scala.language.dynamics
  class D(fields: Map[String, Any]) extends Dynamic {
    def selectDynamic(str: String): Any =
      fields.getOrElse(str, throw new NoSuchFieldException(str))
  }

  val fields = Map[String, Any]("a" -> true, "b" -> "hello")
  val obj = new D(fields)

  println(obj.a)
  println(obj.b)
}

Dynamic is a compiler feature that translates all field/method calls to a call to the *Dynamic* methods. Because the compiler can't know anything about your program (how should it?), the return type you get here is Any and when you call a field/method that does not exist you get an exception at runtime instead of a compile time error. When some fields/methods are known to compile time you can combine Dynamic with macros to get at least some compile time checking (such a method is described in the linked answer).

Beside from that, that is the only syntax you can enable in Scala. If return types are important to you, you can at least add them as type parameter:

object X extends App {

  import scala.language.dynamics
  class D(fields: Map[String, Any]) extends Dynamic {
    def selectDynamic[A : reflect.ClassTag](str: String): A =
      fields.get(str) match {
        case Some(f: A) => f
        case _          => throw new NoSuchFieldException(str)
      }
  }

  val fields = Map[String, Any]("a" -> true, "b" -> "hello")
  val obj = new D(fields)

  println(obj.a[Boolean])
  println(obj.b[String])
}
Community
  • 1
  • 1
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
  • When I run your last example, I get a `MatchError` (2.10.3). It works, if `[java.lang.Boolean]` is used instead of `[Boolean]`. Is there a way so that `[Boolean]` can be used? – Beryllium Jul 08 '14 at 12:00
  • I fixes the `MatchError` but can't say why a normal `Boolean` parameter does not work in 2.10. That seems to be a bug but I can't try it out because I don't have a 2.10 installation here. – kiritsuku Jul 08 '14 at 13:30