要在Scala中映射的案例类

Wil*_*ill 71 scala case-class

有没有一种很好的方法可以转换Scala case class实例,例如

case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
Run Code Online (Sandbox Code Playgroud)

进入某种映射,例如

getCCParams(x) returns "param1" -> "hello", "param2" -> "world"
Run Code Online (Sandbox Code Playgroud)

适用于任何案例类,而不仅仅是预定义的类.我发现你可以通过编写一个询问底层Product类的方法来拉出case类名,例如

def getCCName(caseobj: Product) = caseobj.productPrefix 
getCCName(x) returns "MyClass"
Run Code Online (Sandbox Code Playgroud)

所以我正在寻找类似的解决方案,但对于案例类字段.我想象一个解决方案可能不得不使用Java反射,但是如果案例类的底层实现发生变化,我讨厌在未来的Scala版本中编写一些内容.

目前我正在使用Scala服务器并使用案例类定义协议及其所有消息和异常,因为它们是如此美观,简洁的构造.但是,我需要将它们转换为Java映射,以通过消息传递层发送以供任何客户端实现使用.我当前的实现只是分别为每个案例类定义一个翻译,但是找到一个通用的解决方案会很好.

Wal*_*ang 86

这应该工作:

def getCCParams(cc: AnyRef) =
  cc.getClass.getDeclaredFields.foldLeft(Map.empty[String, Any]) { (a, f) =>
    f.setAccessible(true)
    a + (f.getName -> f.get(cc))
  }
Run Code Online (Sandbox Code Playgroud)

  • 如果这不难你能解释你写的是什么吗? (14认同)
  • @RobinGreen案例类不能相互继承 (2认同)
  • 可能以这种方式更容易理解:`case class Person(name:String,surname:String)val person = new Person("Daniele","DalleMule")val personAsMap = person.getClass.getDeclaredFields.foldLeft(Map [String ,Any]())((map,field)=> {field.setAccessible(true)map +(field.getName - > field.get(person))})` (2认同)

And*_*ejs 36

因为案例类扩展产品可以简单地用于.productIterator获取字段值:

def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names
                .zip( cc.productIterator.to ).toMap // zipped with all values
Run Code Online (Sandbox Code Playgroud)

或者:

def getCCParams(cc: Product) = {          
      val values = cc.productIterator
      cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap
}
Run Code Online (Sandbox Code Playgroud)

Product的一个优点是您无需调用setAccessible字段来读取其值.另一个是productIterator不使用反射.

请注意,此示例适用于不扩展其他类且不在构造函数外声明字段的简单案例类.

  • `getDeclaredFields`规范说:"返回的数组中的元素没有排序,也没有任何特定的顺序." 为什么领域以正确的顺序返回? (4认同)
  • 是的,我不会认为这是理所当然的。我不想开始编写不可移植的代码。 (2认同)

Pio*_*ski 12

如果有人寻找递归版本,这里是@ Andrejs解决方案的修改:

def getCCParams(cc: Product): Map[String, Any] = {
  val values = cc.productIterator
  cc.getClass.getDeclaredFields.map {
    _.getName -> (values.next() match {
      case p: Product if p.productArity > 0 => getCCParams(p)
      case x => x
    })
  }.toMap
}
Run Code Online (Sandbox Code Playgroud)

它还将嵌套的case-classes扩展为任何嵌套级别的映射.


Xav*_*hot 6

Starting Scala 2.13case classes(作为的实现Product)提供有productElementNames方法,该方法返回其字段名称上的迭代器。

通过使用productIterator获得的字段值压缩字段名称,我们通常可以获得相关的信息Map

// case class MyClass(param1: String, param2: String)
// val x = MyClass("hello", "world")
(x.productElementNames zip x.productIterator).toMap
// Map[String,Any] = Map("param1" -> "hello", "param2" -> "world")
Run Code Online (Sandbox Code Playgroud)

  • 这就是我一直在寻找的。很好又干净地解决了我的问题。 (2认同)

Sha*_*umo 5

如果您不关心将其作为通用函数,这是一个简单的变体:

case class Person(name:String, age:Int)

def personToMap(person: Person): Map[String, Any] = {
  val fieldNames = person.getClass.getDeclaredFields.map(_.getName)
  val vals = Person.unapply(person).get.productIterator.toSeq
  fieldNames.zip(vals).toMap
}

scala> println(personToMap(Person("Tom", 50)))
res02: scala.collection.immutable.Map[String,Any] = Map(name -> Tom, age -> 50)
Run Code Online (Sandbox Code Playgroud)


Bar*_* BN 5

如果您碰巧使用 Json4s,则可以执行以下操作:

import org.json4s.{Extraction, _}

case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")

Extraction.decompose(x)(DefaultFormats).values.asInstanceOf[Map[String,String]]
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

41337 次

最近记录:

6 年,1 月 前