代码之家  ›  专栏  ›  技术社区  ›  yiksanchan

将映射转换为嵌套的case类

  •  0
  • yiksanchan  · 技术社区  · 7 年前

    Map[String, Any] 到给定的case类,并且映射可以是嵌套映射。例如, personDataMap 应转换为 Person("evan",24,Address(15213,"5000 Forbes Ave"))

    case class Address(zip: Int, name: String)
    case class Person(name: String, age: Int, addr: Address)
    val addrDataMap = Map("zip" -> 15213, "name" -> "5000 Forbes Ave")
    val personDataMap = Map("name" -> "evan", "age" -> 24, "addr" -> addrDataMap)
    

    转换的代码

    import com.twitter.util.{Return, Try}
    import scala.reflect._
    import scala.reflect.runtime.universe._
    
    // Magic from https://stackoverflow.com/a/24100624/7550592
    // Convert a Map[String, _] to a given case class CC
    class CaseClassDataMapConverter[CC <: Product] {
        def fromMap(dataMap: Map[String, Any])(implicit tt: TypeTag[CC]): Try[CC] = {
            val tClassSymbol = typeOf[CC].typeSymbol.asClass
            val rm = typeTag[CC].mirror
            val classMirror = rm.reflectClass(tClassSymbol)
            val constructor = typeOf[CC].decl(termNames.CONSTRUCTOR).asMethod
            val constructorMirror = classMirror.reflectConstructor(constructor)
    
            val constructorTries = constructor.paramLists.flatten.map { param: Symbol =>
                val paramName = param.name.toString
    
                val tryResult: Try[Any] = 
                    if (param.typeSignature <:< typeOf[Option[Any]]) {
                        Return(dataMap.get(paramName))
                    }
                    else {
                        Try(dataMap.getOrElse(paramName, new Exception("Param " + paramName + " not found")))
                    }
                println("paramName = " + paramName + ", result = " + tryResult + ", type = " + param.typeSignature)
                tryResult
            }
    
            Try.collect(constructorTries).flatMap { constructorArgs =>
                Try(constructorMirror(constructorArgs: _*).asInstanceOf[CC])
            }
        }
    }
    
    case class Address(zip: Int, name: String)
    case class Person(name: String, age: Int, addr: Address)
    
    val converter = new CaseClassDataMapConverter[Person]()
    
    val addrDataMap = Map("zip" -> 15213, "name" -> "5000 Forbes Ave")
    val addrTry = fromMap[Address](addrDataMap) // Return(Address(15213,"5000 Forbes Ave"))
    val addr = addrTry.get
    val personDataMap = Map("name" -> "evan", "age" -> 24, "addr" -> addr)
    val personTry = converter.fromMap(personDataMap) // Return(Person(evan,24,Address(15213,5000 Forbes Ave)))
    val person = personTry.get
    

    为了处理嵌套的case类,我检查 param.typeSignature Product ),则转换其值(即嵌套 Map else-if 分支与变更 personDataMap.addr 消耗 而不是一个已经制作好的case类:

    import com.twitter.util.{Return, Try}
    import scala.reflect._
    import scala.reflect.runtime.universe._
    
    // Magic from https://stackoverflow.com/a/24100624/7550592
    // Convert a Map[String, _] to a given case class CC
    class CaseClassDataMapConverter[CC <: Product] {
        def fromMap(dataMap: Map[String, Any])(implicit tt: TypeTag[CC]): Try[CC] = {
            val tClassSymbol = typeOf[CC].typeSymbol.asClass
            val rm = typeTag[CC].mirror
            val classMirror = rm.reflectClass(tClassSymbol)
            val constructor = typeOf[CC].decl(termNames.CONSTRUCTOR).asMethod
            val constructorMirror = classMirror.reflectConstructor(constructor)
    
            val constructorTries = constructor.paramLists.flatten.map { param: Symbol =>
                val paramName = param.name.toString
    
                val tryResult: Try[Any] = 
                    if (param.typeSignature <:< typeOf[Option[Any]]) {
                        Return(dataMap.get(paramName))
                    }
                    // Need to deal with: type=Address, result=Map(...)
                    else if (param.typeSignature <:< typeOf[Product]) {
                        val nestedDataMap = dataMap.getOrElse(paramName, new Exception("Param " + paramName + " not found"))
                        val nestedConverter = new CaseClassDataMapConverter[param]()
                        nestedConverter.fromMap(nestedDataMap)
                    }
                    else {
                        Try(dataMap.getOrElse(paramName, new Exception("Param " + paramName + " not found")))
                    }
                println("paramName = " + paramName + ", result = " + tryResult + ", type = " + param.typeSignature)
                tryResult
            }
    
            Try.collect(constructorTries).flatMap { constructorArgs =>
                Try(constructorMirror(constructorArgs: _*).asInstanceOf[CC])
            }
        }
    }
    
    case class Address(zip: Int, name: String)
    case class Person(name: String, age: Int, addr: Address)
    
    val converter = new CaseClassDataMapConverter[Person]()
    
    val addrDataMap = Map("zip" -> 15213, "name" -> "5000 Forbes Ave")
    val personDataMap = Map("name" -> "evan", "age" -> 24, "addr" -> addrDataMap)
    val personTry = converter.fromMap(personDataMap) // Return(Person(evan,24,Address(15213,5000 Forbes Ave)))
    val person = personTry.get
    

    毫不奇怪,它抱怨:

    error: not found: type param
           val nestedConverter = new CaseClassDataMapConverter[param]()
    

    CaseClassDataMapConverter 带类型参数 Address 在这种情况下?如果是,怎么办?谢谢!

    仅供参考:运行环境是scala控制台。

    1 回复  |  直到 7 年前
        1
  •  0
  •   Dima    7 年前

    TypeTag 进入之内 .fromMap 作为一个明确的论点:

     class CaseClassDataMapConverter {
        private def fromMap(dataMap: Map[String, Any],  tt: TypeTag[_]): Try[Any] = {   
          ...
        }
     }
    
     object CaseClassDataMapConverter {
        def fromMap[CC <: Product : TypeTag](dataMap: Map[String, Any]) = 
           new CaseClassDataMapConverter()
             .fromMap(data, typeTag[CC])
             .asInstanceOf[Try[CC]]
     }
    
    推荐文章