在 Scala 中解析 json 的元编程

meta-programming to parse json in scala

我需要一些提示来编写一个可以读取 json 文件并在 运行 时间创建案例 class 的 scala 程序。例如,如果我们有 json class 比如 -

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

这应该创建 class 就像

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

是否可以在 scala 中实现?

是的,您可以使用 TreeHugger 轻松实现。我为我的一个工作项目做了类似的事情。

下面是一个生成 Scala Akka Actor 的玩具示例 class。它需要清理,但希望您明白了:

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)    
}

基本上您需要做的就是弄清楚如何使用 TreeHugger 宏;每个宏代表 Scala 中的一个特定关键字。它为您提供了一种类型安全的方式来进行元编程。

还有Scala Meta,但我没用过。

嗯...假设您使用了一些库,例如 treehuggerscala meta 或其他库来为案例 class 生成代码字符串。现在您可以采用多种方法。要从其中之一开始,您可以执行以下操作。

// 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)

问题是 val address 的类型是 Any。此外,宇宙仍然不知道类型 Address 所以你将无法做到 address.asInstanceOf[Address].

您可以通过探索有关 ClassSymbolClassLoader 的事情来解决这个问题,如果运气好的话,您可以通过更多地了解反射在 Scala 中的工作原理来解决您将面临的更多问题和 Java。但这将是一项巨大的努力,并且不能保证一定会成功。