开发过程中遇到需要将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)
}
- 参数中通过隐式获取到泛型的ClassTag,可以通过它获取被擦除的类型信息,避免了jvm运行时的泛型擦除导致我们拿不到类信息。
- 代码中就可以通过ClassTag拿到将要被擦除的Class,并获取构造方法及声明的字段信息。
- 通过字段名称遍历Map,获取map中对应名称的值,转换为数组。
- case class只有全参构造,故在构造方法列表中获取第一个即可。
- 通过构造方法创建一个该类的新实例,并转换类型为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即可