tldr;给定类似
classOf[Option[String]]
的东西,能够提取选项类型 java.lang.String。
完整的上下文是我有一个使用 GSON 将对象序列化/反序列化到 scala 2.13 的项目。我有一个 GSON 编解码器选项,可以与 Scala 2.13 配合使用:
import java.lang.reflect.Type
import scala.reflect.ClassTag
import com.google.gson._
import java.lang.reflect.ParameterizedType
class OptionSerializer extends JsonSerializer[Option[Any]] with JsonDeserializer[Option[Any]] {
private def innerType(outerType: Type): Type =
outerType match {
case pt: ParameterizedType => pt.getActualTypeArguments()(0)
case _ => throw new UnsupportedOperationException("Expected ParameterizedType")
}
override def serialize(
src: Option[Any],
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement = {
src match {
case None => JsonNull.INSTANCE
case Some(v) => context.serialize(v, innerType(typeOfSrc))
}
}
override def deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): Option[Any] = {
json match {
case null => None
case _ if json.isJsonNull => None
case _ => Some(context.deserialize(json, innerType(typeOfT)))
}
}
}
当我尝试将项目切换到 Scala 3 时,此方法不再有效,因为显然,
Option
不再是java.lang.reflect.ParameterizedType
。
private def innerType(outerType: java.lang.reflect.Type): java.lang.reflect.Type =
outerType match {
case pt: ParameterizedType => pt.getActualTypeArguments()(0)
case _ => throw new UnsupportedOperationException("Expected ParameterizedType")
}
我需要替代这个适用于 Scala 3 的
innerType
方法。
此外,在有人建议我将其移至 Circe 等之前。这是一个很大的代码库。移植到 Scala 3 仅在这一个障碍上,而迁移到 Circe 等则是一项非常艰巨的任务。
编辑------
下面我添加了一个可使用 scala-cli 运行的示例代码。将
using scala
线更改为 //> using scala "3.3.1"
以查看故障模式。
#!/usr/bin/env -S scala-cli shebang
// "2.13.12" or "3.3.1"
//> using scala "2.13.12"
//> using lib "com.google.code.gson:gson:2.10.1"
import java.lang.reflect.Type
import scala.reflect.ClassTag
import com.google.gson._
import java.lang.reflect.ParameterizedType
class OptionSerializer extends JsonSerializer[Option[Any]] with JsonDeserializer[Option[Any]] {
private def innerType(outerType: Type): Type =
outerType match {
case pt: ParameterizedType => pt.getActualTypeArguments()(0)
case _ => throw new UnsupportedOperationException(s"Expected ParameterizedType. Found $outerType")
}
override def serialize(
src: Option[Any],
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement = {
src match {
case None => JsonNull.INSTANCE
case Some(v) => context.serialize(v, innerType(typeOfSrc))
}
}
override def deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): Option[Any] = {
json match {
case null => None
case _ if json.isJsonNull => None
case _ => Some(context.deserialize(json, innerType(typeOfT)))
}
}
}
// Pretend these are JPA entities. So mutable vars are required for this case
class Foo[A]() {
var required: String = _
var optional: Option[A] = None
override def toString(): String = s"Foo($required, $optional)"
}
object Foo {
def apply[A](required: String, optional: Option[A] = None): Foo[A] = {
val foo = new Foo[A]
foo.required = required
foo.optional = optional
foo
}
}
def display(a: Foo[?], json: String, b: Foo[?]): Unit = {
println("----------------")
println(s"START: $a")
println(s"JSON: $json")
println(s"END: $b")
}
val gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(classOf[Option[Any]], new OptionSerializer)
.create()
val a = Foo("hello", Some(3))
val jsonA = gson.toJson(a)
val aa = gson.fromJson(jsonA, classOf[Foo[Int]])
display(a, jsonA, aa)
val b = Foo("hello", Some("world"))
val jsonB = gson.toJson(b)
val bb = gson.fromJson(jsonB, classOf[Foo[String]])
display(b, jsonB, bb)
val c = Foo("goodbye")
val jsonC = gson.toJson(c)
val cc = gson.fromJson(jsonC, classOf[Foo[String]])
display(c, jsonC, cc)
看起来
java.lang.reflect.Field.getGenericType()
的行为在 Scala 2 和 3 之间有所不同。这是一个从您的示例派生的小示例,它演示了这一点:
class Foo() {
var f: Option[String] = None
}
val t = classOf[Foo].getDeclaredField("f").getGenericType()
println(s"$t ${t.getClass().getSimpleName()}")
在 Scala 2 中打印“scala.Option
而在 Scala 3 中,这会打印“class scala.Option Class”。
我认为这可能是造成这种情况的根本原因。 Dotty 问题 17069 似乎跟踪了这一点。
免责声明:我不熟悉 Scala,所以这个答案可能有部分不正确。我使用 https://scastie.scala-lang.org 与 Scala 2.13.12 和 Scala 3.3.1 进行测试。