score:15
First, you need to write generic serializers for HList
. That is, you need to specify how to serialize H :: T
and HNil
:
implicit def hconsDatatype[H, T <: HList](implicit hd: Datatype[H],
td: Datatype[T]): Datatype[H :: T] =
new Datatype[H :: T] {
override def serialize(value: H :: T): NodeSeq = value match {
case h :: t =>
val sh = hd.serialize(h)
val st = td.serialize(t)
(sh ++ st).theSeq
}
override def deserialize(from: NodeSeq): Deserialized[H :: T] =
(hd.deserialize(from.head) |@| td.deserialize(from.tail)) {
(h, t) => h :: t
}
}
implicit val hnilDatatype: Datatype[HNil] =
new Datatype[HNil] {
override def serialize(value: HNil): NodeSeq = NodeSeq()
override def deserialize(from: NodeSeq): Deserialized[HNil] =
Success(HNil)
}
Then you can define a generic serializer for any type which can be deconstructed via Generic
:
implicit def genericDatatype[T, R](implicit gen: Generic.Aux[T, R],
rd: Lazy[Datatype[R]]): Datatype[T] =
new Datatype[T] {
override def serialize(value: T): NodeSeq =
rd.value.serialize(gen.to(value))
override def deserialize(from: NodeSeq): Deserialized[T] =
rd.value.deserialize(from).map(rd.from)
}
Note that I had to use Lazy
because otherwise this code would break the implicit resolution process if you have nested case classes. If you get "diverging implicit expansion" errors, you could try adding Lazy
to implicit parameters in hconsDatatype
and hnilDatatype
as well.
This works because Generic.Aux[T, R]
links the arbitrary product-like type T
and a HList
type R
. For example, for this case class
case class A(x: Int, y: String)
shapeless will generate a Generic
instance of type
Generic.Aux[A, Int :: String :: HNil]
Consequently, you can delegate the serialization to recursively defined Datatype
s for HList
, converting the data to HList
with Generic
first. Deserialization works similarly but in reverse - first the serialized form is read to HList
and then this HList
is converted to the actual data with Generic
.
It is possible that I made several mistakes in the usage of NodeSeq
API above but I guess it conveys the general idea.
If you want to use LabelledGeneric
, the code would become slightly more complex, and even more so if you want to handle sealed trait hierarchies which are represented with Coproduct
s.
I'm using shapeless to provide generic serialization mechanism in my library, picopickle. I'm not aware of any other library which does this with shapeless. You can try and find some examples how shapeless could be used in this library, but the code there is somewhat complex. There is also an example among shapeless examples, namely S-expressions.
score:12
Vladimir's answer is great and should be the accepted one, but it's also possible to do this a little more nicely with Shapeless's TypeClass
machinery. Given the following setup:
import scala.xml.NodeSeq
import scalaz._, Scalaz._
trait Serializer[T] {
def serialize(value: T): NodeSeq
}
trait Deserializer[T] {
type Deserialized[T] = Validation[AnyErrors, T]
type AnyError = Throwable
type AnyErrors = NonEmptyList[AnyError]
def deserialize(from: NodeSeq): Deserialized[T]
}
trait Datatype[T] extends Serializer[T] with Deserializer[T]
We can write this:
import shapeless._
object Datatype extends ProductTypeClassCompanion[Datatype] {
object typeClass extends ProductTypeClass[Datatype] {
def emptyProduct: Datatype[HNil] = new Datatype[HNil] {
def serialize(value: HNil): NodeSeq = Nil
def deserialize(from: NodeSeq): Deserialized[HNil] = HNil.successNel
}
def product[H, T <: HList](
dh: Datatype[H],
dt: Datatype[T]
): Datatype[H :: T] = new Datatype[H :: T] {
def serialize(value: H :: T): NodeSeq =
dh.serialize(value.head) ++ dt.serialize(value.tail)
def deserialize(from: NodeSeq): Deserialized[H :: T] =
(dh.deserialize(from.head) |@| dt.deserialize(from.tail))(_ :: _)
}
def project[F, G](
instance: => Datatype[G],
to: F => G,
from: G => F
): Datatype[F] = new Datatype[F] {
def serialize(value: F): NodeSeq = instance.serialize(to(value))
def deserialize(nodes: NodeSeq): Deserialized[F] =
instance.deserialize(nodes).map(from)
}
}
}
Be sure to define these all together so they'll be properly companioned.
Then if we have a case class:
case class Foo(bar: String, baz: String)
And instances for the types of the case class members (in this case just String
):
implicit object DatatypeString extends Datatype[String] {
def serialize(value: String) = <s>{value}</s>
def deserialize(from: NodeSeq) = from match {
case <s>{value}</s> => value.text.successNel
case _ => new RuntimeException("Bad string XML").failureNel
}
}
We automatically get a derived instance for Foo
:
scala> case class Foo(bar: String, baz: String)
defined class Foo
scala> val fooDatatype = implicitly[Datatype[Foo]]
fooDatatype: Datatype[Foo] = Datatype$typeClass$$anon$3@2e84026b
scala> val xml = fooDatatype.serialize(Foo("AAA", "zzz"))
xml: scala.xml.NodeSeq = NodeSeq(<s>AAA</s>, <s>zzz</s>)
scala> fooDatatype.deserialize(xml)
res1: fooDatatype.Deserialized[Foo] = Success(Foo(AAA,zzz))
This works about the same as Vladimir's solution, but it lets Shapeless abstract some of the boring boilerplate of type class instance derivation so you don't have to get your hands dirty with Generic
.
Source: stackoverflow.com
Related Query
- How to shapeless case classes with attributes and typeclasses?
- How to get Scala case class fields and values as (String, String) with Shapeless or Macro
- Domain classes and slick generated case classes conversion with Shapeless
- How to convert between to case classes with `mostly the same` fields using Scala Shapeless
- Refactoring Case Classes With Common Attributes and Functions
- Weird behavior trying to convert case classes to heterogeneous lists recursively with Shapeless
- how to serialize case classes with traits with jsonspray
- How to modify this nested case classes with "Seq" fields?
- Serializing and unserializing case classes with lift-json
- How do I pull apart Case Classes filled with Options in Scala
- How can I write and read an empty case class with play-json?
- How to define case classes with members with unbound type parameters?
- How can I get random data generated for scala case classes with the ability to "change some values" for unit testing?
- How to bridge Scala case classes and collections to Java
- How to use nested case classes and spray json implicits
- How does serialization work for case classes and compared to java?
- Scala shapeless typing Map[Symbol, String] with case classes
- How can I parse a json and extract to different case classes depending of its content
- How to use Java 8 Date classes and Jackson with Spark?
- Polymorphism with Spark / Scala, Datasets and case classes
- How to sort case classes with Option[String] in alphabetical order ignoring None's?
- Case classes with inheritance and default parameters
- Spark Dataframe schema definition using reflection with case classes and column name aliases
- JSON response with Http AKKA and case classes
- How to convert parquet data to case classes with spark?
- How to make the scala compiler find case classes used with wrong arguments
- Get case class field's name and type with shapeless
- How can I match case with shapeless variable?
- How to read json file and convert to case class with Spark and Spray Json
- How can I use case classes with slicks TSQL interpolator?
More Query from same tag
- Scala Spark sort RDD by index of substring
- Spark: How to transform a Seq of RDD into a RDD
- ScalaPB: compilePlugin generate inappropriate class
- Convert from Int to char -> string -> double -> int
- Why doesn't this code compile? Is this really unsafe?
- Index of users distributed across rooms
- wrapping an asynchronous process in an akka actor
- Why does from_json fail with “not found : value from_json"? (2)
- Bash - how to check if file has been uploaded to hdfs?
- How is "become" implemented in languages that support the actor model?
- scala: Async processing using Future.sequence
- Implement abstract behaviour just once... trait as contract, abstract class as concrete-helper
- How to set up sh script to be ran with Terminal (mac os) by default?
- Matching regex pattern in Array of Strings
- type mismatch error when creating Reads for Play 2.1
- Is there a way to generate model codes from DB schema in Lift?
- case class with logic what is the idiomatic way
- Create recursive JSON Writes without combinator pattern
- Json object (de)serialization. Typed languages with class hierarchy
- Why does Spark Cassandra Connector fail with NoHostAvailableException?
- Context bounds with two generic parameters
- Scala this.type conformance to type parameter bounds of supertype
- Stub web calls in Scala
- SSL in a REST Lift project, where to start?
- Writing to HDFS in Spark/Scala reading the zip files
- Scala - aliasing a type inside a type
- Scala Flink get java.lang.NoClassDefFoundError: scala/Product$class after using case class for customized DeserializationSchema
- How to override equals for alias types in scala
- Sbt Multi-Module - using some project as module in another project
- How to transform a categorical variable in Spark into a set of columns coded as {0,1}?