Преобразование Map во вложенный класс case

Я хочу преобразовать 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)

Код, который преобразует Map[String, Any] в невложенный класс 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))
                }
                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. Если это класс case (Product), я сначала преобразую его значение (т. е. вложенный Map) в класс case. Я добавляю ветку else-if и изменяю personDataMap.addr, чтобы использовать Map, а не уже созданный класс 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.


person Yik San Chan    schedule 18.09.2018    source источник


Ответы (1)


Вы можете избавиться от параметра типа и просто передать 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]]
 }
person Dima    schedule 18.09.2018
comment
Я думаю, вам также понадобится этот ответ или что-то подобное для создания тегов типов для рекурсивных вызовов: stackoverflow.com/questions/27887386/get-a-typetag-from-a-type - person Kolmar; 18.09.2018