Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend Scala 3 enum serialization to handle ADTs #8

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ class ScalaEnumNameSerializer[T <: EnumValue] extends Serializer[T] {
def read(kryo: Kryo, input: Input, typ: Class[? <: T]): T = {
val clazz = kryo.readClass(input).getType
val name = input.readString()
// using value instead of ordinal to make serialization more stable, e.g. allowing reordering without breaking compatibility
clazz.getDeclaredMethod("valueOf", classOf[String]).invoke(null, name).asInstanceOf[T]

try {
// using value instead of ordinal to make serialization more stable, e.g. allowing reordering without breaking compatibility
clazz.getDeclaredMethod("valueOf", classOf[String]).invoke(null, name).asInstanceOf[T]
} catch {
case _: java.lang.NoSuchMethodException =>
// work around Scala 3 ADT-like enums missing valueOf method
val objectClazz = Class.forName(clazz.getName + "$")
objectClazz.getDeclaredField(name).get(null).asInstanceOf[T]
}
}

def write(kryo: Kryo, output: Output, obj: T): Unit = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package io.altoo.serialization.kryo.scala.serializer

import com.esotericsoftware.kryo.util.{DefaultClassResolver, ListReferenceResolver}
import io.altoo.serialization.kryo.scala.ScalaVersionSerializers
import io.altoo.serialization.kryo.scala.serializer.{ScalaEnumNameSerializer, ScalaKryo}
import io.altoo.serialization.kryo.scala.testkit.{AbstractKryoTest, KryoSerializationTesting}
import io.altoo.serialization.kryo.scala.testkit.KryoSerializationTesting
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

Expand All @@ -17,34 +15,52 @@ object ScalaEnumSerializationTest {
case class EmbeddedEnum(sample: Sample) {
def this() = this(null)
}

enum SimpleADT {
case A()
case B
}
}

class ScalaEnumSerializationTest extends AnyFlatSpec with Matchers with KryoSerializationTesting {
class ScalaEnumSerializationTest extends AnyFlatSpec with Matchers with KryoSerializationTesting {
import ScalaEnumSerializationTest.*

val kryo = new ScalaKryo(new DefaultClassResolver(), new ListReferenceResolver())
kryo.setRegistrationRequired(false)
kryo.addDefaultSerializer(classOf[scala.runtime.EnumValue], new ScalaEnumNameSerializer[scala.runtime.EnumValue])


behavior of "Kryo serialization"

it should "reoundtrip enum" in {
it should "round trip enum" in {
kryo.setRegistrationRequired(false)

testSerializationOf(Sample.B)
}

it should "reoundtrip external enum" in {
it should "round trip external enum" in {
kryo.setRegistrationRequired(false)

testSerializationOf(io.altoo.external.ExternalEnum.A)
}

it should "reoundtrip embedded enum" in {
it should "round trip embedded enum" in {
kryo.setRegistrationRequired(false)
kryo.register(classOf[EmbeddedEnum], 46)

testSerializationOf(EmbeddedEnum(Sample.C))
}

it should "round trip adt enum class using generic field serializer" in {
kryo.setRegistrationRequired(false)
kryo.register(classOf[SimpleADT], 47)

testSerializationOf(SimpleADT.A)
}

it should "round trip adt enum object using enum serializer" in {
kryo.setRegistrationRequired(false)
kryo.register(classOf[SimpleADT], 47)

testSerializationOf(SimpleADT.B)
}
}
Loading