Search code examples
scalajodatimescala-pickling

Custom unpickler fails with "NoSuchElementException: : key not found: value"


I'm trying to pickle/unpickle a joda DateTime instance to/from json with pickling. With pickling 0.8.0, if I don't supply a custom pickler, I get

JSONPickle({
  "tpe": "org.joda.time.DateTime"
})

When I do:

class DateTimePickler(implicit val format: PickleFormat) extends
  SPickler[DateTime] with Unpickler[DateTime] {
    private val stringUnpickler = implicitly[Unpickler[String]]

    def pickle(picklee: DateTime, builder: PBuilder): Unit = {
      builder.beginEntry(picklee)
      builder.putField("date", b =>
        b.hintTag(stringTag).beginEntry(picklee.toString).endEntry()
      )
      builder.endEntry()
    }

    override def unpickle(tag: => FastTypeTag[_], reader: PReader): DateTime = {
      reader.hintTag(stringTag)
      val tag = reader.beginEntry()
      logger.debug(s"tag is ${tag.toString}")
      val value = stringUnpickler.unpickle(tag, reader).asInstanceOf[String] //can't debug NoSuchElementException: : key not found: value
      logger.debug(s"value is $value")
      reader.endEntry()

      val timeZone = DateTimeZone.getDefault
      DateTime.parse(value).withZone(timeZone) //otherwise the parsed DateTime won't equal the original
    }
  }

  implicit def genDateTimePickler(implicit format: PickleFormat) = new DateTimePickler

I get

JSONPickle({
  "tpe": "org.joda.time.DateTime",
  "date": {
    "tpe": "java.lang.String",
    "value": "2014-09-16T17:59:25.865+03:00"
  }
})

and unpickling fails with NoSuchElementException: : key not found: value. With pickling 0.9.0-SNAPSHOT my specs2 test won't even terminate.


Solution

  • Pickling at the moment suffers from lack of documentation and uninformative error messages. Anyway, my unpickle method was written analogous to this answer, and it assumed binary serialization. The correct unpickle method found by trial and error looks like this:

    override def unpickle(tag: => FastTypeTag[_], reader: PReader): DateTime = {
      val dateReader = reader.readField("date")
      val tag = dateReader.beginEntry()
      val value = stringUnpickler.unpickle(tag, dateReader).asInstanceOf[String]
      dateReader.endEntry()
    
      val timeZone = DateTimeZone.getDefault
      DateTime.parse(value).withZone(timeZone) //otherwise the parsed DateTime won't equal the original
    }