【问题标题】:how to deserialize a json string that contains @@ with scala'如何反序列化包含@@ with scala'的json字符串
【发布时间】:2020-09-27 06:16:39
【问题描述】:

正如标题已经解释的那样,我想反序列化一个包含以 @@ 开头的键的 json 字符串。遗憾的是,使用@@,我使用案例类的标准方法不再适用了。

val test = """{"@@key": "value"}"""
case class Test(@@key: String) // not possible
val gson =  new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])

如何在不预处理输入 json 字符串的情况下使用 @@?

【问题讨论】:

  • 至少有五个很棒的 Scala json 库。为什么要使用一个 fork Java?

标签: json scala gson


【解决方案1】:

最简单的答案是引用字段名称:

case class Test(`@@key`: String)

【讨论】:

  • 感谢您的快速回复。这会将发布的示例反序列化为 Test(null)..
  • 也许 GSON 不明白这一点。它应该适用于 Scala JSON 库。
  • 想一想,因为 JVM 字节码级别的字段名称不会是 @@key,而是以某种方式编码以与那里的命名规则兼容,这是一个基于 Java 反射的库,不是 Scala 感知的,肯定无法匹配名称。
  • (这仍然是最好的答案,不过,您“只是”需要切换到基于 Scala 的 JSON 库:-))
【解决方案2】:

我进行了一些实验,但似乎 GSON 与 Scala case classes 的互操作性不佳(或者相反,我想这是一个视角问题)。我尝试过使用scala.beans.BeanProperty,但似乎没有什么不同。

一种可能的方法是使用常规的classSerializedName 注释,如下例所示:

import com.google.gson.{FieldNamingPolicy, GsonBuilder}
import com.google.gson.annotations.SerializedName

final class Test(k: String) {
  @SerializedName("@@key") val key = k
  override def toString(): String = s"Test($key)"
}

val test = """{"@@key": "foobar"}"""
val gson =  new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])
println(res)

您可以使用此代码here on Scastie.

您可以在SerializedName(以及其他与命名相关的 GSON 功能)here on the user guide 上阅读更多信息。

【讨论】:

  • 我不确定 为什么 它不适用于 case classes。看看javap,从公共接口的角度来看,case class 是普通类的严格超集。 case class 似乎有一些额外的东西最终阻止了 GSON 完成它的工作。不过不确定,只是推测。
【解决方案3】:

我不是 Scala 程序员,我只是使用javap 和反射来检查 Scala 编译器生成的内容,并稍微“了解”了一些 Scala 内部结构的工作原理。 由于以下几个原因,它对您不起作用:

  • Scala 编译器将 case 类元素注释放在构造函数参数中,而 Gson @SerializedName 只能与 fields and methods 一起使用:
// does not work as expected
case class Test(@SerializedName("@@key") `@@key`: String)

从纯 Java 的角度来看:

final Constructor<Test> constructor = Test.class.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(Arrays.deepToString(constructor.getParameterAnnotations()));

public Test(java.lang.String)
[[@com.google.gson.annotations.SerializedName(alternate=[], value=@@key)]]

不确定为什么 Scala 编译器不将注释直接复制到字段,但 Java 语言不允许使用 @SerializedName 注释对参数进行注释,从而导致编译错误(JVM 也不将其视为失败)。

  • 字段名称实际上是在类文件中编码的。

从 Java 的角度来看:

final Field field = Test.class.getDeclaredField("$at$atkey"); // the real name of the `@@key` element
System.out.println(field);
System.out.println(Arrays.deepToString(field.getDeclaredAnnotations()));

private final java.lang.String Test.$at$atkey []

  • Scala 允许将注释移动到字段,这将使您的代码根据 Gson @SerializedName 的设计方式工作(当然,没有考虑到 Scala):
import scala.annotation.meta.field
...
case class Test(@(SerializedName@field)("@@key") `@@key`: String)

Test(value)


如果由于某些/任何原因您必须使用 Gson 并且不能使用 @SerializedName 注释每个字段,那么您可以实现自定义类型适配器,但恐怕您必须深入了解 Scala 的工作原理. 如果我了解 Scala 的功能,它会使用 @ScalaSignature 注释对每个生成的类进行注释。 注释提供了bytes() 方法,该方法返回一个有效负载,该有效负载最有可能用于检测带注释的类型是否为案例类,以及可能如何声明其成员。 我没有找到这样的解析器/解码器,但如果你找到了,你可以在 Gson 中执行以下操作:

  • 注册一个类型适配器工厂,检查它是否可以处理它(基本上,分析@ScalaSignature注解,我相信);
  • 如果可以,则创建一个类型适配器,它知道所有案例类字段,它们的名称可能自己处理@SerializedName,因为您既不能扩展 Gson ReflectiveTypeAdapterFactory,也不能注入名称重映射策略;
  • 考虑到transient 字段(为了良好)和其他排除策略(为了完整性);
  • 读/写每个非排除字段。

工作量太大了,对吧?所以我在这里看到了两个简单的选择:要么像其他人建议的那样使用支持 Scala 的 JSON 工具,要么注释每个具有如此特殊名称的字段。

【讨论】:

    猜你喜欢
    • 2012-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-23
    • 1970-01-01
    相关资源
    最近更新 更多