Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala deserialization: class not found

I'm trying to understand the following issue that occurs when trying to serialize/deserialize a very simple data structure:

case class SimpleClass(i: Int)

object SerializationDebug {

  def main(args: Array[String]) {
    val c = SimpleClass(0)
    val l1 = List(c)

    serializationSaveToFile("test", l1)
    val l2 = serializationLoadFromFile("test") // .asInstanceOf ...
  }

  def serializationSaveToFile(fn: String, o: Any) {
    val fos = new FileOutputStream(fn)
    val oos = new ObjectOutputStream(fos)
    oos.writeObject(o)
    oos.close()
  }

  def serializationLoadFromFile(fn: String): Any = {
    val fis = new FileInputStream(fn)
    val ois = new ObjectInputStream(fis)
    return ois.readObject()
  }  
}

When trying to run this code I get java.lang.ClassNotFoundException: SimpleClass in the deserialization step. The current results of my investigations are:

  • The example works when I exchange SimpleClass by some built-in type, i.e., I can deserialize List[Int] or List[(Int, Double)] without a problem. Mixing built-in types with my SimpleClass (i.e. having a List[Any]) again throws an exception.
  • I tried to define SimpleClass in other scopes (for instance nested in the object or in the local scope even) but that did not change anything. Also, having a normal (non-case) class extending Serializable gives the same result.
  • Even more puzzling is that using an Array[SimpleClass] instead of List does work! Trying other containers confirms this strange inconsistency: having SimpleClass as type parameter within an immutable map works, in case of a mutable map I get the exception.

In case it matters: My Scala version is 2.10.0; JDK is 1.7.0.

What is going on here? Is this supposed to fail or is it some kind of bug? My actual problem at hand involves a much more complex data structure (a lot of nesting; mixture of built-in and own classes). Any suggestions to serializing/deserializing this data structure in a minimal-intrusive simple way (i.e. without having to find working combinations of container classes and their type parameters) are also welcome!

like image 455
bluenote10 Avatar asked May 05 '13 15:05

bluenote10


2 Answers

This solution works fine for me:

val ois = new ObjectInputStream(new FileInputStream(fileName)) {
  override def resolveClass(desc: java.io.ObjectStreamClass): Class[_] = {
    try { Class.forName(desc.getName, false, getClass.getClassLoader) }
    catch { case ex: ClassNotFoundException => super.resolveClass(desc) }
  }
}

of course, when writing object, we use the same way:

val ois = new ObjectOutputStream(new FileOutputStream(path))
ois.writeObject(myobject)
like image 165
loveallufev Avatar answered Sep 20 '22 12:09

loveallufev


@trenobus @bluenote10 related to your findings:

I just ran into this today and I think it's because the class' name is different (mangled) when you define it in the console. For instance, I serialized a class One from scala

> import java.io._
import java.io._
> case class One(s: String, b: Boolean)
defined class One
> new ObjectOutputStream(new FileOutputStream("data")) writeObject One("abc", true)

I then tried to deserialize it in java from the same file, having prepared a similar class named One in the top level (I made SerialVersionUIDs be the same as well, didn't include it here since it didn't matter), but got this error:

Exception in thread "main" java.lang.ClassNotFoundException: $line4.$read$$iw$$iw$One

Suggesting that scala (rightfully) creates a new JVM class each time you define a class, and presumably keeps an internal mapping so that you can refer to it by its defined name (One).

Similarly, because each such class is different in JVM, if you redefine class One in that same scala console, and then try to deserialize from the data file, you'll get an object of the original class (not of the new class that you redefined).

like image 31
Dan Avatar answered Sep 18 '22 12:09

Dan