【问题标题】:discover type from CSV columns in scala从 Scala 中的 CSV 列中发现类型
【发布时间】:2020-03-12 14:41:14
【问题描述】:

我想将带有标题但列号未知的通用 CSV 文件读入类型化结构。我的问题有点像Strongly typed access to csv in scala?,但事实上我没有架构可以传递给解析器......

到目前为止,我一直在使用 Jackson CSV 映射器将每一行作为 Map[String,String] 读取,并且运行良好。

import com.fasterxml.jackson.module.scala.DefaultScalaModule

def genericStringIterator(input: InputStream): Iterator[Map[String, String]] = {

    val mapper = new CsvMapper()

    mapper.registerModule(DefaultScalaModule)

    val schema = CsvSchema.emptySchema.withHeader

    val iterator = mapper
      .readerFor(classOf[Map[String, String]])
      .`with`(schema)
      .readValues[Map[String, String]](input)

    iterator.asScala
  }

现在,我们需要输入字段,因此 4.2 将是一个 Double 但“4.2”仍然是一个字符串。

我们在项目中到处都在使用 play-json,所以我知道 JsValue 已经对类似的通用东西有很好的类型推断。

作为 paly-json,它也是基于 Jackson 的,我认为拥有类似的东西会很棒

import play.api.libs.json.jackson.PlayJsonModule


def genericStringIterator(input: InputStream): Iterator[JsValue] = {

    val mapper = new CsvMapper()

    mapper.registerModule(PlayJsonModule)

    val schema = CsvSchema.emptySchema.withHeader

    val iterator = mapper
      .readerFor(classOf[JsValue])
      .`with`(schema)
      .readValues[JsValue](input)

    iterator.asScala
  }

但是当我尝试以前的代码时,我得到了一个异常:

   val iterator = CSV.genericAnyIterator(input(
      """foo,bar,baz
        |"toto",42,43
        |"tata",,45
        | titi,87,88
        |"tutu",,
        |""".stripMargin))

    iterator
      .foreach { a =>
        println(a)
      }
java.lang.RuntimeException: We should be reading map, something got wrong
    at play.api.libs.json.jackson.JsValueDeserializer.deserialize(JacksonJson.scala:165)
    at play.api.libs.json.jackson.JsValueDeserializer.deserialize(JacksonJson.scala:128)
    at play.api.libs.json.jackson.JsValueDeserializer.deserialize(JacksonJson.scala:123)
    at com.fasterxml.jackson.databind.MappingIterator.nextValue(MappingIterator.java:277)
    at com.fasterxml.jackson.databind.MappingIterator.next(MappingIterator.java:192)
    at scala.collection.convert.Wrappers$JIteratorWrapper.next(Wrappers.scala:40)
    at scala.collection.Iterator.foreach(Iterator.scala:929)
    at scala.collection.Iterator.foreach$(Iterator.scala:929)
    at scala.collection.AbstractIterator.foreach(Iterator.scala:1417)
    at my.company.csv.CSVSpec$$anon$4.<init>(CSVSpec.scala:240)

是不是我做错了什么?

我不关心最后是否有一个 play-json JsValue,任何具有通用类型字段的 Json 结构都可以。我可以使用另一个库吗?对于我的发现,所有其他库都是基于预先提供给 CSV Reader 的映射,对我来说重要的是能够从 CSV 推断类型。

【问题讨论】:

  • 在这种情况下有点矫枉过正,但你可以用 Shapeless 解决这个问题。
  • @sinanspd 我不确定 Shapeless 是否可以提供帮助。 PureCSV 基于 shapeless 但仍需要声明将要读取的类型。

标签: json scala csv jackson play-json


【解决方案1】:

好的,我懒得想找到开箱即用的东西 :) 事实上,我自己很容易实现。

我去寻找可以进行这种推断的其他语言的 lib(JS 中的 PapaParse,python 中的 Pandas),并发现他们正在优先进行测试和重试以猜测类型。

所以我自己实现了,效果很好!

这里是:

def genericAnyIterator(input: InputStream): Iterator[JsValue] = {

    // the result of former code, mapping to Map[String,String]
    val strings = genericStringIterator(input)

    val decimalRegExp: Regex = "(\\d*){1}(\\.\\d*){0,1}".r

    val jsValues: Iterator[Map[String, JsValue]] = strings.map { m =>
      m.mapValues {
        case "" => None
        case "false" | "FALSE" => Some(JsFalse)
        case "true" | "TRUE" => Some(JsTrue)
        case value@decimalRegExp(g1, g2) if !value.isEmpty => Some(JsNumber(value.toDouble))
        case "null" | "NULL" => Some(JsNull)
        case value@_ => Some(JsString(value))
      }
        .filter(_._2.isDefined)
        .mapValues(_.get)
    }
    jsValues.map(map => JsObject(map.toSeq))
  }

在测试中做了什么

 it should "read any csv in JsObject" in new WithInputStream {

    val iterator = CSV.genericAnyIterator(input(
      """foo,bar,baz
        |"toto",NaN,false
        |"tata",,TRUE
        |titi,87.79,88
        |"tutu",,null
        |"tete",5.,.5
        |""".stripMargin))

    val result: Seq[JsValue] = iterator.toSeq

    result should be(Stream(
      Json.obj("foo" -> "toto", "bar" -> "NaN", "baz" -> false)
      , Json.obj("foo" -> "tata",  "baz" -> true)
      , Json.obj("foo" -> "titi", "bar" -> 87.79, "baz" -> 88)
      , Json.obj("foo" -> "tutu",  "baz" -> JsNull)
      , Json.obj("foo" -> "tete", "bar" -> 5, "baz" -> 0.5)
    ) )
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-17
    • 1970-01-01
    • 2015-06-05
    • 1970-01-01
    • 2011-04-09
    • 2014-10-15
    • 1970-01-01
    相关资源
    最近更新 更多