我有两个嵌套的案例类:
case class InnerClass(param1: String, param2: String)
case class OuterClass(myInt: Int, myInner: InnerClass)
val x = OuterClass(11, InnerClass("hello", "world"))
我想将其转换为Map [String,Any]类型的嵌套地图,以便我得到这样的内容:
Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))
当然,解决方案应该是通用的,适用于任何案例类。
注意: This discussion就如何将单个案例类映射到Map提供了一个很好的答案。但我无法使其适应嵌套的案例类。相反,我得到:
Map(myInt -> 11, myInner -> InnerClass(hello,world)
答案 0 :(得分:2)
正如Luigi Plinge在上面的评论中指出的那样,这是一个非常糟糕的主意 - 你在窗口中抛出了类型安全性,并且会遇到很多难看的演员和运行时错误。
也就是说,用新的Scala 2.10 Reflection API:
做你想做的事很容易def anyToMap[A: scala.reflect.runtime.universe.TypeTag](a: A) = {
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(a.getClass.getClassLoader)
def a2m(x: Any, t: Type): Any = {
val xm = mirror reflect x
val members = t.declarations.collect {
case acc: MethodSymbol if acc.isCaseAccessor =>
acc.name.decoded -> a2m((xm reflectMethod acc)(), acc.typeSignature)
}.toMap
if (members.isEmpty) x else members
}
a2m(a, typeOf[A])
}
然后:
scala> println(anyToMap(x))
Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))
不要这样做。事实上,你应该尽力避免在Scala中完全反映运行时反射 - 它几乎从来没有必要。我只发布这个答案,因为如果您确定必须使用运行时反射,那么最好使用Scala Reflection API而不是Java。
答案 1 :(得分:0)
只需递归调用它。所以
def getCCParams(cc: AnyRef) =
(Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
f.setAccessible(true)
val value = f.get(cc) match {
// this covers tuples as well as case classes, so there may be a more specific way
case caseClassInstance: Product => getCCParams(caseClassInstance)
case x => x
}
a + (f.getName -> value)
}
答案 2 :(得分:0)
这是基于无形的更原则性的解决方案。 https://github.com/yongjiaw/datacrafts
class NoSchemaTest extends FlatSpec with ShapelessProduct.Implicits {
"Marshalling and unmarshalling with Map" should "be successful" in {
val op = NoSchema.of[TestClass]
assert(
op.operator.marshal(
Map(
"v1" -> 10,
"v5" -> Map("_2" -> 12),
"v3" -> Iterable(Seq("v21" -> 3)),
"v6" -> TestClass3(v31 = 5)
)) == TestClass(
v1 = 10,
v5 = (null, 12),
v3 = Some(Seq(Some(
TestClass2(
v21 = 3,
v22 = null
)))),
v6 = Some(TestClass3(v31 = 5)),
v2 = None,
v4 = null
)
)
assert(
op.operator.unmarshal(
TestClass(
v1 = 1,
v2 = null
)
) == Map(
"v1" -> 1,
"v2" -> null,
// the rest are default values
"v6" -> null,
"v5" -> Map("_2" -> 2, "_1" -> "a"),
"v4" -> null,
"v3" -> Seq(
Map(
"v21" -> 3,
"v22" -> Map("v" -> Map(), "v32" -> Seq(12.0), "v31" -> 0)
)
)
)
)
}
}
object NoSchemaTest {
case class TestClass(v1: Int,
v2: Option[Seq[Option[Double]]] = None,
v3: Option[Seq[Option[TestClass2]]] = Some(Seq(Some(TestClass2()))),
v4: Seq[Int] = null,
v5: (String, Int) = ("a", 2),
v6: Option[TestClass3] = None
)
case class TestClass2(v21: Int = 3,
v22: TestClass3 = TestClass3(0)
)
case class TestClass3(v31: Int,
v32: Iterable[Double] = Seq(12),
v: Map[String, Int] = Map.empty
)
}
trait DefaultRule extends Operation.Rule {
override def getOperator[V](operation: Operation[V]): Operation.Operator[V] = {
operation.context.noSchema match {
case _: Primitive[V] => new PrimitiveOperator[V](operation)
case shapeless: ShapelessProduct[V, _] =>
new ShapelessProductMapper[V](operation, shapeless)
case option: OptionContainer[_] =>
new OptionOperator[option.Elem](
option.element, operation.asInstanceOf[Operation[Option[option.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case map: MapContainer[_] =>
new MapOperator[map.Elem](
map.element, operation.asInstanceOf[Operation[Map[String, map.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case seq: SeqContainer[_] =>
new SeqOperator[seq.Elem](
seq.element, operation.asInstanceOf[Operation[Seq[seq.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case iterable: IterableContainer[_] =>
new IterableOperator[iterable.Elem](
iterable.element, operation.asInstanceOf[Operation[Iterable[iterable.Elem]]])
.asInstanceOf[Operation.Operator[V]]
}}}