在 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,但我没用过。
嗯...假设您使用了一些库,例如 treehugger
或 scala 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]
.
您可以通过探索有关 ClassSymbol
和 ClassLoader
的事情来解决这个问题,如果运气好的话,您可以通过更多地了解反射在 Scala 中的工作原理来解决您将面临的更多问题和 Java。但这将是一项巨大的努力,并且不能保证一定会成功。
我需要一些提示来编写一个可以读取 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,但我没用过。
嗯...假设您使用了一些库,例如 treehugger
或 scala 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]
.
您可以通过探索有关 ClassSymbol
和 ClassLoader
的事情来解决这个问题,如果运气好的话,您可以通过更多地了解反射在 Scala 中的工作原理来解决您将面临的更多问题和 Java。但这将是一项巨大的努力,并且不能保证一定会成功。