【问题标题】:Convert Nested Case Classes to Nested Maps in Scala在 Scala 中将嵌套案例类转换为嵌套映射
【发布时间】:2013-03-10 16:22:30
【问题描述】:

我有两个嵌套的案例类:

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)

【问题讨论】:

  • 这个问题不是很清楚。在 Map 的上下文中,myIntmyInner 是什么?它们是从OuterClass 实例中获取的,还是将String 用作密钥?在任何情况下,使用Any 表示您可能在Scala 这样的静态类型语言中做错了什么。明确你想要做什么,你会得到一些有用的建议。
  • 检查productIterator,这是一种迭代Products 的所有值的方法。所有案例类都是Products。
  • @luigi-plinge 不确定我的问题是否正确。在 Map 的上下文中,myInt 和 myInner 都取自 OuterClass 实例并用作键。这是通用的。为了阐明背景,我对应用程序中的主要对象使用嵌套案例类。此外,我还有一个Bencode Encoder,它接受字符串、整数和映射。我的意图是将我的对象转换为嵌套地图并将其提供给 Bencode 编码器
  • 我想我明白了:您想使用案例类中的字段名称作为 Map 中的字符串键。做到这一点的唯一方法是使用反射,因为变量名不是应该在运行时可用的数据。除非你真的需要,否则不要使用反射。如果您需要将字符串用作键,请为此目的在您的案例类中添加一个String 字段。那么它应该很容易。

标签: scala case-class


【解决方案1】:

正如 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 反射 API 比使用 Java 更好。

【讨论】:

  • 为什么不使用productIterator
  • @pedrofurla:因为我想假装productIterator 不存在?当您使用scala.reflect.runtime 时,至少很明显您正在做一些令人讨厌的事情。
【解决方案2】:

只需递归调用它。所以

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)
  }

【讨论】:

    【解决方案3】:

    这里有一个基于 shapeless 的更有原则的解决方案。 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]]
    }}}
    

    【讨论】:

      猜你喜欢
      • 2015-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-25
      • 2015-09-09
      • 1970-01-01
      • 2019-11-24
      相关资源
      最近更新 更多