如何改造Scala代码支持Map[String, Any]转嵌套case类及可控类型转换
Scala嵌套Map转Case类改造方案
核心改造点
- 新增case类类型判断逻辑,自动识别嵌套的case类字段类型
- 递归调用工厂类处理嵌套Map结构,同时兼容
Option包裹的嵌套case类场景 - 修复原有默认值获取逻辑的错误(原方法错误传入了多余参数)
- 补充
Long等常用基础类型的转换支持 - 统一使用Scala标准库
Option,避免与JavaOptional类型匹配冲突
完整改造后代码
import scala.reflect.runtime.universe._ object DynamicClassFactory { val classLoaderMirror = runtimeMirror(getClass.getClassLoader) // 提取Option内部类型 private def getOptionInnerType(t: Type): Option[Type] = { if (t <:< typeOf[Option[_]]) Some(t.typeArgs.head) else None } // 递归构造嵌套case类的工厂方法 private def buildNested[T](tpe: Type, map: Map[String, Any]): T = { def factory[A: TypeTag] = new DynamicClassFactory[A] factory(TypeTag(tpe)).buildSafe(map).asInstanceOf[T] } } class DynamicClassFactory[T: TypeTag] { import DynamicClassFactory._ val tpe = typeOf[T] val classSymbol = tpe.typeSymbol.asClass val companion = tpe.typeSymbol.companion val classMirror = classLoaderMirror reflectClass classSymbol val instanceMirror = classLoaderMirror.reflectModule(companion.asModule) val objMirror = classLoaderMirror.reflect(instanceMirror.instance) val constructorSymbol = tpe.decl(termNames.CONSTRUCTOR) val constructorArgs = constructorSymbol.asMethod.paramLists.flatten.map { p => (p.name.decodedName.toString, p.typeSignature) } val defaultConstructor = if (constructorSymbol.isMethod) constructorSymbol.asMethod else { val ctors = constructorSymbol.asTerm.alternatives ctors.map(_.asMethod).find(_.isPrimaryConstructor).get } val constructorMethod = classMirror reflectConstructor defaultConstructor def buildParameters: List[String] = constructorArgs.map(_._1) // 修复默认值获取逻辑,移除多余的入参 def getCompanionParam(method: Symbol): Any = if (classSymbol.isCaseClass) { objMirror.reflectMethod(method.asMethod)() } else null def getDefaults: Map[String, Any] = { val defaults = companion.typeSignature.members .filter(m => m.isMethod && m.name.toString.startsWith("apply$default")) .toList .map { x => val idx = x.name.toString.replaceAll("[^0-9]", "").toInt - 1 (idx, getCompanionParam(x)) }.toMap val defaultValues = scala.collection.mutable.Map[String, Any]() for ((x, i) <- buildParameters.view.zipWithIndex) { if (defaults.contains(i)) { defaultValues(x) = defaults(i) } } defaultValues.toMap } def buildWith(args: Seq[_]): T = { constructorMethod(args: _*).asInstanceOf[T] } def buildSafe(configuration: Map[String, Any]): T = { val config = getDefaults ++ configuration val safeConfig = safeTypeConvert(config, constructorArgs.toMap) buildWith(constructorArgs.map(x => safeConfig(x._1))) } def safeTypeConvert(v: Map[String, Any], t: Map[String, Type]): Map[String, Any] = { val o = scala.collection.mutable.Map[String, Any]() val z = v.filter { case (k, _) => t.contains(k) } z.foreach { case (k, v) => val typeS: Type = t(k) try { val optInnerType = getOptionInnerType(typeS) // 处理Option包裹的嵌套case类 if (optInnerType.isDefined && optInnerType.get.typeSymbol.asClass.isCaseClass) { v match { case nestedMap: Map[String, Any] => o(k) = Some(buildNested(optInnerType.get, nestedMap)) case _ => o(k) = None } } // 处理直接嵌套的case类 else if (typeS.typeSymbol.asClass.isCaseClass) { val nestedMap = v.asInstanceOf[Map[String, Any]] o(k) = buildNested(typeS, nestedMap) } else if (typeS <:< typeOf[Option[String]]) { v match { case x: Int => o(k) = Some(x.toString) case None => o(k) = None case _ => o(k) = Some(v.asInstanceOf[String]) } } else if (typeS <:< typeOf[Option[Int]]) { v match { case x: String => o(k) = Some(x.toFloat.toInt) case None => o(k) = None case _ => o(k) = Some(v.asInstanceOf[Int]) } } else if (typeS <:< typeOf[Option[Long]]) { v match { case x: String => o(k) = Some(x.toLong) case x: Int => o(k) = Some(x.toLong) case None => o(k) = None case _ => o(k) = Some(v.asInstanceOf[Long]) } } else if (typeS <:< typeOf[Option[Boolean]]) { v match { case x: String => o(k) = Some(x.toLowerCase.toBoolean) case None => o(k) = None case _ => o(k) = v } } else if (typeS <:< typeOf[Int]) { v match { case x: String => o(k) = x.toFloat.toInt case _ => o(k) = v.asInstanceOf[Int] } } else if (typeS <:< typeOf[Long]) { v match { case x: String => o(k) = x.toLong case x: Int => o(k) = x.toLong case _ => o(k) = v.asInstanceOf[Long] } } else if (typeS =:= typeOf[String]) { v match { case x: Int => o(k) = x.toString case _ => o(k) = v.asInstanceOf[String] } } else if (typeS =:= typeOf[Boolean]) { v match { case x: String => o(k) = x.toLowerCase.toBoolean case _ => o(k) = v } } else if (typeS <:< typeOf[List[_]]) { o(k) = if (v == Nil) List.empty else v.asInstanceOf[List[_]] } else if (typeS <:< typeOf[Map[_, _]]) { o(k) = if (v == None) Map.empty else v } else { throw new Exception(s"$k must be of type ${typeS.typeSymbol.name.decoded}, got ${v.getClass.getName}: ${v.toString}") } } catch { case e@(_: ClassCastException | _: NumberFormatException | _: IllegalArgumentException) => throw new Exception(s"$k must be of type ${typeS.typeSymbol.name.decoded}, got ${v.getClass.getName}: ${v.toString}", e) } } o.toMap } }
测试验证
针对你提供的嵌套场景测试用例,将示例中的Optional替换为Scala标准的Option后即可正常运行:
case class Address(addLine1: String, addLine2: Option[String], zip: Long) case class Inner(id: Option[String], addr: Option[Address], isPath: Option[Boolean]) case class Outer(name: String, addtlnInfo: Inner) val map: Map[String, Any] = Map("name" -> "john", "addtlnInfo" -> Map("id" -> 123, "addr" -> Map("addLine1" -> "901 Lincoln St", "zip" -> "80101"))) val ins = new DynamicClassFactory[Outer] println(ins.buildSafe(map)) // 输出结果:Outer(john,Inner(Some(123),Some(Address(901 Lincoln St,None,80101)),None))
内容的提问来源于stack exchange,提问作者ni_i_ru_sama
相关产品推荐
相关产品推荐

