开发过程中遇到需要将Map转换为case class以及将case class转换回Map的场景,之前写java的时候会使用BeanUtil之类的工具类实现,但这些工具类到scala中遇到case class有些水土不服,不能正常使用,无奈之下只能自己写个工具,通过反射实现这两个的转换

Map转换为case class

先看代码:

//Map的value不可以是Any,因为Any包含AnyVal,但newInstance(Object ... initargs)参数需要Object的子类,故只可用AnyRef
def mapToCaseClass[T](params: Map[String, AnyRef])(implicit ct: ClassTag[T]): T = {
  val ctor = ct.runtimeClass.getConstructors.head
  val args = ct.runtimeClass.getDeclaredFields.map(f => params.getOrElse(f.getName, null))
  ctor.newInstance(args:_*).asInstanceOf[T]
}

def main(args: Array[String]): Unit = {
  val map = Map("t1" -> "t1", "t2" -> 123)
  mapToCaseClass[TestDTO](map)
}
  1. 参数中通过隐式获取到泛型的ClassTag,可以通过它获取被擦除的类型信息,避免了jvm运行时的泛型擦除导致我们拿不到类信息。
  2. 代码中就可以通过ClassTag拿到将要被擦除的Class,并获取构造方法及声明的字段信息。
  3. 通过字段名称遍历Map,获取map中对应名称的值,转换为数组。
  4. case class只有全参构造,故在构造方法列表中获取第一个即可。
  5. 通过构造方法创建一个该类的新实例,并转换类型为T,并返回。

case class转换为Map

同样先看代码:

def caseClassToMap(cc: Product): Map[String, Any] = {
  cc.getClass.getDeclaredFields.map(_.getName).zip(cc.productIterator.to).toMap
}

def main(args: Array[String]): Unit = {
  val t = TestDTO("t1", 123)
  caseClassToMap(t)
}

因为scala中所有的case class都是Product特征的实现,然后Product提供了获取所有字段值的方法,所以我们只要通过class获取到字段名称,再按顺序拼接上对应的字段值就可以了
使用zip方法,将class.getDeclaredFields获取的字段名拼接cc.productIterator获取的字段值就可以得到一个二元组的集合,再用toMap转换为Map即可