2

I have a case class for which I add some annotation to some of the fields:

final class Anno(min: Int, max: Int) extends StaticAnnotation

case class Test(x: String, @Anno(min = 5, max = 10) y: String)

I would like to iterate each field of the case class, get its value and the case class annotation properties assigned to it (if annotation exists).

Any idea how can it be accomplished in Scala?

bashan
  • 3,572
  • 6
  • 41
  • 58
  • You'll find most of the answers in this post: http://stackoverflow.com/questions/11468571/how-to-access-annotation-defined-on-case-class-field-at-runtime – Harald Gliebe Apr 28 '17 at 02:38
  • Thanks. Already saw it. Didn't help so much... Returned an empty list. I already have a solution for getting list of annotations, but with no reference to the original case class parameters and also I am not sure how to access the annotation properties, once I have an annotation class. – bashan Apr 28 '17 at 03:58

1 Answers1

4

To get the list of annotations of the constructor of the case class you can use:

import scala.reflect.runtime.universe._
symbolOf[Test].asClass.primaryConstructor.typeSignature.paramLists.head.map(s => (s -> s.annotations))

However the annotations in the parameter list will not automatically be added to the corresponding class member. So you would have to match the annotated parameter y that you get with the above call to the generated field y by name.

Alternatively you could annotated the annotation in your case class like this

import scala.annotation.meta._
case class Test(x: String, @(Anno @field)(min = 5, max = 10) y: String)

and then use

val it = symbolOf[Test].toType.members
  .filter(_.annotations.exists(a => a.tree.tpe <:< typeOf[Anno]))

to get an iterable of the fields with the Anno annotation. For a found field and an instance of Test you get the value as follows

val yField = it.iterator.next
val o = Test("123", "abc")
universe.runtimeMirror(o.getClass.getClassLoader).reflect(o)
  .reflectField(yField.asTerm).get
Harald Gliebe
  • 7,236
  • 3
  • 33
  • 38
  • Trying to replace Test with a generic T I am getting this exception: scala.ScalaReflectionException: free type T is not a class. Any way of solving it? – bashan Apr 28 '17 at 20:02
  • The first solution seems to get a map of the annotations properly. I am currently iterating the case class and converting it to String using something like: testCaseClass.productIterator.map(_.tostring).mkString("", "|", ""). Any way of iterating the case class in a way I can have access to the map of the first solution? – bashan Apr 28 '17 at 20:22
  • your help will be much appreciated. The top solution looks good, I basically have 2 issues with it: 1) How can I access the Annotation info? 2) How can this same code be written with a generic T instead of Test? – bashan Apr 29 '17 at 06:55
  • If you declare your type parameter with `[T : TypeTag]` your method will get an implicit parameter and you can use `typeOf[T]` inside it – Harald Gliebe Apr 29 '17 at 09:21
  • It compiles but returns an empty result. I try something like: def extractAnnotations[T: TypeTag]: Unit = { val allAnnotations = typeOf[T].typeSymbol.asClass.primaryConstructor.typeSignature.paramLists.head.map(_.annotations) }. When I take it out of the method and enter the case class directly it seems to work: typeOf[TestCaseClass].typeSymbol.asClass.primaryConstructor.typeSignature.paramLists.head.map(_.annotations). Any way of solving it? – bashan Apr 29 '17 at 10:09
  • Sorry, my mistake. Works GREAT. Thanks. – bashan Apr 29 '17 at 10:18
  • I have one last issue... I created a method that looks like: AnnotationUtil.annotationsOf[T]. I use it in a class that has a generic T which defined as: class SomeClass[T <: CC]. And CC is defined as: trait CC extends Product. I get an error on the method since the type is not "TypeTag". Any way of solving it? – bashan Apr 29 '17 at 10:50
  • Sorry, I don't completely understand the last comment. Could you maybe extend your question or create a new one? – Harald Gliebe Apr 30 '17 at 04:06