обнаружить тип из столбцов CSV в scala - PullRequest
0 голосов
/ 12 марта 2020

Я хочу прочитать универсальный c CSV-файл с заголовками, но с неизвестным номером столбца в типизированную структуру. Мой вопрос примерно такой же, как Строго типизированный доступ к CSV в scala? , но с тем фактом, что у меня не было бы схемы для передачи парсеру ...

До сих пор, Я использовал картографирование Jackson CSV для чтения каждой строки как карты [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 будет двойным но "4.2" все равно будет строкой.

Мы используем play- json повсюду в нашем проекте, и поэтому я знаю, что JsValue уже имеет отличный вывод типа для таких обобщенных c вещей.

Как paly- json он также основан на Джексоне, я думал, что было бы здорово иметь что-то вроде

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 с типизированным полем c будет Хорошо. Есть ли еще одна библиотека, которую я мог бы использовать для этого? Из того, что я обнаружил, все остальные библиотеки основаны на отображении, которое было дано CSV Reader заранее, и для меня важно иметь возможность выводить тип из CSV.

1 Ответ

0 голосов
/ 13 марта 2020

Хорошо, мне лень было хотеть найти что-то работающее из коробки :) На самом деле это было легко реализовать самостоятельно.

Я пошел искать lib на других языках, которые делают это выводом (PapaParse) в JS, Pandas в python) и обнаружили, что они проводят тестирование-повтор с приоритетом для угадывания типов.

Так что я реализовал это сам, и он отлично работает!

Вот оно:

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)
    ) )
  }
...