Сканирование ОГРОМНОГО файла 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 для продолжения синтаксического анализа оттуда: меня это устраивает.

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


person Ryan Delucchi    schedule 16.01.2013    source источник
comment
Будете ли вы извлекать этот файл несколько раз или это одноразовая работа? Другими словами, имеет ли смысл решение с опережающей обработкой, но более быстрым повторным запросом?   -  person Chris Pitman    schedule 18.01.2013
comment
Мне нужно было бы прочитать его только один раз, поэтому, чтобы ответить на ваш вопрос: да.   -  person Ryan Delucchi    schedule 18.01.2013
comment
Это немного необычный формат данных, но я думаю, это связано со стилем обработки (map/reduce?) — чаще всего вы получаете длинную последовательность или массив элементов, а не огромный список свойств объекта JSON. Это основная причина, по которой многие существующие решения не будут работать как есть. Джексон, например, поддерживает итераторы привязки данных через ObjectMapper.reader().readValues(...), где можно перебирать отдельные значения массива (или последовательности корневого уровня).   -  person StaxMan    schedule 09.02.2013


Ответы (2)


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

По сути, это простой процесс Map->Reduce с помощью парсера потока.

Карта (ваш advanceTo)

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

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

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

person Bruno Grieder    schedule 17.01.2013
comment
Интересная мысль, и недалеко от того, чем я сейчас занимаюсь: использование Jerkson в сочетании с util.parsing.input.PagedSeqReader. И вы абсолютно правы в том, что каждый конечный узел данных JSON довольно мал, поэтому мне нужно искать только начало, а затем конец каждого фрагмента. Как только я разработаю свое решение, я опубликую его. Тем временем, любой, у кого есть более элегантный подход - я хотел бы услышать от вас. - person Ryan Delucchi; 17.01.2013

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

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 определенно не дает ему стать слишком беспорядочным.

person Ryan Delucchi    schedule 17.01.2013
comment
Если это работает, хорошо... но у меня есть 3 проблемы с вашим кодом: 1) слишком много vars и while, попробуйте использовать что-то вроде Stream.continually(input.read(buffer)).takeWhile(_ != -1).foreach(... 2) он не обрабатывает кодировки должным образом: и экранирование JSON, и кодировка символов 3) это полностью специфичны для ваших данных и, следовательно, их сложнее поддерживать. Вы действительно должны попробовать использовать существующий парсер JSON Stream, который в основном решит эти 3 проблемы для вас. - person Bruno Grieder; 18.01.2013
comment
Согласен, и я относительно новичок в Scala: я на самом деле не совсем уверен, как использовать эти парсеры JSON Stream таким образом, чтобы это не привело к тому, что весь файл был проглочен, а массивное монолитное представление JSON созданный. Представленная вами конструкция Stream.continually(), безусловно, довольно крутая - мне придется ее попробовать. Но на данный момент синтаксический анализ JSON является второстепенным для приложения, поэтому, вероятно, он захочет занести это в таблицу и вернуться к нему позже. Я буду следить за другими сообщениями на эту тему, но, тем не менее, спасибо за ваше понимание, BGR. - person Ryan Delucchi; 18.01.2013
comment
Я принимаю этот ответ только потому, что это самый полный ответ, который у меня есть на данный момент. Конечно, я полностью осознаю, что это решение не лишено недостатков, и мой самый важный вывод заключается в том, что мне нужно будет изучить правильное применение идиомы Stream.continually(input.read(buffer)) . Более того, когда я буду готов углубиться в анализ потокового JSON, могут появиться некоторые дополнительные возможности, которые я упускаю. - person Ryan Delucchi; 05.02.2013
comment
@BrunoGrieder: я хотел бы попробовать существующий синтаксический анализатор JSON Stream, который вы упомянули. Где это находится? Который из? - person gknauth; 15.01.2016
comment
@BrunoGrieder Спасибо, попробуем json4s, сообщим, как дела. - person gknauth; 15.01.2016