Scala Serialization Guide
Apache Fory™ supports all scala object serialization:
case
class serialization supportedpojo/bean
class serialization supportedobject
singleton serialization supportedcollection
serialization supported- other types such as
tuple/either
and basic types are all supported too.
Scala 2 and 3 are both supported.
Install
To add a dependency on Apache Fory™ scala for with sbt, use the following:
libraryDependencies += "org.apache.fory" %% "fory-scala" % "0.12.3"
Quick Start
case class Person(name: String, id: Long, github: String)
case class Point(x : Int, y : Int, z : Int)
object ScalaExample {
val fory: Fory = Fory.builder().withScalaOptimizationEnabled(true).build()
// Register optimized fory serializers for scala
ScalaSerializers.registerSerializers(fory)
fory.register(classOf[Person])
fory.register(classOf[Point])
def main(args: Array[String]): Unit = {
val p = Person("Shawn Yang", 1, "https://github.com/chaokunyang")
println(fory.deserialize(fory.serialize(p)))
println(fory.deserialize(fory.serialize(Point(1, 2, 3))))
}
}
Fory creation
When using fory for scala serialization, you should create fory at least with following options:
import org.apache.fory.Fory
import org.apache.fory.serializer.scala.ScalaSerializers
val fory = Fory.builder().withScalaOptimizationEnabled(true).build()
// Register optimized fory serializers for scala
ScalaSerializers.registerSerializers(fory)
Depending on the object types you serialize, you may need to register some scala internal types:
fory.register(Class.forName("scala.Enumeration.Val"))
If you want to avoid such registration, you can disable class registration by ForyBuilder#requireClassRegistration(false)
.
Note that this option allow to deserialize objects unknown types, more flexible but may be insecure if the classes contains malicious code.
And circular references are common in scala, Reference tracking
should be enabled by ForyBuilder#withRefTracking(true)
. If you don't enable reference tracking, StackOverflowError may happen for some scala versions when serializing scala Enumeration.
Note that fory instance should be shared between multiple serialization, the creation of fory instance is not cheap.
If you use shared fory instance across multiple threads, you should create ThreadSafeFory
instead by ForyBuilder#buildThreadSafeFory()
instead.
Serialize case class
case class Person(github: String, age: Int, id: Long)
val p = Person("https://github.com/chaokunyang", 18, 1)
println(fory.deserialize(fory.serialize(p)))
println(fory.deserializeJavaObject(fory.serializeJavaObject(p)))
Serialize pojo
class Foo(f1: Int, f2: String) {
override def toString: String = s"Foo($f1, $f2)"
}
println(fory.deserialize(fory.serialize(Foo(1, "chaokunyang"))))
Serialize object singleton
object singleton {
}
val o1 = fory.deserialize(fory.serialize(singleton))
val o2 = fory.deserialize(fory.serialize(singleton))
println(o1 == o2)
Serialize collection
val seq = Seq(1,2)
val list = List("a", "b")
val map = Map("a" -> 1, "b" -> 2)
println(fory.deserialize(fory.serialize(seq)))
println(fory.deserialize(fory.serialize(list)))
println(fory.deserialize(fory.serialize(map)))
Serialize Tuple
val tuple = (100, 10000L) //Tuple2
println(fory.deserialize(fory.serialize(tuple)))
val tuple = (100, 10000L, 10000L, "str") //Tuple4
println(fory.deserialize(fory.serialize(tuple)))
Serialize Enum
Scala3 Enum
enum Color { case Red, Green, Blue }
println(fory.deserialize(fory.serialize(Color.Green)))
Scala2 Enum
object ColorEnum extends Enumeration {
type ColorEnum = Value
val Red, Green, Blue = Value
}
println(fory.deserialize(fory.serialize(ColorEnum.Green)))
Serialize Option
val opt: Option[Long] = Some(100)
println(fory.deserialize(fory.serialize(opt)))
val opt1: Option[Long] = None
println(fory.deserialize(fory.serialize(opt1)))
Scala Class Default Values Support
Fory supports Scala class default values during deserialization when using compatible mode. This feature enables forward/backward compatibility when case classes or regular Scala classes have default parameters.
Overview
When a Scala class has default parameters, the Scala compiler generates methods in the companion object (for case classes) or in the class itself (for regular Scala classes) like apply$default$1
, apply$default$2
, etc. that return the default values. Fory can detect these methods and use them when deserializing objects where certain fields are missing from the serialized data.
Supported Class Types
Fory supports default values for:
- Case classes with default parameters
- Regular Scala classes with default parameters in their primary constructor
- Nested case classes with default parameters
- Deeply nested case classes with default parameters
How It Works
-
Detection: Fory detects if a class is a Scala class by checking for the presence of default value methods (
apply$default$N
or$default$N
). -
Default Value Discovery:
- For case classes: Fory scans the companion object for methods named
apply$default$1
,apply$default$2
, etc. - For regular Scala classes: Fory scans the class itself for methods named
$default$1
,$default$2
, etc.
- For case classes: Fory scans the companion object for methods named
-
Field Mapping: During deserialization, Fory identifies fields that exist in the target class but are missing from the serialized data.
-
Value Application: After reading all available fields from the serialized data, Fory applies default values to any missing fields using direct field access for optimal performance.
Usage
This feature is automatically enabled when:
- Compatible mode is enabled (
withCompatibleMode(CompatibleMode.COMPATIBLE)
) - The target class is detected as a Scala class with default values
- A field is missing from the serialized data but exists in the target class
No additional configuration is required.
Examples
Case Class with Default Values
// Class WITHOUT default values (for serialization)
case class PersonNoDefaults(name: String)
// Class WITH default values (for deserialization)
case class PersonWithDefaults(name: String, age: Int = 25, city: String = "Unknown")
val fory = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE)
.withScalaOptimizationEnabled(true)
.build()
// Serialize using class without default values
val original = PersonNoDefaults("John")
val serialized = fory.serialize(original)
// Deserialize into class with default values - missing fields will use defaults
val deserialized = fory.deserialize(serialized, classOf[PersonWithDefaults])
// deserialized.name will be "John"
// deserialized.age will be 25 (default)
// deserialized.city will be "Unknown" (default)
Regular Scala Class with Default Values
// Class WITHOUT default values (for serialization)
class EmployeeNoDefaults(val name: String)
// Class WITH default values (for deserialization)
class EmployeeWithDefaults(val name: String, val age: Int = 30, val department: String = "Engineering")
val fory = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE)
.withScalaOptimizationEnabled(true)
.build()
// Serialize using class without default values
val original = new EmployeeNoDefaults("Jane")
val serialized = fory.serialize(original)
// Deserialize into class with default values - missing fields will use defaults
val deserialized = fory.deserialize(serialized, classOf[EmployeeWithDefaults])
// deserialized.name will be "Jane"
// deserialized.age will be 30 (default)
// deserialized.department will be "Engineering" (default)