Skip to content

Instantly share code, notes, and snippets.

@kolemannix
Last active November 21, 2018 18:38
Show Gist options
  • Save kolemannix/c85f88e61d834f1f11d00d74c51fad84 to your computer and use it in GitHub Desktop.
Save kolemannix/c85f88e61d834f1f11d00d74c51fad84 to your computer and use it in GitHub Desktop.
Code from post "A practical type class example: SafeSerializable"
#!/usr/bin/env amm
@scala.annotation.implicitNotFound("No Serializer provided for ${A} in scope.")
trait SafeSerializable[A] {
def serialize(a: A): Array[Byte]
def deserialize(bytes: Array[Byte]): A
}
object SafeSerializable {
/**
* Factory constructor - allows easier construction of instances. E.g.,
* {{{
* val ser = SafeSerializable.make[Foo](_.toBinary, _.fromBinary)
* }}}
*/
def make[A](serFn: A => Array[Byte], deserFn: Array[Byte] => A): SafeSerializable[A] = new SafeSerializable[A] {
def serialize(a: A): Array[Byte] = serFn(a)
def deserialize(bytes: Array[Byte]): A = deserFn(bytes)
}
/**
* Summoner method. Allows the syntax
* {{{
* val serializer = SafeSerializable[String]
* }}}
*/
def apply[A](implicit instance: SafeSerializable[A]): SafeSerializable[A] = instance
// This probably does not belong here! It couples
// our typeclass to Play JSON, but it is an example
// of providing a default derived implementation
import play.api.libs.json.{ Json, Format }
implicit def deriveFromJson[A](implicit aFormat: Format[A]): SafeSerializable[A] = {
SafeSerializable.make[A](
serFn = { Json.toJson(_).toString.getBytes() },
deserFn = { Json.parse(_).as[A] }
)
}
}
object Serializer {
/**
* Invocation methods. Enables the more functional syntax
* {{{
* Serializer.serialize(myValue)
* Serializer.deserialize(myBytes)
* }}}
*/
def serialize[A: SafeSerializable](value: A): Array[Byte] = {
val serde = implicitly[SafeSerializable[A]]
serde.serialize(value)
}
def deserialize[A: SafeSerializable](bytes: Array[Byte]): A = {
val serde = implicitly[SafeSerializable[A]]
serde.deserialize(bytes)
}
}
/**
* Extension methods. Enables the 'object-oriented' syntax of method invocation:
* {{{
* import SafeSerializableSyntax._
* myFoo.serialize
* myBytes.deserialize()[Foo]
* }}}
*/
object SafeSerializableSyntax {
implicit class AnyOps[A](target: A)(implicit serializer: SafeSerializable[A]) {
def serialize: Array[Byte] = serializer.serialize(target)
}
implicit class BytesOps(bytes: Array[Byte]) {
def deserialize[A](implicit serializer: SafeSerializable[A]): A = serializer.deserialize(bytes)
}
}
case class Point(x: Int, y: Int)
implicit val pointSerializer = SafeSerializable.make[Point](
serFn = { point => s"${point.x},${point.y}".getBytes("UTF-8") },
deserFn = { bytes =>
val x :: y :: Nil = new String(bytes, "UTF-8").split(",").toList.map(_.toInt)
Point(x, y)
}
)
val point = Point(3, 4)
val ex1 = {
val pointBytes = Serializer.serialize(point)
println(new String(pointBytes))
println(Serializer.deserialize(pointBytes))
}
val ex2 = {
import SafeSerializableSyntax._
val point = Point(3, 4)
val pointBytes = point.serialize
val samePoint: Point = pointBytes.deserialize
assert(samePoint == point)
}
case class Color(red: Int, green: Int, blue: Int, alpha: Float = 1.0f)
val red = Color(255, 0, 0)
val ex3 = {
val red = Color(255, 0, 0)
// Serializer.serialize(red)
// safeserializable.sc:95: No Serializer provided for Color in scope.
// Serializer.serialize(red)
}
import $ivy.`com.typesafe.play::play-json:2.6.10`
import play.api.libs.json.{ Json, Format }
implicit val colorJsonFormat: Format[Color] = Json.format
val colorToJsonBytes = { color: Color =>
Json.toJson(color).toString.getBytes()
}
val jsonBytesToColor = { jsonBytes: Array[Byte] =>
Json.parse(jsonBytes).as[Color]
}
implicit val colorJsonSerializable = SafeSerializable.make[Color](
serFn = colorToJsonBytes, deserFn = jsonBytesToColor
)
import SafeSerializableSyntax._
println(red.serialize.deserialize[Color]) // Color(255,0,0,1.0)
def serializableFromJson[A](implicit aFormat: Format[A]): SafeSerializable[A] = {
SafeSerializable.make[A](
serFn = { Json.toJson(_).toString.getBytes() },
deserFn = { Json.parse(_).as[A] }
)
}
val colorJsonFormatEasy: SafeSerializable[Color] = serializableFromJson[Color]
case class Foo(x: Int)
object Foo {
implicit val format: Format[Foo] = Json.format
}
val foo = Foo(42)
assert(Serializer.deserialize[Foo](Serializer.serialize(foo)) == foo)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment