All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.geteit.json.Json.scala Maven / Gradle / Ivy

The newest version!
package com.geteit.json

import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.reflect.macros.whitebox

class Json extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro Json.jsonMacroImpl.impl
}

class JsonValue extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro Json.jsonValueMacroImpl.impl
}

object Json {
  object jsonMacroImpl extends JsonMacro
  object jsonValueMacroImpl extends JsonMacro {
    override protected def jsonCodecs(c: whitebox.Context)(className: c.universe.TypeName, fields: List[c.universe.ValDef]): Seq[c.Tree] = {
      if (fields.length != 1) c.abort(c.enclosingPosition, s"Cannot create json value codecs for case class with ${fields.length} fields")
      import c.universe._
      Seq (
        q"implicit val jsonEncoder: com.geteit.json.JsonEncoder[$className] = com.geteit.json.JsonEncoder.valueEncoder[$className]",
        q"implicit val jsonDecoder: com.geteit.json.JsonDecoder[$className] = com.geteit.json.JsonDecoder.valueDecoder[$className]"
      )
    }
  }
}

trait JsonMacro {

  def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    def extractClassNameAndFields(classDecl: ClassDef): (TypeName, List[ValDef]) = {
      try {
        val q"case class $className(..$fields) extends ..$bases { ..$body }" = classDecl
        (className, fields)
      } catch {
        case _: MatchError => c.abort(c.enclosingPosition, "Annotation is only supported on case class")
      }
    }

    def modifiedCompanion(compDeclOpt: Option[ModuleDef], codecs: Seq[Tree], className: TypeName) = {
      compDeclOpt map { compDecl =>
        // Add the formatter to the existing companion object
        val q"object $obj extends ..$bases { ..$body }" = compDecl
        q"""
          object $obj extends ..$bases {
            ..$codecs
            ..$body
          }
        """
      } getOrElse {
        // Create a companion object with the formatter
        q"object ${className.toTermName} { ..$codecs }"
      }
    }

    def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None) = {
      val (className, fields) = extractClassNameAndFields(classDecl)
      val codecs = jsonCodecs(c)(className, fields)
      val compDecl = modifiedCompanion(compDeclOpt, codecs, className)

      // Return both the class and companion object declarations
      c.Expr(q"""
        $classDecl
        $compDecl
      """)
    }

    annottees.map(_.tree) match {
      case (classDecl: ClassDef) :: Nil => modifiedDeclaration(classDecl)
      case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifiedDeclaration(classDecl, Some(compDecl))
      case _ => c.abort(c.enclosingPosition, "Invalid annottee")
    }
  }

  protected def jsonCodecs(c: whitebox.Context)(className: c.universe.TypeName, fields: List[c.universe.ValDef]): Seq[c.Tree] = {
    import c.universe._
    Seq (
      q"implicit val jsonEncoder: com.geteit.json.JsonEncoder[$className] = com.geteit.json.JsonEncoder[$className]",
      q"implicit val jsonDecoder: com.geteit.json.JsonDecoder[$className] = com.geteit.json.JsonDecoder[$className]"
    )
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy