Search code examples
jsonscalascala-pickling

Scala Pickling doesn't seem to work with Point2D.Double


I'm working on a Scala program that uses the Scala Pickling library to serialize and deserialize a Map object that contains a String and a Point2D.Double object from the java.awt.geom package.

Here's the relevant logic:

contents +=
      new Button("Save Config") {
        reactions += {
          case ButtonClicked(_) => {
            var m: Map[String, Point2D.Double] = Map()
            nodeFields.foreach(x => {
              m += (x._1 -> new Point2D.Double(x._2._1.text.toDouble, x._2._2.text.toDouble))
            })
            val pkl = m.pickle
            fc.showSaveDialog(null)
            val outputFile = fc.selectedFile
            val writer = new PrintWriter(outputFile)
            writer.write(pkl.value)
            writer.close()
            Dialog.showMessage(null, "Success!")
          }
        }
      }

If you need to see more, here's the commit with the offending logic

As it stands, the JSON formatted string output from pkl.value is a working serialized Map[String, Point2D.Double], except that the values of Point2D.Double are dropped!

Here's a snippet of the output:

{
  "$type": "scala.collection.mutable.Map[java.lang.String,java.awt.geom.Point2D.Double]",
  "elems": [
    {
    "$type": "scala.Tuple2[java.lang.String,java.awt.geom.Point2D.Double]",
    "_1": "BOTTOMLANE\r",
    "_2": {

    }
  },
    {
    "$type": "scala.Tuple2[java.lang.String,java.awt.geom.Point2D.Double]",
    "_1": "UPPERLANESECOND_0\r",
    "_2": {

    }
  },
    {
    "$type": "scala.Tuple2[java.lang.String,java.awt.geom.Point2D.Double]",
    "_1": "upperSecondTower_1",
    "_2": {

    }
  },
...
  ]
}

What can I do to fix this?


Solution

  • scala-pickling can not directly pickle/unpickle Point2D.Double because it has no public fields (the x and y values are accessible through the getX and getY getters).

    A possible Pickler / Unpickler for Point2D.Double would be :

    object Point2DPickler { 
      import scala.pickling._
      import scala.pickling.Defaults._
      import java.awt.geom.Point2D
    
      type DoublePoint = java.awt.geom.Point2D.Double
      implicit object Point2DDoublePickle extends Pickler[DoublePoint] with Unpickler[DoublePoint] {
        private val doubleUnpickler = implicitly[Unpickler[Double]]    
    
        override def tag = FastTypeTag[java.awt.geom.Point2D.Double]
    
        override def pickle(point: DoublePoint, builder: PBuilder) = {
          builder.beginEntry(point)
          builder.putField("x",
            b => b.hintTag(FastTypeTag.Double).beginEntry(point.getX).endEntry()
          )
          builder.putField("y",
            b => b.hintTag(FastTypeTag.Double).beginEntry(point.getY).endEntry()
          )
          builder.endEntry()
        }
    
        override def unpickle(tag: String, reader: PReader): DoublePoint = {
          val x = doubleUnpickler.unpickleEntry(reader.readField("x")).asInstanceOf[Double]
          val y = doubleUnpickler.unpickleEntry(reader.readField("y")).asInstanceOf[Double]
          new Point2D.Double(x, y)
        }
      }
    }
    

    Which could be used as :

    import scala.pickling.Defaults._
    import scala.pickling.json._
    import java.awt.geom.Point2D
    
    import Point2DPickler._
    
    val dpoint = new Point2D.Double(1d, 2d)
    
    scala> val json = dpoint.pickle
    json: pickling.json.pickleFormat.PickleType =
    JSONPickle({
      "$type": "java.awt.geom.Point2D.Double",
      "x": {
        "$type": "scala.Double",
        "value": 1.0
      },
      "y": {
        "$type": "scala.Double",
        "value": 2.0
      }
    })
    
    scala> val dpoint2 = json.value.unpickle[java.awt.geom.Point2D.Double]
    dpoint2: java.awt.geom.Point2D.Double = Point2D.Double[1.0, 2.0]