微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用受控类型转换将 Map[String, Any] 转换为嵌套 case 类

如何解决使用受控类型转换将 Map[String, Any] 转换为嵌套 case 类

我想将 Map[String,Any] 转换为给定的 case 类,并且该映射可以是嵌套映射。 case 类可以有某些字段作为 Optional,如果地图没有相关字段,它将有一个认值。 例如。

case class CC(a: Int,b: Option[Int],c: String)
Map(a -> 1,c -> "abc")
result case class : CC(1,None,"abc")

唯一需要注意的是我需要控制这些字段的 typeSignatures。 例如:

Map(a ->,b -> "10",c ->"abc"). \\Note b is string here
result case class : CC(1,Some(10),"abc")

我得到了一些可以执行这种确切行为的代码(下面的代码),但我似乎无法使其适用于嵌套案例类

import scala.reflect.runtime.universe._


object DynamicclassFactory {
  val classLoaderMirror = runtimeMirror(getClass.getClassLoader)
}

class DynamicclassFactory[T: TypeTag] {
    import DynamicclassFactory.classLoaderMirror

    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 ( x => x._1 )
    
    def getCompanionParam(name: String): Any = 
      if(classSymbol.isCaseClass) {
        objMirror.reflectMethod(instanceMirror.symbol.info.decl(TermName(name)).asMethod)("test")   
    }

    
    def getDefaults = {
      val defaults = companion.typeSignature.members.filter { m => m.isMethod && m.name.toString.contains("apply$default")}.toList.map { x => (x.asMethod.name.toTermName.toString.replaceAll("[^0-9]","").toInt-1,getCompanionParam(x.asMethod.name.toTermName.toString)) }.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,v) =>
        t.contains(k)
    }
    z.foreach {
      case (k,v) =>
        val typeS: Type = t(k)
        try {
          if (typeS <:< typeOf[Option[String]]) {
            v match {
              case x: Int => o(k) = Some(x.toString)
              case y: Option[nothing] => 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 y: Option[nothing] => o(k) = None
              case _ => o(k) = Some(v.asInstanceOf[Int])
            }
          }
          else if (typeS <:< typeOf[Option[Boolean]]) {
            v match {
              case x: String => o(k) = Some(x.toLowerCase.toBoolean)
              case y: Option[nothing] => o(k) = None
              case _ => o(k) = v
            }
          }
          
          else if (typeS <:< typeOf[Int]) {
            v match {
              case x: String => o(k) = x.toFloat.toInt // this is to handle decimals returned from json
              case _ => o(k) = v.asInstanceOf[Int]
            }
          } 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[_]]) {
            if (v == Nil) {
              o(k) = List[String]()
            } else {
              o(k) = v.asInstanceOf[List[_]]
            }
          } else if (typeS <:< typeOf[Map[_,_]]) {
            if (v == None) {
              o(k) = Map()
            } else o(k) = v
          }
          else {
            throw new Exception(s"sdf $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}")
        }
    }
    return o.toMap
  }
}

以上对于不是嵌套地图的地图非常有效,但对于嵌套地图失败

case class Address(addLine1: String,addLine2: Optional[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]
ins.buildSafe(map)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。