Support generic deserialization from a List[(Strin

2019-07-22 03:12发布

问题:

This is a follow up to the following question, which concerned serialization: How best to keep a cached list of member fields, one each for a family of case classes in Scala

I'm trying to generically support deserialization in the same way. One straightforward attempt is the following:

  abstract class Serializer[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)))
    }
    def deserialize(data: List[(String, Any)]): T = {
      val m = data toMap
      val r: T = ctag.runtimeClass.newInstance // ???
      fields.foreach { case f => f.set(r, m(f.getName)) }
      r;
    }
  }

There are a couple of issues with the code:

  1. The line with val r: T = ... has a compile error because the compiler thinks it's not guaranteed to have the right type. (I'm generally unsure of how to create a new instance of a generic class in a typesafe way -- not sure why this isn't safe since the instance of Serializer is created with a class tag whose type is checked by the compiler).
  2. The objects I'm creating are expected to be immutable case class objects, which are guaranteed to be fully constructed if created in the usual way. However, since I'm mutating the fields of instances of these objects in the deserialize method, how can I be sure that the objects will not be seen as partially constructed (due to caching and instruction reordering) if they are published to other threads?

回答1:

  1. ClassTag's runtimeClass method returns Class[_], not Class[T], probably due to the fact generics in Scala and Java behave differently; you can try casting it forcefully: val r: T = ctag.runtimeClass.newInstance.asInstanceOf[T]

  2. newInstance calls the default, parameterless constructor. If the class doesn't have one, newInstance will throw InstantiationException. There's no way around it, except for:

    • looking around for other constructors

    • writing custom serializers (see how Gson does that; BTW Gson can automatically serialize only classes with parameterless constructors and those classes it has predefined deserializers for)

    • for case classes, finding their companion object and calling its apply method

Anyhow, reflection allows for modifying final fields as well, so if you manage to create an immutable object, you'll be able to set its fields.