Question

I am using Play Framework 2.1.5 in a Java application.

I've got a view component that takes a list of generic objects as a parameter. In this component, I want to iterate on the list and get some properties of each element.

This would look like something like this:

@(elements: List[_])

@for((element, i) <- elements.view.zipWithIndex) {
    @i
    @element.id
    @element.name
}

(I need those 3 values)

But, of course, element.id and element.name would not compile even if the type of objects I put in the list contained these methods. So I did this:

@for((element, i) <- elements.view.zipWithIndex) {
    @defining(
        ViewsUtils.getGenericElementId(element),
        ViewsUtils.getGenericElementName(element)) {
            case (id, name, something) =>
                @i
                @id
                @name
        }
}

And in a Java utility class:

public final class ViewsUtils {

    public static String getGenericElementId(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return object.getClass().getMethod("getId").invoke(object).toString();
    }

    public static String getGenericElementName(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return object.getClass().getMethod("getName").invoke(object).toString();
    }
}

This works but I know it's not right because it could throw a RuntimeException in case one of these methods didn't exist for the type of object I put in the list.

Here are my suppositons:

  1. Casting the elements

  2. Using inheritance

  3. As I just need 2 properties of each object (id and name), I could use a map, but I need the index of the loop (is it possible to get it from a map?) and it wouldn't work if I needed more than 2 properties.

  4. Maybe Scala provides the syntax for this kind of stuff.

Or maybe I'm just looking in the wrong direction.

Thank you for your help.

Was it helpful?

Solution

Ok, that is a bit too much for a comment, so I'll risk to post it as an answer.

Assuming you wish to never get a RuntimeException you mentioned it makes sense that your object implement some interface (or mix in a trait, as it is said in Scala), so you won't risk getting an exception and no more need reflection to get values.

Suppose, you declare such a trait:

trait GenericObject {
  val id: Long
  val name: String
}

Then you declare some case classes:

case class A(id: Long, name: String, someOtherField: SomeType) extends GenericObject {
  //your implementation
}

case class B(id: Long, name: String) extends GenericObject

case class C(id: Long, name: String) extends B(id, name)

Now you can change your template like this:

@(elements: List[GenericObject])

@for((element, i) <- elements.view.zipWithIndex) {
  @i
  @element.id
  @element.name
}

And you should pass the list of GenericObjects to your template:

val myObjects: List[GenericObject] = List(A(1, "A name"), B(2, "B name"), C(3, "C name"))
Ok(your_template.scala.html(myObjects))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top