如何从字符串中提取值以在 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&name=John&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])
其中name
和surname
可能是None
,只要字符串不包含因此不存在于Map
中的此类属性/值。另请注意,为了传递大小为 2 的数组的过滤,可以使用模式匹配。
【讨论】:
在这种情况下将其设为Map[String, String]
并使用Map.get
会得到相同的结果。 Map[String, Option[String]]
在您需要区分密钥存在的情况但值为None
与密钥不存在的情况的上下文中是必需的。此外,无需更改案例类:val p: Option[Person] = for (n <- m.get("name"); s <- m.get("surname")) yield Person(n, s)
我还将filter
和第二个map
折叠为与数组大小匹配的collect
:text.split("&").map(_.split("=")).collect case Array(k, v) => (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) -> parts(1)) else None text.split("&").map(strToPair(_)).flatten.toMap
查看我的second comment 到 elm 的回答
是的,收集似乎是一种更好的方法。我认为有一种很好的方法可以从一组值创建元组,然后从那里创建一个映射。我想你可以使用 grouped(2) 但如果有没有值的键,那么你会得到一个不均匀的集合。以上是关于如何从字符串中提取值以在 Scala 中创建案例类实例的主要内容,如果未能解决你的问题,请参考以下文章
从 xxx.plist 获取值以在 Target 中构建设置
scala 将元组解包到案例类参数和附加的 zip 两个序列中
如何从 JSON 数组在 DB 中创建表以在 Spring Boot 中创建 REST API