Type Registry

There are cases when the exact type to be deserialized is not known at compile time. In this case an additional type ID must be serialized to the data stream in order to being able to select the correct deserializer when reading it back.

desert is not doing any automatic reflection based type identification. All the types that can participate in the above described scenario must be explicitly registered to a type registry:

import io.github.vigoo.desert._
import io.github.vigoo.desert.shapeless._

case class TestProd(value: Int)
object TestProd {
  implicit val codec: BinaryCodec[TestProd] = DerivedBinaryCodec.derive
}
val typeRegistry1 = DefaultTypeRegistry()
  .register[String]
  .register[TestProd]
  .freeze() 
// typeRegistry1: TypeRegistry = io.github.vigoo.desert.DefaultTypeRegistry@25611228

In this example we register two types, String and a custom case class TestProd. We can then use this registry to try serializing a value of AnyRef. If the type matches any of the registered ones, it will succeed:

val dataOrFailure1 = serializeUnknownToArray("Hello world", typeRegistry1)
// dataOrFailure1: Either[DesertFailure, Array[Byte]] = Right(
//   value = Array(1, 22, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100)
// )
val dataOrFailure2 = serializeUnknownToArray(TestProd(11), typeRegistry1)
// dataOrFailure2: Either[DesertFailure, Array[Byte]] = Right(
//   value = Array(2, 0, 0, 0, 0, 11)
// )

Both data streams start with a compact type ID (in this case it is 1 and 2) so when deserializing them, the library knows which codec to use:

val x = dataOrFailure1.flatMap(data1 => deserializeUnknownFromArray(data1, typeRegistry1))
// x: Either[DesertFailure, Any] = Right(value = "Hello world")
val y = dataOrFailure2.flatMap(data2 => deserializeUnknownFromArray(data2, typeRegistry1))
// y: Either[DesertFailure, Any] = Right(value = TestProd(value = 11))

Unknowns in fields

These functions (and the similar ones working on BinaryInput and BinaryOutput instances) are good when the top-level type is unknown, like when serializing arbitrary actor messages.

But what if a field’s type is an open trait? For this the BinaryCodec.unknown function can be used to automatically use the above described mechanisms.

Let’s see an example:

trait Iface
object Iface {
  implicit val codec: BinaryCodec[Iface] = BinaryCodec.unknown
}

case class Impl1(x: Int) extends Iface
object Impl1 {
  implicit val codec: BinaryCodec[Impl1] = DerivedBinaryCodec.derive
}

case class Impl2(x: Int) extends Iface
object Impl2 {
  implicit val codec: BinaryCodec[Impl2] = DerivedBinaryCodec.derive
}

case class Outer(inner: Iface)
object Outer {
  implicit val codec: BinaryCodec[Outer] = DerivedBinaryCodec.derive
}

val typeRegistry2 = DefaultTypeRegistry()
  .register[Impl1]
  .register[Impl2]
  .freeze()
// typeRegistry2: TypeRegistry = io.github.vigoo.desert.DefaultTypeRegistry@354305d9
val dataOrFailure = serializeToArray(Outer(Impl2(11)), typeRegistry2)
02000011
val result = dataOrFailure.flatMap(data => deserializeFromArray[Outer](data, typeRegistry2))
// result: Either[DesertFailure, Outer] = Right(
//   value = Outer(inner = Impl2(x = 11))
// )