Created
March 7, 2014 16:25
-
-
Save Arneball/9414661 to your computer and use it in GitHub Desktop.
Json Quasiquotes example
This file contains hidden or 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
sealed trait JsValue { | |
override final def toString = mkString | |
def mkString: String | |
} | |
case class JsArray(value: JsValue*) extends JsValue { | |
def mkString = value.map{ _.mkString }.mkString("[", ", ", "]") | |
} | |
case class JsObj(value: (String, JsValue)*) extends JsValue { | |
def mkString = value.map{ | |
case (k, v) => s""""$k": ${v.mkString}""" | |
}.mkString("{", ", ", "}") | |
} | |
case class JsString(value: String) extends JsValue{ | |
def mkString = s""""$value"""" | |
} | |
case class JsNumber(value: BigDecimal) extends JsValue { | |
def mkString = value.toString | |
} | |
trait Json[T]{ | |
def toJson(t: T): JsValue | |
} | |
object Json { | |
def js[T](fun: T => JsValue) = new Json[T]{ | |
def toJson(t: T) = fun(t) | |
} | |
implicit def any[T]: Json[T] = macro JsonHelper.apply_impl[T] | |
implicit val int = js[Int](JsNumber.apply(_)) | |
implicit val str = js[String](JsString.apply(_)) | |
implicit def seq[T : Json, CC[X]<:Iterable[X]]: Json[CC[T]] = new Json[CC[T]]{ | |
def toJson(values: CC[T]) = { | |
val result = values.map{ value => | |
implicitly[Json[T]].toJson(value) | |
} | |
JsArray(result.toSeq: _*) | |
} | |
} | |
} | |
object JsonHelper { | |
def apply_impl[T : c.WeakTypeTag](c: Context): c.Expr[Json[T]] = { | |
import c.universe._ | |
val tTyp = weakTypeOf[T] | |
val fields = tTyp.declarations.collect { | |
case sym: MethodSymbol if sym.isCaseAccessor => (q"a.${sym.name}", sym.typeSignature, sym.name.encoded) | |
} | |
if (fields.isEmpty) { | |
c.abort(c.enclosingPosition, "Not a case class!") | |
} else { | |
val scalaSym = typeOf[Any].typeSymbol.owner | |
tTyp.baseClasses.collectFirst { | |
case sym if sym.name.decoded.startsWith("Tuple") && sym.owner == scalaSym => | |
c.abort(c.enclosingPosition, "Not needed for tuples!") | |
}.getOrElse{ | |
val selects = fields.map{ case (select, typeOfField, nameOfField) => | |
q""" ($nameOfField -> implicitly[Json[$typeOfField]].toJson($select) ) """ | |
} | |
c.Expr[Json[T]]{ | |
q"""new Json[$tTyp]{ | |
def toJson(a: $tTyp) = JsObj(..$selects) | |
}""" | |
} | |
} | |
} | |
} | |
} | |
==== | |
import JsonHelper._ | |
case class Horse(horsename: String, age: Int) | |
case class Rider(name: String, horse: Horse) | |
case class Stable(horses: IndexedSeq[Horse]) { | |
def this(h: Horse*) = this(h.toIndexedSeq) | |
} | |
object HorseTester extends App { | |
private def printJson[T: Json](t: T) = println{ | |
implicitly[Json[T]].toJson(t) | |
} | |
printJson(new Horse("SpearHorse", 3)) | |
printJson(Rider("Arne", Horse("Brunte", 22))) | |
printJson(new Stable(Horse("brunte", 3), Horse("curry", 5))) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment