如何从字符串中提取值以在 Scala 中创建案例类实例

Posted

技术标签:

【中文标题】如何从字符串中提取值以在 Scala 中创建案例类实例【英文标题】:How can I extract values from a String to create a case class instance in Scala 【发布时间】:2016-02-28 05:20:19 【问题描述】:

我正在尝试从标记化的字符串中提取值并从中创建一个(可选的)案例类实例。

字符串采用以下形式:

val text = "name=John&surname=Smith"

我有一个接受两个值的 Person 类:

case class Person(name: String, surname: String)

我有一些代码可以进行转换:

def findKeyValue(values: Array[String])(prefix: String): Option[String] = 
     values.find(_.startsWith(prefix)).map(_.substring(prefix.length)) 

val fields: Array[String] = text.split("&")
val personOp = for 
   name <- findKeyValue(fields)("name=")
   surname <- findKeyValue(fields)("surname=")
 yield Person(name, surname)

虽然这会产生我需要的答案,但我想知道:

    有没有更有效的方法来做到这一点? 有没有更以函数式编程为中心的方法来做到这一点?

一些限制:

    文本中姓名和姓氏字段的顺序可以更改。以下也是有效的:

    val text = "surname=Smith&name=John"
    

    可能还有其他需要忽略的字段:

    val text = "surname=Smith&name=John&age=25"
    

    当提供的文本格式错误或没有必填字段时,该解决方案需要满足。

    解决方案不能使用反射或宏。

【问题讨论】:

如果上下文是 HTTP 查询参数解析,正如 Alvaro Carrasco 提到的,你最好重用一个 HTTP 库方法,或者如果你不能,你必须对参数和字符集进行 URL 编码考虑到。如果它在不同的上下文中,榆树的答案非常简洁。而且我不会担心效率,除非字符串有可能很大并且被测量为瓶颈。 【参考方案1】:

我想说,做这些事情更惯用的方法是使用Extractors

考虑这个答案:Read case class object from string in Scala (something like Haskell's "read" typeclass)

【讨论】:

Extractors 是一种有趣的方法。您将如何解释字段顺序颠倒的情况?如果有额外的字段会发生什么?例如。 val text = "surname=Smith&amp;name=John&amp;age=25"我已将此约束添加到问题中。 @ssanj 所以,这取决于你的实现。您应该以他们知道如何解析此类字符串的方式实现您的提取器。但是,如果您希望所有查询参数都使用相同的解决方案,那可能会很棘手。【参考方案2】:

在解析字符串时,例如构造一个Map[String,Option[String]]从属性到值

val m = text.split("&")
            .map(_.split("="))
            .filter(_.size == 2)
            .map (xs => xs.head -> Some(xs.last))
            .toMap

例如获取

Map(surname -> Some(Smith), name -> Some(John))

要获取构造类实例的值(例如,从已经建议的提取器中),请像这样使用getOrElse

m.getOrElse("kjkj",None)
Option[String] = None

m.getOrElse("surname",None)
Option[String] = Some(Smith)

案例类需要重新表述为

case class Person(name: Option[String], surname: Option[String])

其中namesurname 可能是None,只要字符串不包含因此不存在于Map 中的此类属性/值。另请注意,为了传递大小为 2 的数组的过滤,可以使用模式匹配。

【讨论】:

在这种情况下将其设为Map[String, String] 并使用Map.get 会得到相同的结果。 Map[String, Option[String]] 在您需要区分密钥存在的情况但值为None 与密钥不存在的情况的上下文中是必需的。此外,无需更改案例类:val p: Option[Person] = for (n &lt;- m.get("name"); s &lt;- m.get("surname")) yield Person(n, s) 我还将filter 和第二个map 折叠为与数组大小匹配的collecttext.split("&amp;").map(_.split("=")).collect case Array(k, v) =&gt; (k, v) .toMap 正如@KristianDomagala 所说,您可以只使用 Map.get 并简单地使用 Map[String, String]。我也喜欢 Kristian 对 collect 的使用。【参考方案3】:

如果您在开头一直将其解析为Map[String,String](而不是Array[String]),那么它的效率会更高。

如果您碰巧已经将 apache 的 http-client 库作为您的依赖项的一部分(如果您使用的是 Web 框架,那么很有可能),我会使用它:

import org.apache.http.client.utils.URLEncodedUtils
import java.nio.charset.StandardCharsets
import scala.collection.JavaConverters._

val values = URLEncodedUtils.parse(text, StandardCharsets.UTF_8)
  .asScala.map(x => x.getName -> x.getValue).toMap

val personOpt = 
  for 
    name <- values.get("name")
    surname <- values.get("surname")
   yield Person(name, surname)

使用库的原因是假设这来自某种 http 请求,您很有可能需要对库处理的键和值或其他详细信息进行 urldecode。

我认为提取器版本有点矫枉过正,但这就是它的样子:

object PersonFromString 
  def unapply (s: String): Option[Person] =  ... same as above ... 

...
text match 
  case PersonFromString(person) => ... do something with it...
  ...

【讨论】:

我将不得不解码数据,但我将使用简单的函数来做到这一点。我没有想过使用图书馆来做到这一点,但也许这样更干净。嗯。 我想过直接去 Map[String, String] 但我需要的步骤比我想出的解决方案要多:def strToPair(line: String): Option[Tuple2[String, String]] = val parts = line.split("=") if (parts.length == 2) Option(parts(0) -&gt; parts(1)) else None text.split("&amp;").map(strToPair(_)).flatten.toMap 查看我的second comment 到 elm 的回答 是的,收集似乎是一种更好的方法。我认为有一种很好的方法可以从一组值创建元组,然后从那里创建一个映射。我想你可以使用 grouped(2) 但如果有没有值的键,那么你会得到一个不均匀的集合。

以上是关于如何从字符串中提取值以在 Scala 中创建案例类实例的主要内容,如果未能解决你的问题,请参考以下文章

从 xxx.plist 获取值以在 Target 中构建设置

聚合列值以在 python/pyspark 中创建一个新列

scala 将元组解包到案例类参数和附加的 zip 两个序列中

如何从 JSON 数组在 DB 中创建表以在 Spring Boot 中创建 REST API

如何从我的 webpack 2 配置中创建/生成/导出文件以在我的 React 代码中使用?

如何在scala中创建镶木地板?