It is possible. But as far as I can see you will have to use reflection.
Additionally, you would have to change your SortingField class a bit as there is no way the scala compiler can figure out the right Ordering type class for each field.
Here is a simplified example.
import scala.reflect.ClassTag
/** You should be able to figure out the correct field ordering here. Use `reverse` to decide whether you want to sort ascending or descending. */
case class SortingField[T](field: String, ord: Ordering[T]) { type FieldType = T }
case class Book(a: Int, b: Long, c: String, z: String)
def sort[T](unsorted: Seq[T], fields: Seq[SortingField[_]])(implicit tag: ClassTag[T]): Seq[T] = {
val bookClazz = tag.runtimeClass
fields.foldLeft(unsorted) { case (sorted, currentField) =>
// keep in mind that scala generates a getter method for field 'a'
val field = bookClazz.getMethod(currentField.field)
sorted.sortBy[currentField.FieldType](
field.invoke(_).asInstanceOf[currentField.FieldType]
)(currentField.ord)
}
}
However, for sorting by multiple fields you would have to either sort the sequence multiple times or better yet compose the various orderings correctly.
So this is getting a bit more 'sophisticated' without any guarantees about correctness and completeness, but with a little test that it does not fail spectacularly:
def sort[T](unsorted: Seq[T], fields: Seq[SortingField[_]])(implicit tag: ClassTag[T]): Seq[T] = {
@inline def invokeGetter[A](field: Method, obj: T): A = field.invoke(obj).asInstanceOf[A]
@inline def orderingByField[A](field: Method)(implicit ord: Ordering[A]): Ordering[T] = {
Ordering.by[T, A](invokeGetter[A](field, _))
}
val bookClazz = tag.runtimeClass
if (fields.nonEmpty) {
val field = bookClazz.getMethod(fields.head.field)
implicit val composedOrdering: Ordering[T] = fields.tail.foldLeft {
orderingByField(field)(fields.head.ord)
} { case (ordering, currentField) =>
val field = bookClazz.getMethod(currentField.field)
val subOrdering: Ordering[T] = orderingByField(field)(currentField.ord)
new Ordering[T] {
def compare(x: T, y: T): Int = {
val upperLevelOrderingResult = ordering.compare(x, y)
if (upperLevelOrderingResult == 0) {
subOrdering.compare(x, y)
} else {
upperLevelOrderingResult
}
}
}
}
unsorted.sorted(composedOrdering)
} else {
unsorted
}
}
sort(
Seq[Book](
Book(1, 5L, "foo1", "bar1"),
Book(10, 50L, "foo10", "bar15"),
Book(2, 3L, "foo3", "bar3"),
Book(100, 52L, "foo4", "bar6"),
Book(100, 51L, "foo4", "bar6"),
Book(100, 51L, "foo3", "bar6"),
Book(11, 15L, "foo5", "bar7"),
Book(22, 45L, "foo6", "bar8")
),
Seq(
SortingField("a", implicitly[Ordering[Int]].reverse),
SortingField("b", implicitly[Ordering[Long]]),
SortingField("c", implicitly[Ordering[String]])
)
)
>> res0: Seq[Book] = List(Book(100,51,foo3,bar6), Book(100,51,foo4,bar6), Book(100,52,foo4,bar6), Book(22,45,foo6,bar8), Book(11,15,foo5,bar7), Book(10,50,foo10,bar15), Book(2,3,foo3,bar3), Book(1,5,foo1,bar1))