Home > Enterprise >  Get only super class fields
Get only super class fields

Time:09-17

case class Person(name: String, 
                  override val age: Int, 
                  override val address: String
    ) extends Details(age, address)

class Details(val age: Int, val address: String)

val person = Person("Alex", 33, "Europe")

val details = person.asInstanceOf[Details] // ??? 
println(details) // I want only Details class fields

I have these 2 classes. In reality, both have a lot of fields. Somewhere, I need only field of superclass, taken from Person class.

There is a nice way to get only super class values and not mapping them field by field?

*I'm pretty sure I'll have some problems with json writes for class Details (which is not a case class and have not a singleton object, but this is another subject)

CodePudding user response:

If I get your question correctly, then you might be asking me runtime polymorphism or dynamic method dispatch from java. If so, you may have to create both the class and not case class

class Details( val age: Int,  val address: String)

 class Person(name: String,
                  override val age: Int,
                  override val  address: String
                 ) extends Details(age, address) {

}

Now create the object of person and reference to superclass (Details)

val detail:Details =  new Person("Alex", 33, "Europe")


println(detail.address)
println(detail.age)

This way you will be able to get the only address and age

Another way is like , why can't we create the Details a separate entity like:

case class Details(  age: Int,   address: String)

 case class Person(name: String,
                   details: Details
                 )


val detail =   Person("Alex", Details(10,"Europe") )

Output:

println(detail.details)

Details(10,Europe)

CodePudding user response:

I will post a solution that leverages scala macro system (old kind, not the newest introduced with Scala 3.0). It could be an overkill for you...

BTW, if you want to access to only parent values (for example for getting key, value pair), you can:

  1. given a type tag, get all parents;
  2. from them, extract all the accessors (vals);
  3. for each val, get its value;
  4. and finally returns a list with all accessors taken

So, I try to solve each point step by step. First of all, we have to write the macro definition as:

object Macros {
  def accessors[T](element : T): String = macro MacrosImpl.accessors[T]
}

object MacrosImpl {
  def accessors[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[String] = ...
}

for the first point, we can leverage the reflection macroprogramming API using c.universe:

import c.universe._
val weakType = weakTypeTag[T] //thanks to the WeakTypeTag typeclass
val parents = weakType.tpe.baseClasses

for the second point, we can iterate over the parent classes and then take only the public accessors:

val accessors = parents
      .map(weakType.tpe.baseType(_))
      .flatMap(_.members)
      .filter(_.isPublic)
      .filter(_.isMethod)
      .map(_.asMethod)
      .filter(_.isAccessor)
      .toSet

So, for example, if the we write Macros.accessors[Details](person), accessors will yield age and address.

To take the value, we can leverage quasiqouting. So, first we take only the values name:

val names = accessors
      .map(_.fullName)
      .map(_.split("\\."))
      .map(_.reverse.head)

Then we convert them into a TermName:

val terms = names.map(TermName(_))

And finally, we convert each term to a key value tuple containing the val name and its value:

val accessorValues = terms
   .map(name => c.Expr[(String, Any)](q"(${name.toString}, ${element}.${name})"))
   .toSeq

The last step consist in convert a Seq[Expr[(String, Any)] into a Expr[Seq[(String, Any)]. A way to do that, could be leveraging recursion, reify, and splicing expression:

def seqToExprs(seq: Seq[Expr[(String, Any)]]): c.Expr[Seq[(String, Any)]] =
  seq.headOption match {
      case Some(head) => 
        c.universe.reify(
          Seq((head.splice._1, head.splice._2))   
          seqToExprs(seq.tail).splice
        )
      case _ => c.Expr[Seq[(String, Any)]](q"Seq.empty")
    }

So now I decide to return a String representation (but you can manipulate it as you want):

val elements = seqToExprs(accessorValues)
c.Expr[String](q"${elements}.mkString")

You can use it as:

import Macros._
class A(val a : Int)
class B(val b : Int) extends A(b)
class C(val c: Int) extends B(c)
//println(typeToString[List[Set[List[Double]]]])
val c = new C(10)
println(accessors[C](c)) // prints (a, 10)(b, 10)(c, 10)
println(accessors[B](c)) // prints (a, 10)(b, 10)
println(accessors[A](c)) // prints (a, 10)

And, using your example:

// Your example:
  case class Person(name: String,
                    override val age: Int,
                    override val address: String
                   ) extends Details(age, address)

  class Details(val age: Int, val address: String)

  val person = Person("Alex", 33, "Europe")
  println(accessors[Details](person)) // prints (address,Europe)(age,33)
  println(accessors[Person](person)) // prints (address,Europe)(age,33)(name,Alex)

Here there is a repository with the macro implemented.

Scala 3.0 introduce a safer and cleaner macro system, if you use it and you want to go further you can read these articles:

  • Related