如何基于Scala反射Symbol将Any对象转为对应类型?
Scala反射:通过Symbol将Any对象转换为对应类型
问题描述
我有一个Scala反射的Symbol:
val symbol = scala.reflect.api.Symbol
现在需要把一个Any类型的对象:
val obj: Any
强制转换为该Symbol对应的类型。
我遇到这个需求的场景是:我写了一个隐式方法,用来把Map[String, Any]转换成Scala样例类,但需要在方法内部对每个字段做类型强制转换。下面代码里的TODO部分就是我要实现类型转换的位置:
object MapStringAnyOps { implicit class MapStringAnyOps(m: Map[String, _]) { def toCaseClass[T: TypeTag : ClassTag]: T = { val rm = runtimeMirror(classTag[T].runtimeClass.getClassLoader) val destType = typeOf[T] val classTest = destType.typeSymbol.asClass val classMirror = rm.reflectClass(classTest) val constructor = destType.decl(termNames.CONSTRUCTOR).asMethod val constructorMirror = classMirror.reflectConstructor(constructor) val constructorArgs = constructor.paramLists.flatten.map((param: Symbol) => { val paramName = param.name.toString if (param.typeSignature <:< typeOf[Option[Any]]) m.get(paramName) // TODO 这里我需要把Any类型的值转换成该字段的正确类型,但只有Symbol,没法直接用asInstanceOf[T] else m.getOrElse(paramName, throw new IllegalArgumentException("Map缺少必填参数: " + paramName)) }) try { constructorMirror(constructorArgs: _*).asInstanceOf[T] } catch { case e: java.lang.IllegalArgumentException => throw new java.lang.IllegalArgumentException(s"转换到类构造器出错:\n 源类型: ${constructorArgs.map(r => r.getClass.getName).mkString(",")}, 目标构造器类型: ${constructor.paramLists.flatten.map(r => r.typeSignature.toString).mkString(",")}", e) } } } }
解决方案
核心思路是通过Symbol获取目标类型签名,结合Scala反射工具实现类型转换,需要分别处理普通类型和Option类型的场景。
1. 封装通用类型转换方法
先实现一个工具方法,接收Any值和目标Type,完成类型转换:
import scala.reflect.runtime.universe._ private def castValue(value: Any, targetType: Type): Any = { value match { // 字符串类型直接转成String case v if targetType =:= typeOf[String] => v.toString // 处理基本类型(如Int/Long等,适配Map中存的包装类型) case v if targetType.isPrimitive => targetType match { case t if t =:= typeOf[Int] => v.asInstanceOf[Number].intValue() case t if t =:= typeOf[Long] => v.asInstanceOf[Number].longValue() case t if t =:= typeOf[Double] => v.asInstanceOf[Number].doubleValue() case t if t =:= typeOf[Float] => v.asInstanceOf[Number].floatValue() case t if t =:= typeOf[Boolean] => v.asInstanceOf[Boolean] case t if t =:= typeOf[Byte] => v.asInstanceOf[Number].byteValue() case t if t =:= typeOf[Short] => v.asInstanceOf[Number].shortValue() case _ => v } // 引用类型直接强转 case _ => value.asInstanceOf[AnyRef] } }
2. 修改原代码中的字段转换逻辑
在构造参数处理的map方法里,调用上面的工具方法完成类型转换:
val constructorArgs = constructor.paramLists.flatten.map((param: Symbol) => { val paramName = param.name.toString val paramType = param.typeSignature if (paramType <:< typeOf[Option[_]]) { // 处理Option类型,转换内部实际值 m.get(paramName).map(v => castValue(v, paramType.typeArgs.head)) } else { // 处理必填字段,转换后返回 val v = m.getOrElse(paramName, throw new IllegalArgumentException("Map缺少必填参数: " + paramName)) castValue(v, paramType) } })
完整修改后的代码
整合工具方法后的完整实现:
import scala.reflect.ClassTag import scala.reflect.runtime.universe._ object MapStringAnyOps { implicit class MapStringAnyOps(m: Map[String, _]) { def toCaseClass[T: TypeTag : ClassTag]: T = { val rm = runtimeMirror(classTag[T].runtimeClass.getClassLoader) val destType = typeOf[T] val classTest = destType.typeSymbol.asClass val classMirror = rm.reflectClass(classTest) val constructor = destType.decl(termNames.CONSTRUCTOR).asMethod val constructorMirror = classMirror.reflectConstructor(constructor) val constructorArgs = constructor.paramLists.flatten.map((param: Symbol) => { val paramName = param.name.toString val paramType = param.typeSignature if (paramType <:< typeOf[Option[_]]) { m.get(paramName).map(v => castValue(v, paramType.typeArgs.head)) } else { val v = m.getOrElse(paramName, throw new IllegalArgumentException("Map缺少必填参数: " + paramName)) castValue(v, paramType) } }) try { constructorMirror(constructorArgs: _*).asInstanceOf[T] } catch { case e: java.lang.IllegalArgumentException => throw new java.lang.IllegalArgumentException( s"转换到类构造器出错:\n 源类型: ${constructorArgs.map(r => r.map(_.getClass.getName).getOrElse("None")).mkString(",")}, " + s"目标构造器类型: ${constructor.paramLists.flatten.map(r => r.typeSignature.toString).mkString(",")}", e ) } } private def castValue(value: Any, targetType: Type): Any = { value match { case v if targetType =:= typeOf[String] => v.toString case v if targetType.isPrimitive => targetType match { case t if t =:= typeOf[Int] => v.asInstanceOf[Number].intValue() case t if t =:= typeOf[Long] => v.asInstanceOf[Number].longValue() case t if t =:= typeOf[Double] => v.asInstanceOf[Number].doubleValue() case t if t =:= typeOf[Float] => v.asInstanceOf[Number].floatValue() case t if t =:= typeOf[Boolean] => v.asInstanceOf[Boolean] case t if t =:= typeOf[Byte] => v.asInstanceOf[Number].byteValue() case t if t =:= typeOf[Short] => v.asInstanceOf[Number].shortValue() case _ => v } case _ => value.asInstanceOf[AnyRef] } } } }
关键说明
- 针对
Option类型,先提取内部实际类型(paramType.typeArgs.head),再对Some中的值做转换; - 基本类型处理了包装类型转原始类型的场景(比如
Integer转Int); - 字符串类型通过
toString适配各种可转字符串的输入; - 引用类型直接强转,构造器调用时会自动验证类型合法性,不匹配会抛出异常。
内容的提问来源于stack exchange,提问作者linehrr
相关产品推荐
相关产品推荐

