package dispatch.json
trait Extract[T] {
def unapply(js: JsValue): Option[T]
override def toString = getClass.getName
}
trait Insert[T] extends Extract[T] {
def << (t: T)(js: JsValue): JsObject
def <<| (t: T) = <<(t)(Js())
}
case class Property[T](sym: Symbol, ext: Extract[T]) extends Extract[T] with Insert[T] {
def unapply(js: JsValue) = js match {
case js: JsObject => js.self.get(JsString(sym)) flatMap ext.unapply
case _ => None
}
def << (t: T)(js: JsValue) = js match {
case JsObject(m) => JsObject(m + (JsString(sym) -> JsValue(t)))
case js => error("Unable to replace property in " + js)
}
override def toString = "%s ! %s" format (sym, ext)
}
case class Child[T, E <: Insert[T]](parent: Option[Obj], self: E) extends Extract[T] with Insert[T] {
def unapply(js: JsValue) = parent map { parent => js match {
case parent(self(t)) => Some(t)
} } getOrElse { js match {
case self(t) => Some(t)
case _ => None
}
}
def << (t: T)(js: JsValue) = parent map { parent => js match {
case parent(my_js) => (parent << (self << t)(my_js))(js)
} } getOrElse (self << t)(js)
}
class Obj(sym: Symbol)(implicit parent: Option[Obj])
extends Child[JsObject, Property[JsObject]](parent, Property(sym, Js.obj)) {
implicit val ctx = Some(this)
}
trait Js {
object str extends Extract[String] {
def unapply(js: JsValue) = js match {
case JsString(v) => Some(v)
case _ => None
}
}
object num extends Extract[BigDecimal] {
def unapply(js: JsValue) = js match {
case JsNumber(v) => Some(v)
case _ => None
}
}
object bool extends Extract[Boolean] {
def unapply(js: JsValue) = js match {
case JsBoolean(v) => Some(v)
case _ => None
}
}
object obj extends Extract[JsObject] {
def unapply(js: JsValue) = js match {
case JsObject(v) => Some(JsObject(v))
case _ => None
}
}
object list extends Extract[List[JsValue]] {
def unapply(js: JsValue) = js match {
case JsArray(v) => Some(v)
case _ => None
}
def ! [T](ext: Extract[T]) = new Extract[List[T]] {
def unapply(js: JsValue) = js match {
case list(l) => Some(l map { _ match { case ext(v) => v } } )
case _ => None
}
}
}
implicit val ctx: Option[Obj] = None
type JsF[T] = JsValue => T
type JsIF = JsF[JsObject]
implicit def sym_add_operators[T](sym: Symbol) = new SymOp(sym)
case class SymOp(sym: Symbol) {
def ? [T](cst: Extract[T])(implicit parent: Option[Obj]) =
new Child[T, Property[T]](parent, Property(sym, cst))
def ! [T](cst: Extract[T]): JsF[T] = Js.ext2fun(new Property(sym, cst))
def << [T](t: T): JsIF = _ match {
case JsObject(m) => JsObject(m + (JsString(sym) -> JsValue(t)))
case js => error("Unable to replace property in " + js)
}
def << (f: JsIF): JsIF = js => (this << f((this ! obj)(js)))(js)
def <<| [T] (t: T) = <<(t)(Js())
}
def %[A,B](a: JsF[A], b: JsF[B])(js: JsValue) = (a(js), b(js))
def %[A,B,C](a: JsF[A], b: JsF[B], c: JsF[C])(js: JsValue) = (a(js), b(js), c(js))
def %[A,B,C,D](a: JsF[A], b: JsF[B], c: JsF[C], d: JsF[D])(js: JsValue) = (a(js), b(js), c(js), d(js))
implicit def ext2fun[T](ext: Extract[T]): JsF[T] = jsv => ext.unapply(jsv).getOrElse {
error("Extractor %s does not match JSON: %s" format (ext, jsv))
}
}
object Js extends Js {
def apply(): JsValue = JsObject()
def apply(stream: java.io.InputStream): JsValue = JsValue.fromStream(stream)
def apply(string: String): JsValue = JsValue.fromString(string)
}