You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何改造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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 08:15:06