Open dallmeier opened 10 years ago
PS: Tested against cde968a6c1c1b020929d34e5449cf28263372a67
on branch 0.9.x
.
Hi there, thanks for the report!
This is actually intended behavior – though I agree that it can be confusing, and that maybe we can/should do something about it.
First, background: this happens due to Scala/pickling's role as a serialization framework first – which means that its first goal is to encode (in JSON/binary/etc) the minimum required to serialize/deserialize instances of Scala classes.
In your example, name
is effectively static. That is, for every new instance of TheClass
, name
will always be "TestName"
– we know this at compile-time, so it's unnecessary (from the point of view of efficient serialization/deserialization) to include that field in the pickle since we can just call TheClass
's constructor to fill name
with the value "TestName"
.
However, what you point out is a very interesting problem that arises from the fact that Scala/pickling is more than a simple serialization framework – aside from basic serialization, people often would also like to use it to publish information for running services via formats like JSON, and in these cases, fields of Scala objects might conceptually be mapped directly to JSON fields (and, like in your case, could be elided!). In such a situation, unpickling is likely unnecessary.
So, the question is – knowing this, as a user, what would you prefer? We could add an annotation like @pickleMe
(name TBD) to ensure that a field gets stored in a pickle.
Thanks for getting back so quickly. While reading your answer I realized that my bug report was incomplete. It's actually the unpickling that fails. I have minimized the test case too much, and so far I have failed to reproduce the unpickling error that I see in production. I'll continue working on that and let you know as soon as I have a good test case that describes my problem.
Regarding your question I agree that when using json serialized objects outside scala, being able to annotate fields would definetly be the way to go.
Ah, gotcha.
I tried your above example, and unpickling works fine for me on 0.9.x:
scala> trait TheTrait {
| val name : String
| }
defined trait TheTrait
scala>
scala> sealed abstract class BaseClass extends TheTrait {
| val name = "TestName"
| }
defined class BaseClass
scala>
scala> case class TheClass(value : Int) extends BaseClass
defined class TheClass
scala> import scala.pickling._
import scala.pickling._
scala> import json._
import json._
scala> val instance = TheClass(1)
instance: TheClass = TheClass(1)
scala> val pickle = instance.pickle
pickle: scala.pickling.json.JSONPickle =
JSONPickle({
"tpe": "TheClass",
"value": 1
})
scala> pickle.unpickle[TheClass]
res0: TheClass = TheClass(1)
Added a test case for your example here: https://github.com/scala/pickling/pull/186
You're right. Took me a while to reproduce my actual problem. Here is my test case that fails on HEAD in develop:
package pickling.run
import org.scalatest.FunSuite
import scala.pickling._
import json._
trait Message {
val id: String
val recipient: String
}
case class EventMessage(id: String, ev: Event) extends Message {
override val recipient = ev.eventType.toString
}
trait Event {
val name : String
val eventType: EventType
}
class EventType(private val path: String)
sealed abstract class StartTaskEvent extends Event {
val name = "starttask"
override val eventType = new EventType("StartTask")
}
case class StartSpecializedTaskEvent(value : Int) extends StartTaskEvent {
override val eventType = new EventType("StartTask.Specialized")
}
/**
*/
class TraitWithAbstractValueTest extends FunSuite {
def toPickle(message: Message) : String = {
message.pickle.value
}
def fromPickle(pickleString: String) : Message = {
val pickle = JSONPickle(pickleString)
pickle.unpickle[Message]
}
test("main") {
val event = EventMessage("theid", new StartSpecializedTaskEvent(1))
val pickledEvent = fromPickle(toPickle(event))
println(pickledEvent)
}
}
Running this yields a NullPointerException:
java.lang.NullPointerException was thrown.
java.lang.NullPointerException
at pickling.run.EventMessage.<init>(TraitWithAbstractValueTest.scala:16)
at pickling.run.TraitWithAbstractValueTest$PicklingRunEventMessageUnpickler$macro$3$2$.unpickle(TraitWithAbstractValueTest.scala:45)
at pickling.run.TraitWithAbstractValueTest.fromPickle(TraitWithAbstractValueTest.scala:45)
at pickling.run.TraitWithAbstractValueTest$$anonfun$1.apply$mcV$sp(TraitWithAbstractValueTest.scala:50)
at pickling.run.TraitWithAbstractValueTest$$anonfun$1.apply(TraitWithAbstractValueTest.scala:48)
at pickling.run.TraitWithAbstractValueTest$$anonfun$1.apply(TraitWithAbstractValueTest.scala:48)
at org.scalatest.Transformer$$anonfun$apply$1.apply(Transformer.scala:22)
at org.scalatest.Transformer$$anonfun$apply$1.apply(Transformer.scala:22)
at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
at org.scalatest.Transformer.apply(Transformer.scala:22)
at org.scalatest.Transformer.apply(Transformer.scala:20)
at org.scalatest.FunSuiteLike$$anon$1.apply(FunSuiteLike.scala:158)
at org.scalatest.Suite$class.withFixture(Suite.scala:1121)
at org.scalatest.FunSuite.withFixture(FunSuite.scala:1559)
at org.scalatest.FunSuiteLike$class.invokeWithFixture$1(FunSuiteLike.scala:155)
at org.scalatest.FunSuiteLike$$anonfun$runTest$1.apply(FunSuiteLike.scala:167)
at org.scalatest.FunSuiteLike$$anonfun$runTest$1.apply(FunSuiteLike.scala:167)
at org.scalatest.SuperEngine.runTestImpl(Engine.scala:306)
at org.scalatest.FunSuiteLike$class.runTest(FunSuiteLike.scala:167)
at org.scalatest.FunSuite.runTest(FunSuite.scala:1559)
at org.scalatest.FunSuiteLike$$anonfun$runTests$1.apply(FunSuiteLike.scala:200)
at org.scalatest.FunSuiteLike$$anonfun$runTests$1.apply(FunSuiteLike.scala:200)
at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:413)
at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:401)
at scala.collection.immutable.List.foreach(List.scala:381)
at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:401)
at org.scalatest.SuperEngine.org$scalatest$SuperEngine$$runTestsInBranch(Engine.scala:396)
at org.scalatest.SuperEngine.runTestsImpl(Engine.scala:483)
at org.scalatest.FunSuiteLike$class.runTests(FunSuiteLike.scala:200)
at org.scalatest.FunSuite.runTests(FunSuite.scala:1559)
at org.scalatest.Suite$class.run(Suite.scala:1423)
at org.scalatest.FunSuite.org$scalatest$FunSuiteLike$$super$run(FunSuite.scala:1559)
at org.scalatest.FunSuiteLike$$anonfun$run$1.apply(FunSuiteLike.scala:204)
at org.scalatest.FunSuiteLike$$anonfun$run$1.apply(FunSuiteLike.scala:204)
at org.scalatest.SuperEngine.runImpl(Engine.scala:545)
at org.scalatest.FunSuiteLike$class.run(FunSuiteLike.scala:204)
at org.scalatest.FunSuite.run(FunSuite.scala:1559)
at org.scalatest.tools.SuiteRunner.run(SuiteRunner.scala:55)
at org.scalatest.tools.Runner$$anonfun$doRunRunRunDaDoRunRun$3.apply(Runner.scala:2563)
at org.scalatest.tools.Runner$$anonfun$doRunRunRunDaDoRunRun$3.apply(Runner.scala:2557)
at scala.collection.immutable.List.foreach(List.scala:381)
at org.scalatest.tools.Runner$.doRunRunRunDaDoRunRun(Runner.scala:2557)
at org.scalatest.tools.Runner$$anonfun$runOptionallyWithPassFailReporter$2.apply(Runner.scala:1044)
at org.scalatest.tools.Runner$$anonfun$runOptionallyWithPassFailReporter$2.apply(Runner.scala:1043)
at org.scalatest.tools.Runner$.withClassLoaderAndDispatchReporter(Runner.scala:2722)
at org.scalatest.tools.Runner$.runOptionallyWithPassFailReporter(Runner.scala:1043)
at org.scalatest.tools.Runner$.run(Runner.scala:883)
at org.scalatest.tools.Runner.run(Runner.scala)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.runScalaTest2(ScalaTestRunner.java:141)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.main(ScalaTestRunner.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
I'm not sure of the original title of the bug report still applies. Could you run the test and let me know if I'm trying something stupid?
I accidentally closed the bug report. Sorry.
Any updates or hints on this? I reran the above test against HEAD of 0.9.x.
I am still seeing NPE for https://github.com/scala/pickling/issues/180#issuecomment-54806095 with Pickling 0.10.0.
{
"$type": "pickling.run.EventMessage",
"id": "theid",
"ev": {
"$type": "pickling.run.StartSpecializedTaskEvent",
"value": 1
}
}
[info] TraitWithAbstractValueTest:
[info] - main *** FAILED ***
[info] java.lang.NullPointerException:
[info] at pickling.run.EventMessage.<init>(test.scala:16)
[info] at pickling.run.TraitWithAbstractValueTest$PicklingRunMessageUnpickler$macro$6$2$PicklingRunEventMessageUnpickler$macro$7$2$.unpickle(test.scala:45)
[info] at pickling.run.TraitWithAbstractValueTest$PicklingRunMessageUnpickler$macro$6$2$.unpickle(test.scala:45)
[info] at scala.pickling.Unpickler$class.unpickleEntry(Pickler.scala:79)
[info] at pickling.run.TraitWithAbstractValueTest$PicklingRunMessageUnpickler$macro$6$2$.unpickleEntry(test.scala:45)
[info] at scala.pickling.functions$.unpickle(functions.scala:11)
[info] at scala.pickling.UnpickleOps.unpickle(Ops.scala:23)
[info] at pickling.run.TraitWithAbstractValueTest.fromPickle(test.scala:45)
[info] at pickling.run.TraitWithAbstractValueTest$$anonfun$1.apply$mcV$sp(test.scala:52)
[info] at pickling.run.TraitWithAbstractValueTest$$anonfun$1.apply(test.scala:48)
[info] ...
I have an abstract class that extends a trait with an abstract
val
, and a case class that extends the abstract class:I would expect the generated pickle to contain val
name
. However, the output I get is: