A path forward for pickling Java objects #296
Description
Related: #60, #263, #33, #211, #295, #301
This is a general issue for discussing pickling Java objects. Hopefully we can reach some positive path forward.
Joda-Time isn't fixed
First, I would like to dispel the notion that Pickling 0.10.0 works with Joda-Time. It really doesn't.
Here's a demonstration of #33 and #60 combined:
scala> import scala.pickling._, Defaults._, json._
import scala.pickling._
import Defaults._
import json._
scala> import org.joda.time._
import org.joda.time._
scala> val dt = DateTime.now.plusDays(3)
dt: org.joda.time.DateTime = 2015-02-15T23:12:36.915-05:00
scala> dt.pickle
res0: scala.pickling.json.pickleFormat.PickleType =
JSONPickle({
"$type": "org.joda.time.DateTime",
"Millis": "1424059956915",
"Chronology": {
"$type": "org.joda.time.chrono.ISOChronology"
}
})
scala> res0.unpickle[DateTime]
res1: org.joda.time.DateTime = 2015-02-15T23:12:36.915-05:00
scala> val ld = LocalDate.now.plusDays(3)
ld: org.joda.time.LocalDate = 2015-02-15
scala> ld.pickle
res2: scala.pickling.json.pickleFormat.PickleType =
JSONPickle({
"$type": "org.joda.time.LocalDate"
})
scala> res2.unpickle[LocalDate]
res3: org.joda.time.LocalDate = 2015-02-12
Why does it "work" for DateTime
, but not for LocalDate
? What was added in #211 is a matching on method name that starts with "set" and "get". DateTime
appears to roundtrip because it happens to have to have setters setChronology
and setMillis
. LocalDate
doesn't have any methods that starts with "set" so Pickling 0.10.0 happily generates a pickler that pickles an empty object.
Note "set" and "get" are not the only pair allowed in JavaBeans Specification.
Guessing is dangerous
#263 illustrates that java.lang.Byte
can roundtrip any number to 0
.
scala> val r1: java.lang.Byte = 10.toByte
r1: Byte = 10
scala> val r2 = r1.pickle.unpickle[java.lang.Byte]
r2: Byte = 0
The fact that it's a boxed primitives is irrelevant. Due to the current logic of only picking up "set" and "get" pairs, the state of java.lang.Byte
is not the exception, it's the rule. Note the problem with java.lang.Byte
is not the fact that it's not handled, but the fact that it did not fail to compile. We should stop guessing, and fail when we don't have the complete information.
(More reports from the field)
scala> val uri = new java.net.URI("urn:isbn:096139210x")
uri: java.net.URI = urn:isbn:096139210x
scala> uri.pickle.unpickle[java.net.URI]
res5: java.net.URI = null
Java reflection
Are we doomed about Java? If we give up the speed and fall back to using reflection, we can still achieve correctness. I think that's a better approach.
scala> val r1: java.lang.Byte = 10.toByte
r1: Byte = 10
scala> r1.getClass.getDeclaredFields.toList foreach println
public static final byte java.lang.Byte.MIN_VALUE
public static final byte java.lang.Byte.MAX_VALUE
public static final java.lang.Class java.lang.Byte.TYPE
private final byte java.lang.Byte.value
public static final int java.lang.Byte.SIZE
private static final long java.lang.Byte.serialVersionUID
scala> {
| val f = r1.getClass.getDeclaredField("value")
| f.setAccessible(true)
| f.getByte(r1)
| }
res1: Byte = 10
BeanInfo
There's BeanInfo
, but I don't think we can discover setters reliably using it.
scala> import org.joda.time._
import org.joda.time._
scala> import java.beans.Introspector
import java.beans.Introspector
scala> val info = Introspector.getBeanInfo(classOf[DateTime])
info: java.beans.BeanInfo = java.beans.GenericBeanInfo@b508085
scala> info.getPropertyDescriptors.toList
res2: List[java.beans.PropertyDescriptor] = List(java.beans.PropertyDescriptor[name=afterNow; propertyType=boolean; readMethod=public boolean org.joda.time.base.AbstractInstant.isAfterNow()]....
scala> info.getPropertyDescriptors.toList collect { case p if p.getWriteMethod != null => p }
res3: List[java.beans.PropertyDescriptor] = List()