Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get all object vals and subobject vals using reflection in Scala?

I have an object that looks like this:

object Settings {
  final val Host = "host"
  final val Protocol = "protocol"

  object User {
    final val Name = "username"
    final val Password = "password"
  }

  object Subject {
    final val Query = "query"
    final val Predicate = "predicate"
  }
}

What I'd like to do is something like membersAsHash(classOf[CollectionSettings]) and receive a hash) of all of the vals that I've declared in the object:

[
  Host => "host", 
  Protocol => "protocol", 
  Name => "username",
  Password => "password",
  Query => "query",
  Predicate => "predicate"
]

It'd be fine if the key was a string, even the full package name (e.g. com.example.Settings.User). What I really need is the values, so if I can only get that, it's still acceptable.

This has gotten me the name of the subobjects, but I can't seem to figure out how to get the vals that are internal to each:

val optionsToCheck = {
  import scala.reflect.runtime.{universe => ru}
  val mirror = ru.runtimeMirror(getClass.getClassLoader)
  val subObjects = ru.typeOf[CollectionSettings.type].declarations.filter(_.isModule)
  subobjects.map(o => mirror.reflectModule(o.asModule).instance.asInstanceOf[Object].toString).toList
}
like image 577
Colin Dean Avatar asked Oct 01 '13 14:10

Colin Dean


1 Answers

The neat thing here is that you're using constant value definitions (i.e., final values with no type annotation; see §4.1 of the language specification), so you don't even need any mirrors:

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

  def members(s: Symbol): Map[String, String] =
    s.typeSignature.decls.collect {
      case m: ModuleSymbol => members(m)
      case m: MethodSymbol if m.isAccessor => m.returnType match {
        case ConstantType(Constant(s: String)) => Map(m.name.decodedName.toString -> s)
        case _ => Map.empty[String, String]
      }
    }.foldLeft(Map.empty[String, String])(_ ++ _)

  members(typeOf[A].termSymbol)
}

It works like this:

scala> deepMembers(Settings) foreach println
(Name,username)
(Predicate,predicate)
(Query,query)
(Password,password)
(Protocol,protocol)
(Host,host)

If for some reason you couldn't use constant value definitions, you'd need to adjust the MethodSymbol case to work with instance mirrors, but the basic approach to recursively collecting key-value pairs from child objects would be the same.

like image 175
Travis Brown Avatar answered Sep 30 '22 15:09

Travis Brown