【问题标题】:meta-programming to parse json in scala在 scala 中解析 json 的元编程
【发布时间】:2016-08-12 20:34:37
【问题描述】:

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

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

这应该创建像

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

可以在scala中做到吗?

【问题讨论】:

  • 所以...您想在运行时动态定义类。你可以在 scala 中做到这一点,但只有在对 scala-reflection 进行了非常非常好的控制之后。我的建议 - 除非绝对必要,否则避免使用这种方法。
  • 然后,即使您能够实现这一点,您仍需要对 scala-reflection 有很好的理解才能使用这些类及其实例。
  • 这个项目试图解决你的部分问题 - github.com/julianpeeters/case-class-generator 。只有在这个项目中的所有代码对您来说都像英文一样简单之后,您才能认为自己能够解决您的问题。

标签: scala metaprogramming


【解决方案1】:

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

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

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,但我没用过。

【讨论】:

  • 他的要求不能使用 Scala 宏来解决,因为这些宏在编译时工作。他需要的是在运行时定义新类以适应仅在运行时可用的 JSON 结构的能力。唯一能在运行时提供帮助的就是反射。
  • 是的,他可以做到。可以使用 TreeHugger 将 JSON 解析并转换为类文件(他需要确保编译器库是可访问的)。一旦他有了文本表示,他就可以动态编译它,并使用类加载器来加载生成的类。
  • 如果你认为 treehugger 可以帮助生成并在运行时实例化未知类,我认为你对 treehugger 有一些错误的想法。 Treehugger 可以帮助您在运行时创建scala-code strings,仅此而已。您唯一的希望是以某种方式从运行时获取解释器并以某种方式使用它来解释 code-string,但即使这样也不太可能奏效。
  • 我并不是说 TreeHugger 会编译这个类。这将帮助他生成定义。
  • @tesnik03,一旦你产生了定义,然后使用scala反射库中的工具箱模块来编译生成的类定义,然后使用正常的类加载技术将该类加载到JVM中。
【解决方案2】:

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

// 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 中的工作原理来解决您将面临的更多问题。但这将是一项艰巨的努力,并且不能保证成功。

【讨论】:

  • 我也喜欢。这是你的一点:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-20
  • 2018-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-30
  • 2017-01-24
相关资源
最近更新 更多