如何使用 scala 反序列化包含 @@ 的 json 字符串'
how to deserialize a json string that contains @@ with scala'
正如标题已经解释的那样,我想反序列化一个 json 字符串,其中包含一个以 @@ 开头的键。使用@@ 我的标准方法使用 case 类 遗憾地不再起作用。
val test = """{"@@key": "value"}"""
case class Test(@@key: String) // not possible
val gson = new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])
如何在不对输入 json 字符串进行预处理的情况下使用 @@?
最简单的答案是引用字段名称:
case class Test(`@@key`: String)
我做了一些实验,但似乎 GSON 与 Scala case class
es 不能很好地互操作(或者相反,我想这是一个角度问题)。我试着玩弄 scala.beans.BeanProperty
但它似乎并没有什么不同。
一种可能的方法是使用常规 class
和 SerializedName
注释,如本例所示:
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。
我不是 Scala 程序员,我只是使用 javap
和反射来检查 Scala 编译器生成的内容以及稍微 "learnt" 一些 Scala 内部结构的工作原理。
由于以下几个原因,它对您不起作用:
- Scala 编译器将 case class 元素注释放在构造函数参数中,而 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 不允许也将其视为失败)。
- 字段名称实际上是在 class 文件中编码的。
从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
<- this is how the field can be accessed from Java
[]
<- no annotations by default
- Scala 允许将注释移动到字段,这将使您的代码根据 Gson
@SerializedName
的设计方式工作(当然,不考虑 Scala):
import scala.annotation.meta.field
...
case class Test(@(SerializedName@field)("@@key") `@@key`: String)
Test(value)
如果由于 some/any 的原因你必须使用 Gson 并且不能用 @SerializedName
注释每个字段,那么你可以实现一个自定义类型适配器,但恐怕你必须深入了解 Scala 的工作原理。
如果我了解 Scala 的作用,它会使用 @ScalaSignature
注释对每个生成的 class 进行注释。
注释提供了 bytes()
方法,该方法 returns 最有可能用于检测注释类型是否为 case class 的有效载荷,以及其成员的声明方式。
我没有找到这样的 parser/decoder,但是如果你找到了,你可以在 Gson 中执行以下操作:
- 注册一个类型适配器工厂,检查它是否可以处理它(基本上,分析
@ScalaSignature
注释,我相信);
- 如果可以,则创建一个类型适配器,它可以识别所有大小写 class 字段,它们的名称可能会自己处理
@SerializedName
,因为您既不能扩展 Gson ReflectiveTypeAdapterFactory
也不注入名称重映射策略;
- 考虑
transient
字段(永远)和其他排除策略(为了完整性);
- read/write 每个未排除的字段。
工作量太大了吧?所以我在这里看到两个简单的选择:要么像其他人建议的那样使用 Scala-aware JSON 工具,要么注释具有这种特殊名称的每个字段。
正如标题已经解释的那样,我想反序列化一个 json 字符串,其中包含一个以 @@ 开头的键。使用@@ 我的标准方法使用 case 类 遗憾地不再起作用。
val test = """{"@@key": "value"}"""
case class Test(@@key: String) // not possible
val gson = new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])
如何在不对输入 json 字符串进行预处理的情况下使用 @@?
最简单的答案是引用字段名称:
case class Test(`@@key`: String)
我做了一些实验,但似乎 GSON 与 Scala case class
es 不能很好地互操作(或者相反,我想这是一个角度问题)。我试着玩弄 scala.beans.BeanProperty
但它似乎并没有什么不同。
一种可能的方法是使用常规 class
和 SerializedName
注释,如本例所示:
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。
我不是 Scala 程序员,我只是使用 javap
和反射来检查 Scala 编译器生成的内容以及稍微 "learnt" 一些 Scala 内部结构的工作原理。
由于以下几个原因,它对您不起作用:
- Scala 编译器将 case class 元素注释放在构造函数参数中,而 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 不允许也将其视为失败)。
- 字段名称实际上是在 class 文件中编码的。
从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
<- this is how the field can be accessed from Java
[]
<- no annotations by default
- Scala 允许将注释移动到字段,这将使您的代码根据 Gson
@SerializedName
的设计方式工作(当然,不考虑 Scala):
import scala.annotation.meta.field
...
case class Test(@(SerializedName@field)("@@key") `@@key`: String)
Test(value)
如果由于 some/any 的原因你必须使用 Gson 并且不能用 @SerializedName
注释每个字段,那么你可以实现一个自定义类型适配器,但恐怕你必须深入了解 Scala 的工作原理。
如果我了解 Scala 的作用,它会使用 @ScalaSignature
注释对每个生成的 class 进行注释。
注释提供了 bytes()
方法,该方法 returns 最有可能用于检测注释类型是否为 case class 的有效载荷,以及其成员的声明方式。
我没有找到这样的 parser/decoder,但是如果你找到了,你可以在 Gson 中执行以下操作:
- 注册一个类型适配器工厂,检查它是否可以处理它(基本上,分析
@ScalaSignature
注释,我相信); - 如果可以,则创建一个类型适配器,它可以识别所有大小写 class 字段,它们的名称可能会自己处理
@SerializedName
,因为您既不能扩展 GsonReflectiveTypeAdapterFactory
也不注入名称重映射策略; - 考虑
transient
字段(永远)和其他排除策略(为了完整性); - read/write 每个未排除的字段。
工作量太大了吧?所以我在这里看到两个简单的选择:要么像其他人建议的那样使用 Scala-aware JSON 工具,要么注释具有这种特殊名称的每个字段。