scalametaprogrammingscalametatreehugger

meta-programming to parse json in scala


I need some hints to write a scala program that could read json file and create a case class at run time. As an example if we have json class like -

Employ{
    name:{datatype:String,  null:false}
    age:{datatype:Int,  null:true}
    Address:{city: {datatype: String, null:true}, zip: {datatype: String, null:false}}
}

and this should create class like

case class Employ(name: String, age: Option[Int], address: Address}
case class Address(city: Option[String], zip:String}

would it be possible to do it in scala?


Solution

  • Yes, you can easily achieve this using TreeHugger. I did a similar thing for one of my work projects.

    Below is a toy example which produces a Scala Akka Actor class. It needs to be cleaned up but, hopefully, you get the idea:

    import argonaut.Argonaut._
    import argonaut._
    import org.scalatest.FunSuite
    import treehugger.forest._
    import definitions._
    import treehuggerDSL._
    
    class ConvertJSONToScalaSpec extends FunSuite {
    
      test("read json") {
    
        val input =
          """
            |{
            |  "rulename" : "Rule 1",
            |  "condition" : [
            |    {
            |      "attribute" : "country",
            |      "operator" : "eq",
            |      "value" : "DE"
            |    }
            |    ],
            |  "consequence" : "route 1"
            |}
          """.stripMargin
    
        val updatedJson: Option[Json] = input.parseOption
    
        val tree =
          BLOCK(
            IMPORT(sym.actorImports),
            CLASSDEF(sym.c).withParents(sym.d, sym.e) :=
              BLOCK(
                IMPORT(sym.consignorImport, "_"),
                DEFINFER(sym.methodName) withFlags (Flags.OVERRIDE) := BLOCK(
                  CASE(sym.f DOT sym.methodCall APPLY (REF(sym.mc))) ==>
                    BLOCK(
                      sym.log DOT sym.logmethod APPLY (LIT(sym.logmessage)),
                      (IF (sym.declaration DOT sym.header DOT sym.consignor DOT sym.consignoreTID ANY_== LIT(1))
                        THEN (sym.sender APPLY() INFIX ("!", LIT(sym.okcm)))
                        ELSE
                        (sym.sender APPLY() INFIX ("!", LIT(sym.badcm)))
                        )
                    )
                )
              )
          ) inPackage (sym.packageName)    
    }
    

    Essentially all you need to do is work out how to use the TreeHugger macros; each macro represents a specific keyword in Scala. It gives you a type-safe way to do your meta-programming.

    There's also Scala Meta but I haven't used that.