Last active
November 21, 2018 18:38
-
-
Save kolemannix/c85f88e61d834f1f11d00d74c51fad84 to your computer and use it in GitHub Desktop.
Code from post "A practical type class example: SafeSerializable"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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