2015-11-25 27 views
0

我试图从字符串字符串中提取值并从中创建(可选)案例类实例。如何从字符串中提取值以在Scala中创建案例类实例

该字符串采用的形式:

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) 

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

  1. 有没有更有效的方法来做到这一点?
  2. 是否有一个更功能的编程为中心的方式来做到这一点?

一些制约因素:

  1. 在文中姓名字段的顺序可以改变。下面的内容也是有效的:

    val text = "surname=Smith&name=John" 
    
  2. 有可能是需要被忽略其他领域:

    val text = "surname=Smith&name=John&age=25" 
    
  3. 解决方案需要满足时提供的文本格式不正确或有没有的必填字段。

  4. 该解决方案无法使用反射或宏。

+0

如果上下文是HTTP查询参数解析,如阿尔瓦罗·卡拉斯科提到的,你最好还是重用HTTP库方法或者如果您不能,则必须考虑参数和字符集的URL编码。如果它处于不同的背景下,榆木的回答非常简洁。而且我不会担心效率,除非字符串有巨大的潜力,而且它被认为是一个瓶颈。 –

回答

0

我会说,做这种事的更习惯的方法是使用Extractors

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

+0

提取器是一个有趣的方式来做到这一点。您如何解释字段顺序颠倒的情况?如果还有其他领域会发生什么?例如。 '''val text =“surname = Smith&name = John&age = 25”'''我在这个问题中增加了这个限制。 – ssanj

+0

@ssanj所以,这取决于你的实现。你应该以他们知道如何解析这些字符串的方式来实现你的提取器。但是如果你想为所有查询参数使用相同的解决方案,可能会非常棘手。 – leshkin

0

关于字符串的解析,从属性值构造一个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的数组的过滤,可以使用模式匹配。

+1

在这种情况下,使它成为'Map [String,String]'并使用'Map.get'给出了相同的结果。在上下文中需要'Map [String,Option [String]]',在这种情况下,如果密钥不存在,需要区分存在密钥的情况,而值是'None'。另外,不需要改变case类:'val p:Option [Person] = for(n < - m.get(“name”); s < - m.get(“surname”))yield Person(n, s)' –

+1

我也会将'filter'和第二个'map'压缩到与数组大小匹配的'collect':'text.split(“&”)。map(_。split(“=” “))。collect {case Array(k,v)=>(k,v)} .toMap' –

+0

由于@ KristianDomagala说你可以使用Map.get并简单地使用Map [String,String]。我也喜欢Kristian使用collect。 – ssanj

2

什么会使其更加高效的是,如果你分析它一路到Map[String,String]在开始(而不是一个Array[String]

如果你碰巧有Apache的HTTP客户端库作为你的依赖的一部分已经(好机会,如果您使用的是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请求,有一个很好的机会,你可能需要对图书馆关键的键和值或其他细节进行网址解码。

我想提取的版本将是矫枉过正,但这里是它会是什么样子:

object PersonFromString { 
    def unapply (s: String): Option[Person] = { ... same as above ... } 
} 
... 
text match { 
    case PersonFromString(person) => ... do something with it... 
    ... 
} 
+0

我将不得不解码数据,但我将使用简单的功能来做到这一点。我没有想过使用库来做到这一点,但也许这是更清洁。嗯。 – ssanj

+0

我想直接去一个Map [String,String],但是我需要的步数远远超过我想出的解决方案:def strToPair(line:String):Option [Tuple2 [String (parts.length == 2)Option(parts(0) - > parts(1))else None } text.split(“=”) text.split (“&”)。map(strToPair(_))。flatten.toMap''' – ssanj

+0

See my [second comment](http://stackoverflow.com/questions/33916159/how-can-i-extract-values-从一个字符串到另一个创建一个case-class-instance-in-scala#comment55616424_33922554)到elm的回答 –

相关问题