Document workaround for jackson serialiaztion of scala case objects (#29531)
* workaround for jackson serialiaztion of scala case objects * improved sentence
This commit is contained in:
parent
192be028a0
commit
32ec0428d3
2 changed files with 71 additions and 15 deletions
|
|
@ -164,11 +164,20 @@ when using polymorphic types.
|
||||||
|
|
||||||
### ADT with trait and case object
|
### ADT with trait and case object
|
||||||
|
|
||||||
In Scala it's common to use a sealed trait and case objects to represent enums. If the values are case classes
|
It's common in Scala to use a sealed trait and case objects to represent enums. If the values are case classes
|
||||||
the `@JsonSubTypes` annotation as described above works, but if the values are case objects it will not.
|
the `@JsonSubTypes` annotation as described above works, but if the values are case objects it will not.
|
||||||
The annotation requires a `Class` and there is no way to define that in an annotation for a `case object`.
|
The annotation requires a `Class` and there is no way to define that in an annotation for a `case object`.
|
||||||
|
|
||||||
This can be solved by implementing a custom serialization for the enums. Annotate the `trait` with
|
The easiest workaround is to define the case objects as case class without any field.
|
||||||
|
|
||||||
|
Alternatively, you can define an intermediate trait for the case object and a custom deserializer for it. The example below builds on the previous `Animal` sample by adding a fictitious, single instance, new animal, an `Unicorn`.
|
||||||
|
|
||||||
|
Scala
|
||||||
|
: @@snip [SerializationDocSpec.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala) { #polymorphism-case-object }
|
||||||
|
|
||||||
|
The case object `Unicorn` can't be used in a `@JsonSubTypes` annotation, but its trait can. When serializing the case object we need to know which type tag to use, hence the `@JsonTypeName` annotation on the object. When deserializing, Jackson will only know about the trait variant therefore we need a custom deserializer that returns the case object.
|
||||||
|
|
||||||
|
On the other hand, if the ADT only has case objects, you can solve it by implementing a custom serialization for the enums. Annotate the `trait` with
|
||||||
`@JsonSerialize` and `@JsonDeserialize` and implement the serialization with `StdSerializer` and
|
`@JsonSerialize` and `@JsonDeserialize` and implement the serialization with `StdSerializer` and
|
||||||
`StdDeserializer`.
|
`StdDeserializer`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,11 @@ import akka.serialization.SerializationExtension
|
||||||
import akka.serialization.SerializerWithStringManifest
|
import akka.serialization.SerializerWithStringManifest
|
||||||
import akka.serialization.Serializers
|
import akka.serialization.Serializers
|
||||||
import akka.testkit.TestKit
|
import akka.testkit.TestKit
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
import com.fasterxml.jackson.annotation.{ JsonSubTypes, JsonTypeInfo, JsonTypeName }
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import org.scalatest.BeforeAndAfterAll
|
import org.scalatest.BeforeAndAfterAll
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
@ -126,6 +129,8 @@ object SerializationDocSpec {
|
||||||
#//#manifestless
|
#//#manifestless
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
object Polymorphism {
|
||||||
|
|
||||||
//#polymorphism
|
//#polymorphism
|
||||||
final case class Zoo(primaryAttraction: Animal) extends MySerializable
|
final case class Zoo(primaryAttraction: Animal) extends MySerializable
|
||||||
|
|
||||||
|
|
@ -140,6 +145,35 @@ object SerializationDocSpec {
|
||||||
|
|
||||||
final case class Elephant(name: String, age: Int) extends Animal
|
final case class Elephant(name: String, age: Int) extends Animal
|
||||||
//#polymorphism
|
//#polymorphism
|
||||||
|
}
|
||||||
|
|
||||||
|
object PolymorphismMixedClassObject {
|
||||||
|
|
||||||
|
//#polymorphism-case-object
|
||||||
|
final case class Zoo(primaryAttraction: Animal) extends MySerializable
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
|
@JsonSubTypes(
|
||||||
|
Array(
|
||||||
|
new JsonSubTypes.Type(value = classOf[Lion], name = "lion"),
|
||||||
|
new JsonSubTypes.Type(value = classOf[Elephant], name = "elephant"),
|
||||||
|
new JsonSubTypes.Type(value = classOf[Unicorn], name = "unicorn")))
|
||||||
|
sealed trait Animal
|
||||||
|
|
||||||
|
final case class Lion(name: String) extends Animal
|
||||||
|
final case class Elephant(name: String, age: Int) extends Animal
|
||||||
|
|
||||||
|
@JsonDeserialize(using = classOf[UnicornDeserializer])
|
||||||
|
sealed trait Unicorn extends Animal
|
||||||
|
@JsonTypeName("unicorn")
|
||||||
|
case object Unicorn extends Unicorn
|
||||||
|
|
||||||
|
class UnicornDeserializer extends StdDeserializer[Unicorn](Unicorn.getClass) {
|
||||||
|
// whenever we need to deserialize an instance of Unicorn trait, we return the object Unicorn
|
||||||
|
override def deserialize(p: JsonParser, ctxt: DeserializationContext): Unicorn = Unicorn
|
||||||
|
}
|
||||||
|
//#polymorphism-case-object
|
||||||
|
}
|
||||||
|
|
||||||
val configDateTime = """
|
val configDateTime = """
|
||||||
#//#date-time
|
#//#date-time
|
||||||
|
|
@ -207,6 +241,19 @@ class SerializationDocSpec
|
||||||
private def serializerFor(obj: Any): SerializerWithStringManifest =
|
private def serializerFor(obj: Any): SerializerWithStringManifest =
|
||||||
serialization.serializerFor(obj.getClass).asInstanceOf[SerializerWithStringManifest]
|
serialization.serializerFor(obj.getClass).asInstanceOf[SerializerWithStringManifest]
|
||||||
|
|
||||||
|
"serialize trait + case classes" in {
|
||||||
|
import doc.akka.serialization.jackson.SerializationDocSpec.Polymorphism._
|
||||||
|
verifySerialization(Zoo(Lion("Simba"))) should ===(Zoo(Lion("Simba")))
|
||||||
|
verifySerialization(Zoo(Elephant("Dumbo", 1))) should ===(Zoo(Elephant("Dumbo", 1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
"serialize trait + case classes + case object" in {
|
||||||
|
import doc.akka.serialization.jackson.SerializationDocSpec.PolymorphismMixedClassObject._
|
||||||
|
verifySerialization(Zoo(Lion("Simba"))) should ===(Zoo(Lion("Simba")))
|
||||||
|
verifySerialization(Zoo(Elephant("Dumbo", 1))) should ===(Zoo(Elephant("Dumbo", 1)))
|
||||||
|
verifySerialization(Zoo(Unicorn)) should ===(Zoo(Unicorn))
|
||||||
|
}
|
||||||
|
|
||||||
"serialize trait + object ADT" in {
|
"serialize trait + object ADT" in {
|
||||||
import CustomAdtSerializer.Compass
|
import CustomAdtSerializer.Compass
|
||||||
import CustomAdtSerializer.Direction._
|
import CustomAdtSerializer.Direction._
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue