Сканирование огромного JSON-файла для десериализуемых данных в Scala

Мне нужно иметь возможность обрабатывать большие JSON-файлы, создавая экземпляры объектов из десериализуемых подстрок, пока мы выполняем итерацию в файле.

Например:

Допустим, я могу десериализовать только в следующих случаях:

case class Data(val a: Int, val b: Int, val c: Int)

и ожидаемый формат JSON:

{   "foo": [ {"a": 0, "b": 0, "c": 0 }, {"a": 0, "b": 0, "c": 1 } ], 
    "bar": [ {"a": 1, "b": 0, "c": 0 }, {"a": 1, "b": 0, "c": 1 } ], 
     .... MANY ITEMS .... , 
    "qux": [ {"a": 0, "b": 0, "c": 0 }  }

Что я хотел бы сделать, это:

import com.codahale.jerkson.Json
val dataSeq : Seq[Data] = Json.advanceToValue("foo").stream[Data](fileStream)
// NOTE: this will not compile since I pulled the "advanceToValue" out of thin air.

В заключение, я бы предпочел найти решение, которое включает в себя Jerkson или любые другие библиотеки, которые поставляются с платформой Play, но если другая библиотека Scala обрабатывает этот сценарий с большей легкостью и приличной производительностью: я не против попробовать другую библиотеку, Если есть чистый способ ручного поиска по файлу, а затем с помощью библиотеки Json для продолжения анализа оттуда: я в порядке.

Что я не хочу делать, так это принимать весь файл без потоковой передачи или использования итератора, так как хранение всего файла в памяти за раз было бы слишком дорого.

2 ответа

Решение

Вот нынешний способ решения проблемы:

import collection.immutable.PagedSeq
import util.parsing.input.PagedSeqReader
import com.codahale.jerkson.Json
import collection.mutable

private def fileContent = new PagedSeqReader(PagedSeq.fromFile("/home/me/data.json"))
private val clearAndStop = ']'

private def takeUntil(readerInitial: PagedSeqReader, text: String) : Taken = {
  val str = new StringBuilder()
  var readerFinal = readerInitial

  while(!readerFinal.atEnd && !str.endsWith(text)) {
    str += readerFinal.first
    readerFinal = readerFinal.rest
  }

  if (!str.endsWith(text) || str.contains(clearAndStop))
    Taken(readerFinal, None)
  else
    Taken(readerFinal, Some(str.toString))
}

private def takeUntil(readerInitial: PagedSeqReader, chars: Char*) : Taken = {
  var taken = Taken(readerInitial, None)
  chars.foreach(ch => taken = takeUntil(taken.reader, ch.toString))

  taken
}

def getJsonData() : Seq[Data] = {
  var data = mutable.ListBuffer[Data]()
  var taken = takeUntil(fileContent, "\"foo\"")
  taken = takeUntil(taken.reader, ':', '[')

  var doneFirst = false
  while(taken.text != None) {
    if (!doneFirst)
      doneFirst = true
    else
      taken = takeUntil(taken.reader, ',')

    taken = takeUntil(taken.reader, '}')
    if (taken.text != None) {
      print(taken.text.get)
      places += Json.parse[Data](taken.text.get)
    }
  }

  data
}

case class Taken(reader: PagedSeqReader, text: Option[String])
case class Data(val a: Int, val b: Int, val c: Int)

Конечно, этот код не совсем точно обрабатывает искаженный JSON, и для использования для нескольких ключей верхнего уровня "foo", "bar" и "qux" потребуется заглянуть в будущее (или сопоставить из списка возможных ключей верхнего уровня).), но в целом: я считаю, что это делает работу. Это не так функционально, как хотелось бы, и не очень надежно, но PagedSeqReader определенно не дает этому слишком запутаться.

Я не делал этого с JSON (и я надеюсь, что кто-то придумает для вас готовое решение для вас), но сделал это с помощью XML, и вот способ его обработки.

Это в основном простой процесс Map->Reduce с помощью потокового парсера.

Карта (ваша advanceTo)

Используйте потоковый анализатор, такой как JSON Simple (не тестировался). Когда в обратном вызове вы соответствуете своему "пути", соберите все что угодно ниже, записав это в поток (файл в резервной копии или в памяти, в зависимости от ваших данных). Это будет вашим foo массив в вашем примере. Если ваш картограф достаточно сложен, вы можете собрать несколько путей во время шага карты.

Уменьшить (ваш stream[Data])

Поскольку потоки, которые вы собрали выше, выглядят довольно маленькими, вам, вероятно, не нужно снова отображать / разбивать их, и вы можете анализировать их непосредственно в памяти как объекты / массивы JSON и манипулировать ими (преобразовывать, рекомбинировать и т. Д.).

Другие вопросы по тегам