Skip to content

Instantly share code, notes, and snippets.

@negator
Last active April 26, 2017 04:13
Show Gist options
  • Save negator/b41c875f1166f46eb69e to your computer and use it in GitHub Desktop.
Save negator/b41c875f1166f46eb69e to your computer and use it in GitHub Desktop.
Easy XML typeclasses with Shapeless and Scalaz
/** Domain mapped XML doesnt have to be a boilerplate filled annotational mess. Using shapeless automatic
* typeclass derivation and a little scalaz magic, it becomes painless. Requires shapeless-2.1.0 and scala >2.10
*
*
* import scalaz._
* import scalaz.std._
* import anyVal._
* import list._
* import scala.xml._
*
* case class Foo(a: Int)
* case class Bar(b: Int, foo: Foo)
* case class Goo(g: Int, foo: List[Foo])
* case class Doo(d: Int, bar: List[Bar])
*
* val toFoo: ToXml[Foo] = ToXml.deriveInstance
* val toBar: ToXml[Bar] = ToXml.deriveInstance
* val toGoo: ToXml[Goo] = ToXml.deriveInstance
* val toDoo: ToXml[Doo] = ToXml.deriveInstance
* val fromDoo: FromXml[Doo] = FromXml.deriveInstance
* val from: FromXml[Goo] = FromXml.deriveInstance
*
* val bar = Bar(2, Foo(2))
* val doo = Doo(1, List(Bar(2, Foo(1)), Bar(5, Foo(2))))
*
*
* val xml = toDoo.toXml(doo, rootLabel="root") // prints : <root><bar><foo><a>1</a></foo><b>2</b></bar><bar><foo><a>2</a></foo><b>5</b></bar><d>1</d></root>
* xml doo = fromDoo.fromXml(xml) // prints : \/-(Doo(1,List(Bar(2,Foo(1)), Bar(5,Foo(2)))))
*
*
*
*
*
*
**/
import scalaz.{ Success => _, Failure => _, _ }
import Scalaz._
import shapeless.{ `::` => :#:, _ }
import scala.xml._
import scala.util._
trait Read[T] {
def reads(s: String): Try[T]
}
trait ReadInstances {
implicit val StringReads = new Read[String] {
def reads(s: String) = Success(s)
}
implicit val IntReads = new Read[Int] {
def reads(s: String) = Try(s.toInt)
}
implicit val LongReads = new Read[Long] {
def reads(s: String) = Try(s.toLong)
}
}
object Read extends ReadInstances {
def apply[T](f: String => Try[T]): Read[T] = new Read[T] {
override def reads(s: String) = f(s)
}
def reads[T](s: String)(implicit r: Read[T]): Try[T] = r reads s
}
@implicitNotFound(msg = "Implicit ToXml[${T}] not found. Try supplying an implicit instance of ToXml[${T}]")
trait ToXml[T] {
def toXml(t: T): NodeSeq
def toXml(t: T, rootLabel: String): NodeSeq = XML loadString s"<$rootLabel>${toXml(t)}</$rootLabel>"
}
@implicitNotFound(msg = "Implicit FroXml[${T}] not found. Try supplying an implicit instance of FromXml[${T}]")
trait FromXml[T] {
def fromXml(elem: NodeSeq): String \/ T
}
object ToXml extends LabelledProductTypeClassCompanion[ToXml] {
val Empty = NodeSeq.Empty
def apply[T](f: T => NodeSeq): ToXml[T] = new ToXml[T] {
override def toXml(t: T): NodeSeq = f(t)
}
def toXml[T](t: T)(implicit tx: ToXml[T]) = tx.toXml(t)
/*Anything that can be shown can be xml*/
implicit def ShowsToXml[T: Show]: ToXml[T] = ToXml[T]{ t: T =>
val s = Show[T].shows(t)
scala.xml.Text(s.stripPrefix("\"").stripSuffix("\""))
}
implicit def OptionToXml[T](implicit tx: ToXml[T]) = new ToXml[Option[T]] {
override def toXml(o: Option[T]) = o.fold(Empty)(tx.toXml)
override def toXml(o: Option[T], rootLabel: String) = o.fold(Empty)(tx.toXml(_, rootLabel))
}
/*Anything that is foldable can be serialized to xml*/
implicit def FoldableToXml[T, L[_]](implicit tx: ToXml[T], foldable: Foldable[L]): ToXml[L[T]] = new ToXml[L[T]] {
override def toXml(list: L[T]): NodeSeq = list.foldLeft(Empty)(_ ++ tx.toXml(_))
override def toXml(list: L[T], rootLabel: String): NodeSeq = list.foldLeft(Empty)(_ ++ tx.toXml(_, rootLabel))
}
object typeClass extends LabelledProductTypeClass[ToXml] {
def emptyProduct: ToXml[HNil] = new ToXml[HNil] {
def toXml(t: HNil) = Empty
}
def product[F, T <: HList](name: String, FHead: ToXml[F], FTail: ToXml[T]) = new ToXml[F :#: T] {
override def toXml(hlist: F :#: T): NodeSeq = {
hlist match {
case head :#: tail =>
val h = FHead toXml (head, name)
val t = FTail toXml tail
t ++ h
}
}
}
def project[F, G](instance: => ToXml[G], to: F => G, from: G => F) = new ToXml[F] {
override def toXml(f: F): NodeSeq = instance.toXml(to(f))
}
}
}
object FromXml extends LabelledProductTypeClassCompanion[FromXml] {
/*Anything with a Read typeclass instance can be deserialized from xml*/
implicit def ReadsFromXml[T](implicit read: Read[T]): FromXml[T] = FromXml[T]{ n: NodeSeq =>
read.reads(n.text) match {
case Success(t) => t.right
case Failure(e) => s" [Error reading value: ${e.getMessage}]".left
}
}
implicit def OptionFromXml[T](implicit fx: FromXml[T]): FromXml[Option[T]] = FromXml[Option[T]] { node: NodeSeq =>
node match {
case n if n.isEmpty => none.right
case n => fx.fromXml(n).map(_.some)
}
}
/*You know for like lists and sets and stuff*/
implicit def MonoidFromXml[T, L[_]](implicit fx: FromXml[T], monoid: Monoid[L[T]], app: Applicative[L]): FromXml[L[T]] = FromXml[L[T]]{ node: NodeSeq =>
val zero = monoid.zero.right[String]
node.foldLeft(zero){ (listF, n) =>
for {
list <- listF
f <- fx fromXml n
v = app point f
} yield monoid.append(list, v)
}
}
def apply[T](f: NodeSeq => String \/ T): FromXml[T] = new FromXml[T] {
def fromXml(t: NodeSeq) = f(t)
}
object typeClass extends LabelledProductTypeClass[FromXml] {
def emptyProduct: FromXml[HNil] = new FromXml[HNil] {
def fromXml(t: NodeSeq) = HNil.right
}
def product[F, T <: HList](name: String, FHead: FromXml[F], FTail: FromXml[T]) = new FromXml[F :#: T] {
override def fromXml(v: NodeSeq) = {
val node = (v \ name)
val result = for {
h <- FHead fromXml node
t <- FTail fromXml (v diff node)
} yield h :: t
result.leftMap(msg => s"/$name" + msg)
}
}
def project[F, G](instance: => FromXml[G], to: F => G, from: G => F) = new FromXml[F] {
override def fromXml(f: NodeSeq) = instance.fromXml(f).map(from)
}
}
}
@feliperazeek
Copy link

@negator nice! I was doing some research on shapeless, ended up running into this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment