package tests import spray.json.JsValue import language.experimental.macros import reflect.macros.whitebox.Context abstract class JsonLens[T] { def get(obj: T): JsValue def set(obj: T, value: JsValue): T def fields: Map[String, JsonLens[T]] = Map() } object JsonLens { def of[T]: JsonLens[T] = macro lensMacro[T] def lensMacro[T : c.WeakTypeTag](c: Context): c.Expr[JsonLens[T]] = { import c.universe._ val lensType = weakTypeOf[T] val primaryCtor = lensType.decls.collectFirst { case m: MethodSymbol if m.isPrimaryConstructor => m } val fields = primaryCtor match { case Some(pc) => pc.paramLists.head case None => Nil } val fieldLenses = fields.map { field => val fieldName = field.name.decodedName val fieldType = field.typeSignature q""" Map( ${fieldName.toString} -> new JsonLens[$lensType] { def get(obj: $lensType) = implicitly[JsonLens[$fieldType]].get(obj.${fieldName.toTermName}) def set(obj: $lensType, value: JsValue) = obj.copy(${fieldName.toTermName} = implicitly[JsonLens[$fieldType]].set(obj.${fieldName.toTermName}, value)) }) ++ implicitly[JsonLens[$fieldType]].fields.map({ kv => (${fieldName.toString} + "." + kv._1 -> new JsonLens[$lensType] { def get(obj: $lensType): JsValue = kv._2.get(obj.${fieldName.toTermName}) def set(obj: $lensType, value: JsValue): $lensType = obj.copy(${fieldName.toTermName} = kv._2.set(obj.${fieldName.toTermName}, value)) }) }) """ } val fieldLensMap = fieldLenses.fold(q"Map[String, JsonLens[$lensType]]()")((fieldLens1, fieldLens2) => q"$fieldLens1 ++ $fieldLens2") c.Expr[JsonLens[T]] { q""" new JsonLens[$lensType] { def get(obj: $lensType): JsValue = implicitly[JsonFormat[$lensType]].write(obj) def set(obj: $lensType, value: JsValue): $lensType = implicitly[JsonFormat[$lensType]].read(value) override def fields = $fieldLensMap } """ } } }