Scala 3中如何从Option提取参数化类型的java.lang.reflect.Type?
问题描述
核心需求(TLDR)
给定classOf[Option[String]]这类类型,需提取其内部参数化类型java.lang.String。
背景说明
我有一个使用GSON进行序列化/反序列化的Scala 2.13项目,其中实现的Option编解码器可正常工作,代码如下:
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后,上述代码失效,原因是Scala 3中的Option不再是java.lang.reflect.ParameterizedType,需要重写innerType方法适配Scala 3。
项目代码库规模庞大,无法迁移到Circe等其他序列化框架,仅该问题阻碍Scala 3迁移。
可复现代码
使用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)
解决方案
Scala 3中Option作为泛型特质,其Java反射类型不再直接是ParameterizedType,需通过以下方式提取内部参数化类型:
兼容Scala 2/3的通用实现
通过反射访问Scala 3泛型类型的内部字段,同时保留Scala 2的兼容逻辑:
import java.lang.reflect.Type 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 { // 兼容Scala 2的ParameterizedType场景 case pt: ParameterizedType => pt.getActualTypeArguments()(0) // 处理Scala 3的泛型类型 case _ => try { // Scala 3生成的泛型类型会包含argTypes字段存储类型参数 val argTypesField = outerType.getClass.getDeclaredField("argTypes") argTypesField.setAccessible(true) val argTypes = argTypesField.get(outerType).asInstanceOf[Array[Type]] if (argTypes.nonEmpty) argTypes(0) else throw new UnsupportedOperationException("No type arguments found for Option") } catch { case e: NoSuchFieldException => throw new UnsupportedOperationException(s"Failed to extract inner type from $outerType", e) } } // 原serialize和deserialize方法保持不变 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的泛型类型在Java反射中会被包装为带有
argTypes字段的实现类,通过反射访问该字段可获取类型参数。 - 上述实现兼容Scala 2和Scala 3,无需修改其他业务代码。
- 若项目中使用
TypeToken传递类型信息(如new TypeToken[Option[String]](){}),可直接从TypeToken.getType()获取参数化类型,逻辑会更可靠。
内容的提问来源于stack exchange,提问作者hohonuuli
相关产品推荐
相关产品推荐

