我正在尝试在 JSON 中为简单的 Scala3 类型(例如 case 类)实现“通用”反序列化 - 我需要传入序列化的 JSON 和目标类的 name (字符串)并接收该实例目标类(之后我将其视为
java.lang.Object
)。
我选择使用Lift框架的JSON库。由于 Lift 仅是 Scala2,我通过 CrossVersion.for3Use2_13 兼容层使用它(这是来自我的 build.sbt):
libraryDependencies += ("net.liftweb" %% "lift-json" % "3.5.0").cross(CrossVersion.for3Use2_13),
我选择 Lift 的 JSON 库是因为可以显式传递目标类进行反序列化(请参阅Extraction Lift 文档):
def extract(json: JValue, target: TypeInfo)(implicit formats: Formats): Any
为了利用它,我首先将类名“取消引用”到 Java 中的 java.lang.Class 实例:
String targetClassName = (inp.entrypointClass == null ? "Main" : inp.entrypointClass);
String mainClassName = this.getClass().getPackage().getName() + "." + targetClassName;
Class targetClass;
try {
targetClass = Class.forName(mainClassName);
} catch(ClassNotFoundException err1) {
throw new InvalidEntrypointClassError(err1);
}
这部分似乎运行正确(事实证明我在调试尝试中没有得到
InvalidEntrypointClassError
)。
然后我尝试将字符串反序列化为类,如下所示:
import net.liftweb.json._
import net.liftweb.json.Extraction._
import net.liftweb.json.Serialization._
import reflect._
val formats = net.liftweb.json.DefaultFormats
object ScalaWrapper {
def deserialize(jsonData: java.lang.String, clazz: java.lang.Class[?]): java.lang.Object = {
val typeInfo = TypeInfo(clazz, None)
val data = parse(jsonData)
val res: Any = extract(data, typeInfo)(formats)
res.asInstanceOf[java.lang.Object]
}
}
当我尝试使用简单的示例时,这会失败,例如尝试反序列化到此处定义的
Input
案例类:
case class Node(name: String, left: Option[Int], right: Option[Int])
case class Walk(nodes: List[Node])
case class Input(nodes: List[Node])
请注意,对我来说,拥有“简单”的案例类定义非常重要,即将反序列化问题的复杂性传递给案例类,由于其他要求,这并不是一个解决方案
使用以下 JSON 输入:
{
"nodes": [
{
"name": "A",
"left": 1,
"right": 2
},
{
"name": "B"
},
{
"name": "C",
"left": 4,
"right": 3
},
{
"name": "D"
},
{
"name": "E"
}
]
}
错误在 Lift JSON 内部相当深:
java.lang.NullPointerException
at scala.tools.scalap.scalax.rules.scalasig.ByteCode$.forClass(ClassFileParser.scala:24)
at scala.tools.scalap.scalax.rules.scalasig.ScalaSigParser$.parse(ScalaSig.scala:68)
at net.liftweb.json.ScalaSigReader$.findScalaSig(ScalaSig.scala:126)
at net.liftweb.json.ScalaSigReader$.$anonfun$findScalaSig$1(ScalaSig.scala:126)
at scala.Option.orElse(Option.scala:477)
at net.liftweb.json.ScalaSigReader$.findScalaSig(ScalaSig.scala:126)
at net.liftweb.json.ScalaSigReader$.findClass(ScalaSig.scala:60)
at net.liftweb.json.ScalaSigReader$.readConstructor(ScalaSig.scala:44)
... <SKIPPED ABOUT 20 OTHER STACK FRAMES>
at net.liftweb.json.Meta$.mappingOf(Meta.scala:195)
at net.liftweb.json.Extraction$.extract(Extraction.scala:210)
at binary_tree_dfs.ScalaWrapper$.deserialize(ScalaWrapper.scala:13)
at binary_tree_dfs.ScalaWrapper.deserialize(ScalaWrapper.scala)
我在调试这个问题时失败了,而且我不知道 Scala 到底发生了什么,但我的猜测是我正在为尝试在 Scala3 项目中使用 Scala2 泛型功能付出代价。 这是正确的吗?这段代码可以在不进行重大重构的情况下修复吗?
由于我不知道这里发生了什么,所以我考虑了其他几种方法:
izumi-reflect
的 TypeTag
转换为 Scala2 Manifest
兼容的对象,但我不知道如何做到这一点,而且我担心这很糟糕且不可靠。 这种方法可行吗?更有意义吗?TypeTag
。 是否有一个 Scala3 库可以让我做到这一点,而无需修改案例类定义?
也许我可以通过另一种我不明白的方式获得java.lang.Class
和 Manifest
的元组吗?如果 Manifest 没有通用参数,这还有意义吗?
AFAIU,Scala3 宏是 Scala2 反射的预期替代品。 总结一下问题:
我正在尝试将 JSON 字符串反序列化为我只知道名称的 Scala3 类。 我希望这门课是一个案例课。我目前正在尝试使用 Scala2 的 Lift JSON 库,但失败了。 我希望 JSON 将被反序列化为具有简单定义的案例类,因此我不需要serialize
/
deserialize
方法或类似方法。如何在 Scala 3 中实现这一目标?
)能够执行您想要的操作(即解析为运行时已知的类)。
package com.myapp
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.scala.{ClassTagExtensions, DefaultScalaModule}
case class Node(name: String, left: Option[Int], right: Option[Int])
case class Walk(nodes: List[Node])
case class Input(nodes: List[Node])
val objectMapper = JsonMapper.builder().addModule(DefaultScalaModule).build() :: ClassTagExtensions
val input: String = ???
val parsedInput = objectMapper.readValue(input, Class.forName("com.myapp.Input"))
println(parsedInput)
// Input(List(Node(A,Some(1),Some(2)), Node(B,None,None), Node(C,Some(4),Some(3)), Node(D,None,None), Node(E,None,None)))