How best to keep a cached list of member fields, o

2019-06-04 07:18发布

问题:

This is a follow up to the following question: Fastest way to get the names of the fields of a case class in Scala

I'm trying to find a simple way to provide fast custom serialization (lets say to a list of tuples of (String, Object), which can be converted into a db row in production or an in memory map in unit testing) to a family of case classes in Scala, and it seems that keeping a cached list of a fields of the class may be a promising way of doing this. However, I'm not sure about the cleanest way to do this. I know I can do something like the following:

case class Playlist(
  val id: Option[Long],
  val title: Option[String],
  val album: Option[String],
  val artist: Option[String],
  val songId: Option[UUID]) {
  def serialize = Playlist.fields.map(f => (f.getName, f.get(this)))
}

object Playlist {
  val empty = Playlist(None, None, None, None, None)
  val fields = Playlist.empty.getClass.getDeclaredFields.toList
  fields foreach { _.setAccessible(true) }
}

There a are a couple of things I don't like about this, however:

  1. I don't want to have to use empty from the companion class just to get a cached list of fields
  2. I don't want to have to declare the serialization logic for each case class for which I want this serialization behavior. There are probably a few ways of getting around this, but I'm not sure of the cleanest way that will give correct behavior (worried about mixing reflection and inheritance)

What's the cleanest way to achieve this in Scala?

回答1:

I think it would be simplest to keep a cache map of Class[_] -> fields separately from any individual case class, such as in a global singleton with a method serialize(instance). This way you don't have to write any extra code in the classes you wish to serialize.

Another way could be to create a trait to mixin to the case classes' companion objects, with the cached list of fields, and an implicit wrapper class to add the serialize method. You can use an implicit ClassTag to initialize fields:

abstract class MyCompanion[T](implicit ctag: ClassTag[T]) {
    private val fields = ctag.runtimeClass.getDeclaredFields.toList
    fields foreach { _.setAccessible(true) }
    implicit class AddSerializeMethod(obj: T) {
        def serialize = fields.map(f => (f.getName, f.get(obj)))
    }
}

case class C(...) { ... }
object C extends MyCompanion[C]

Unfortunately, it seems you can't make AddSerializeMethod a value class this way.