在Scala中扫描一个巨大的JSON文件以获取可反序列化的数据

时间:2013-01-16 18:59:32

标签: json scala stream iterator jerkson

我需要能够处理大型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 个答案:

答案 0 :(得分:2)

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

在流解析器的帮助下,它基本上是一个简单的Map-> Reduce进程。

地图(您的advanceTo

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

减少(您的stream[Data]

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

答案 1 :(得分:1)

以下是我解决问题的最新方式:

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肯定会让这个凌乱。