meta-programming to parse json in scala

611 views Asked by At

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?

2

There are 2 answers

6
AudioBubble On BEST ANSWER

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.

1
sarveshseri On

Well... lets say you used some library like treehugger or scala meta or something else to generate the code string for case class. Now there are multiple approaches that you can take. To start with one of them, you can do the following.

// import the current runtime mirror as cm
import scala.reflect.runtime.{currentMirror => cm}

// you case code string
val codeString = """
  case class Address(city: Option[String], zip:String)

  Address(Some("CityName"), "zipcode")
"""

// get the toolbox from mirror
val tb = cm.mkToolBox()

// use tool box to convert string to Tree
val codeTree = tb.parse(codeString)

// eval your tree
val address = tb.eval(codeTree)

The problem is that the val address will have type Any. Also the universe still does not know about type Address so you will not be able to do address.asInstanceOf[Address].

You can solve this one by exploring things about ClassSymbol and ClassLoader and with enough luck may be able to solve many more issues that you will face by understanding more about how reflection works in Scala and Java. But that will be a high effort and no guaranty of success path.