从Scala 3中的Option中提取参数化类型的java.lang.reflect.Type

问题描述 投票:0回答:1

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 scala gson
1个回答
0
投票

看起来

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 ParameterizedTypeImpl”(根据需要),
而在 Scala 3 中,这会打印“class scala.Option Class”。

我认为这可能是造成这种情况的根本原因。 Dotty 问题 17069 似乎跟踪了这一点


免责声明:我不熟悉 Scala,所以这个答案可能有部分不正确。我使用 https://scastie.scala-lang.org 与 Scala 2.13.12 和 Scala 3.3.1 进行测试。

© www.soinside.com 2019 - 2024. All rights reserved.