jackson-module-scala icon indicating copy to clipboard operation
jackson-module-scala copied to clipboard

Sealed hierarchy of case classes serialization won't work

Open edmondop opened this issue 9 years ago • 7 comments

Sealed hierarchy of case classes are widely used in Scala, however in Jackson-Scala they don't seem to be supported.

We are looking for a workaround: when we try to add custom serializers / deserializers for our sealed trait and then use the default "serializers" for the subclasses, the serializers/deserializers for the trait take higher priority and this results in a Stackoverflow error.

Is there a solution for this?

edmondop avatar Oct 13 '16 17:10 edmondop

Can you provide a more thorough example of your case class structure? I'm not understanding why it doesn't work without custom serializers/deserializers in the first place.

The case class serializer is found very early in the class hierarchy. When it later stumbles upon your custom serializer it uses that since it appears more specific than the generic case class one it found earlier.

nbauernfeind avatar Nov 30 '16 13:11 nbauernfeind

EDIT My first thought was wrong. Problem I described is not the same that described in issue . ORIGINAL As far as I understand we talk about following situation:

case class Wrapper(v: Type)
sealed trait Type
case class TypeA(v: String) extends Type
case object TypeB extends Type

When Wrapper(TypeA("1")) is serialized we expect:

{
   "v": {
      "type": "TypeA",
      "value": {
         "v": "1"
      }  
   }
}

because otherwise it will be impossible to deserialize it properly.

I looked into code and found that jackson is unable to handle this situation. We can try to check that object to be serialized is of class that extends sealed trait and create propper serializer, but it will break usecase when we directly reference to such case class. Like that:

case class Wrapper(v: TypeA)
sealed trait Type
case class TypeA(v: String) extends Type
case object TypeB extends Type

Because in this situation we do not need to serialize type information.

And jackson doesn't store context in which class is used, so "there is no hope for us".

a-e-tsvetkov avatar Jun 06 '18 14:06 a-e-tsvetkov

Have you approached this problem using JsonTypeInfo? See: https://stackoverflow.com/questions/17135166/looking-for-a-good-example-of-polymorphic-serialization-deserialization-using-ja/26720380#26720380

nbauernfeind avatar Jun 06 '18 17:06 nbauernfeind

I tried but

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.PROPERTY,
)
@JsonSubTypes(Array(
  new Type(value = classOf[TypeA]),
  new Type(value = classOf[TypeB])
)
)
sealed trait Some
case class TypeA(v: String) extends Some
case object TypeB extends Some

compilation failed

[ERROR] /home/atcvetkov/project/dummy/dummy-scala/src/main/scala/dummy/App.scala:27: error: not found: type TypeB
[INFO]   new Type(value = classOf[TypeB])
[INFO]                            ^

a-e-tsvetkov avatar Jun 06 '18 17:06 a-e-tsvetkov

TypeB in the code you provided is an object not a class. The proper way to get the class of an object is the TypeB.getClass. Unfortunately the type info annotation requires the value be constant which means you can't call getClass() on TypeB's singleton/object. Though they are discouraged, have you considered using parameter-less case classes instead of case objects?

nbauernfeind avatar Jun 06 '18 18:06 nbauernfeind

Yes. It will solve the problem, but I thing they are discouraged for a reason (unknown for me), so I just created custom serializer and deserializer for sealed trait. It is possible to create generic one, but it will have numbers of restriction and not so obvious problem. I don't think it is good idea to include such generic serializer and deserializer in this (or some other) library.

a-e-tsvetkov avatar Jun 06 '18 19:06 a-e-tsvetkov

The plan has always been to add "real" scala support in scala 2.12 once we had access to a real scala reflection API. Now that it's available no one has really had much time to make it happen. As of right now, though, this is just not possible yet.

nbauernfeind avatar Jun 06 '18 20:06 nbauernfeind