Simple serialisation, de-serialsation using Shapeless
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
package derivation | |
import java.util.Locale | |
import shapeless._ | |
import scala.reflect.ClassTag | |
import scala.util.Try | |
sealed trait Utensil | |
case class Plate(capacity: Int, bowl: Boolean) extends Utensil | |
case class Fork(material: String) extends Utensil | |
case class Spoon(material: String) extends Utensil | |
sealed trait Cars | |
case class Volvo(model: String, isDiesel: Boolean, gearType: Int) extends Cars | |
case class Benz(model: String, isDiesel: Boolean, gearType: Int) extends Cars | |
case class BMW(model: String, isDiesel: Boolean, gearType: Int) extends Cars | |
trait Serializer[T] { | |
def serialize(t: T): String | |
} | |
object Consts { | |
val TypeIdentifier = "Type: " | |
} | |
import Consts._ | |
object Serializer { | |
implicit val intSerializer: Serializer[Int] = new Serializer[Int] { | |
def serialize(t: Int): String = t.toString | |
} | |
implicit val stringSerializer: Serializer[String] = new Serializer[String] { | |
def serialize(t: String): String = s""""$t"""" | |
} | |
implicit val booleanSerializer: Serializer[Boolean] = new Serializer[Boolean] { | |
def serialize(t: Boolean): String = if (t) "yes" else "no" | |
} | |
implicit val HNilSerializer: Serializer[HNil] = new Serializer[HNil] { | |
def serialize(t: HNil): String = "" | |
} | |
implicit def HConsSerializer[H, T <: HList](implicit serH: Lazy[Serializer[H]], | |
serT: Lazy[Serializer[T]]): Serializer[H :: T] = { | |
new Serializer[H :: T] { | |
def serialize(value: H :: T): String = value match { | |
case (h :: HNil) => serH.value.serialize(h) | |
case (h :: t) => s"${serH.value.serialize(h)} , ${serT.value.serialize(t)}" | |
} | |
} | |
} | |
implicit def GenericSerializer[T, R](implicit gen: Generic.Aux[T, R], | |
serializer: Lazy[Serializer[R]]): Serializer[T] = { | |
new Serializer[T] { | |
def serialize(t: T): String = { | |
val str = serializer.value.serialize(gen.to(t)) | |
if (!str.startsWith(TypeIdentifier)) s"$TypeIdentifier${t.getClass.getName} , $str" else str | |
} | |
} | |
} | |
//co-product | |
implicit def CNilSerializer: Serializer[CNil] = new Serializer[CNil] { | |
def serialize(t: CNil): String = ??? | |
} | |
implicit def CConsSerializer[H, T <: Coproduct](implicit serH: Lazy[Serializer[H]], | |
serT: Lazy[Serializer[T]]): Serializer[H :+: T] = { | |
new Serializer[H :+: T] { | |
def serialize(c: H :+: T): String = c match { | |
case Inl(h) => serH.value.serialize(h) | |
case Inr(t) => serT.value.serialize(t) | |
} | |
} | |
} | |
} | |
trait Deserializer[T] { | |
def deserialize(str: String): Option[T] | |
} | |
object Deserializer { | |
implicit val intDeserializer: Deserializer[Int] = new Deserializer[Int] { | |
def deserialize(str: String): Option[Int] = Try(str.toInt).toOption | |
} | |
implicit val stringDeserializer: Deserializer[String] = new Deserializer[String] { | |
def deserialize(str: String): Option[String] = Try { | |
require(str.head == '"' && str.last == '"') | |
str.substring(1, str.length - 1) | |
}.toOption | |
} | |
implicit val booleanDeserializer: Deserializer[Boolean] = new Deserializer[Boolean] { | |
def deserialize(str: String): Option[Boolean] = str.toLowerCase(Locale.US) match { | |
case "yes" => Some(true) | |
case "no" => Some(false) | |
case x => None | |
} | |
} | |
implicit val typeDeserializer: Deserializer[Class[_]] = new Deserializer[Class[_]] { | |
def deserialize(str: String): Option[Class[_]] = Try(Class.forName(str.replace(TypeIdentifier, "").trim)).toOption | |
} | |
implicit val HNilDeserializer: Deserializer[HNil] = new Deserializer[HNil] { | |
def deserialize(str: String): Option[HNil] = Some(HNil) | |
} | |
implicit def HConstDeserializer[H, T <: HList](implicit deserH: Deserializer[H], | |
deserT: Deserializer[T]): Deserializer[H :: T] = new Deserializer[H :: T] { | |
override def deserialize(str: String): Option[H :: T] = { | |
val (head, tail) = { | |
val rest = if (str.startsWith(TypeIdentifier)) str.dropWhile(_ != ',').drop(1).trim else str | |
val firstComma = rest.indexOf(",") | |
if (firstComma < 0) (rest, "") else rest.splitAt(firstComma) | |
} | |
deserH.deserialize(head.trim) match { | |
case Some(headEntity) => deserT.deserialize(tail.drop(2).trim) match { | |
case Some(tailEntity) => Some(headEntity :: tailEntity) | |
case None => None | |
} | |
case None => None | |
} | |
} | |
} | |
implicit def GenericDeserializer[T, R](implicit gen: Generic.Aux[T, R], | |
deserializer: Lazy[Deserializer[R]]): Deserializer[T] = { | |
new Deserializer[T] { | |
def deserialize(str: String): Option[T] = { | |
deserializer.value.deserialize(str).map(gen.from) | |
} | |
} | |
} | |
//co-product | |
implicit def CNilDeserializer: Deserializer[CNil] = new Deserializer[CNil] { | |
def deserialize(str: String): Option[CNil] = None | |
} | |
implicit def CConsSerializer[H, T <: Coproduct](implicit serH: Lazy[Deserializer[H]], | |
tag: ClassTag[H], | |
serT: Lazy[Deserializer[T]]): Deserializer[H :+: T] = { | |
new Deserializer[H :+: T] { | |
def deserialize(str: String): Option[H :+: T] = { | |
val derivedType = str.substring(0, str.indexOf(',')).replace(TypeIdentifier, "").trim | |
if (derivedType == tag.runtimeClass.getName) { | |
serH.value.deserialize(str).map(Inl[H, T]) | |
} else { | |
serT.value.deserialize(str).map(Inr[H, T]) | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment