【问题标题】:Scanning a HUGE JSON file for deserializable data in Scala在 Scala 中扫描一个巨大的 JSON 文件以获取可反序列化的数据
【发布时间】:2012-12-31 04:50:47
【问题描述】:

我需要能够处理大型 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 库从那里继续解析:我很好。

想做的是在不使用流式传输或使用迭代器的情况下摄取整个文件,因为一次将整个文件保存在内存中会非常昂贵。

【问题讨论】:

  • 您会多次提取此文件,还是一次性完成?换句话说,具有预先处理时间但更快地重复查询的解决方案是否有意义?
  • 我只需要读一遍,所以回答你的问题:是的。
  • 这有点不寻常的数据格式,但我猜这是由于处理方式(map/reduce?) - 更常见的是你会得到一个长序列或项目数组,而不是巨大的 JSON 列表对象属性。这是许多现有解决方案无法按原样工作的主要原因。例如,Jackson 通过ObjectMapper.reader().readValues(...) 支持数据绑定迭代器,其中可以迭代数组(或根级序列)的各个值。

标签: json scala stream iterator jerkson


【解决方案1】:

我还没有用 JSON 完成它(我希望有人会为你提供一个交钥匙解决方案)但是用 XML 完成了它,这是一种处理它的方法。

它基本上是一个简单的 Map->Reduce 过程,借助流解析器。

地图(你的advanceTo

使用像JSON Simple 这样的流解析器(未测试)。在回调时,您匹配您的“路径”,通过将其写入流(文件支持或内存中,取决于您的数据)来收集下面的任何内容。在您的示例中,这将是您的 foo 数组。如果您的映射器足够复杂,您可能希望在映射步骤中收集多个路径。

减少(你的stream[Data]

由于您在上面收集的流看起来很小,您可能不需要再次映射/拆分它们,您可以直接在内存中将它们解析为 JSON 对象/数组并对其进行操作(转换、重组等...) .

【讨论】:

  • 有趣的想法,与我现在实际在做的不远:这涉及将 Jerkson 与 util.parsing.input.PagedSeqReader 结合使用。而且您绝对正确,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”,需要向前看(或从可能的顶部列表中匹配) -level 键),但总的来说:我相信这可以完成工作。它没有我想要的那么实用,也不是超级健壮,但 PagedSeqReader 绝对不会让这变得混乱。

【讨论】:

  • 如果它可以工作,很好......但我的代码有 3 个问题:1)太多的变量,同时尝试使用类似Stream.continually(input.read(buffer)).takeWhile(_ != -1).foreach(... 2)它不能正确处理编码:两者JSON 转义和字符编码 3) 它完全特定于您的数据,因此更难维护。你真的应该尝试使用现有的 JSON Stream 解析器,它可以为你解决这 3 个问题。
  • 同意,并且对 Scala 来说相对较新:我实际上并不确定 如何 以不会导致整个文件被吞食的方式使用这些 JSON 流解析器并创建了一个庞大的整体 JSON 表示。您介绍的 Stream.continually() 构造肯定非常酷 - 我将不得不尝试它。但就目前而言,JSON 解析是应用程序的外围设备,因此可能希望将其列出并稍后重新访问。我会密切关注这方面的其他帖子——不过,感谢您的洞察力,BGR。
  • 我接受这个答案只是因为它是我迄今为止最完整的答案。当然,我完全意识到这个解决方案并非没有缺陷,我最重要的收获是我需要研究 Stream.continually(input.read(buffer)) 习语的正确应用。此外,一旦我准备好深入研究流式 JSON 解析,可能会缺少一些额外的功能。
  • @BrunoGrieder:我想试试你提到的现有 JSON Stream 解析器。它在哪里?哪一个?
  • @BrunoGrieder 谢谢,试用 json4s,会告诉你进展如何。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 2016-10-18
  • 2018-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多