如何使用Oj SAX解析器Saj解析JSON

时间:2016-06-04 22:07:33

标签: ruby-on-rails ruby json

我想解析一个10-20MB的JSON文件,并认为不要一次解析整个JSON文件并导致大量内存使用。环顾四周之后,似乎Oj的Saj或ScHandler API可能非常合适。

唯一的问题是我无法真正了解如何使用它们,文档并没有让它更清晰。我查看了the example in Saj source code,并定义了一个超级简单的Oj :: Saj子类,如下所示:

class MySaj < Oj::Saj
  def hash_start(key)
    p key
  end
end

像这样使用:

open(URL) do |contents|
  Oj.saj_parse(handler, contents)
end

这导致我的JSON中的许多键被打印出来。但我仍然不知道如何实际访问属于我正在打印的键的值。

我可以以某种方式访问​​哈希本身,或者我该怎么做呢?

2 个答案:

答案 0 :(得分:2)

SAX风格的解析是复杂的。您必须维护解析的状态,并适当地处理每个状态更改。

hash_startarray_start回调,通知您的SAX处理程序Saj找到了哈希的开头,并且发生的下一个回调将在该哈希的上下文中。请注意,哈希可以嵌套,包含(或包含在)数组或简单值中。

这是一个简单的Saj处理程序,它解析一个非常简单的JSON对象:

require 'oj'

class MySaj < ::Oj::Saj
  def initialize()
    @hash_cnt = 0
    @array_cnt = 0
  end

  def hash_start(key)
    @hash_cnt += 1
    puts "Start-Hash[@hash_cnt]: '#{key}'"
  end

  def hash_end(key)
    @hash_cnt -= 1
    puts "End-Hash[@hash_cnt]: '#{key}'"
  end

  def array_start(key)
    @array_cnt += 1
    puts "Start-Array[@array_cnt]: '#{key}'"
  end

  def array_end(key)
    @array_cnt -= 1
    puts "End-Array[@array_cnt]: '#{key}'"
  end

  def add_value(value, key);
    puts "Value: [#{key}] = '#{value}'"
  end

  def error(message, line, column)
    puts "ERRRORRR: #{line}:#{column}: #{message}"
  end
end

json = '[{ "key1": "abc", "key2": 123}, { "test1": "qwerty", "pi": 3.14159 }]'

cnt = MySaj.new()
Oj.saj_parse(cnt, json)

使用Saj进行此基本JSON解析的结果给出了以下结果:

Start-Array[@array_cnt]: ''
Start-Hash[@hash_cnt]: ''
Value: [key1] = 'abc'
Value: [key2] = '123'
End-Hash[@hash_cnt]: ''
Start-Hash[@hash_cnt]: ''
Value: [test1] = 'qwerty'
Value: [pi] = '3.14159'
End-Hash[@hash_cnt]: ''
End-Array[@array_cnt]: ''

您可能会注意到此输出大致相当于每个令牌的一次回调(省略&#39;,&#39;和&#39;:&#39;)。您基本上必须在回调中构建有关如何处理各个JSON元素的知识。沿着这些方向,您还需要构建回调描述的层次结构。例如,当调用hash_start时,在堆栈上推送空哈希;调用hash_end时,弹出哈希值或向后移动层次结构中的一个级别。

例如,您可以在hash_end中使用一个处理程序来检查这是否结束了顶级哈希,如果是,则使用该哈希执行某些操作。请注意,您通常不能对数组执行此操作,因为大量JSON文档中的顶级元素是一个数组,因此您必须确定该数组何时是顶级+ 1级数组。

如果您喜欢编写编译器后端,这是适合您的JSON解析解决方案。就个人而言,我从未喜欢在Sax工作,但对于大型文档,它可以非常有利于资源,并且具有高性能,具体取决于您编写处理程序的能力。准备好进行大量的调试和略微不匹配的状态管理,这与萨克斯式解析的课程相提并论。

但是,你不应该过于担心10-20MB的JSON,因为它实际上并不是很大。我用&#34;常规&#34;处理了80 + MB JSON。 Oj(loaddump)相当多,并没有遇到任何问题。除非您在严重资源受限的计算机上运行,​​否则标准Oj将很适合您。

答案 1 :(得分:2)

Saj是一个流解析器。在实践中,这意味着它不完整地知道文件的内容并整体解析它 - 它会在遇到它时通知您解析事件。你的想法是可靠的:文件越大,如果你想从中进行选择,就会越多地以这种方式解析。

hash_start是一个这样的事件,当Oj看到一个Object的开头(它将成为Ruby域中的Hash)时触发。

以JSON为例:

{
  "student-1": {
    "name": "John Doe",
    "age": 42,
    "knownAliases": ["Blabby Joe", "Stack Underflow"],
    "trainingGrades": {
        "Advanced Zumba Dancing": "A+",
        "Introduction to Twitter Arguments": "C-"
    }
  },
  "student-2": {
    "name": "Rebecca Melecca",
    "age": 26,
    "knownAliases": ["Booger Becca", "Tanktop Terror"],
    "trainingGrades": {
        "Intermediate Groin Kickery": "A+",
        "Advanced Quantum Mechanics": "A+"
  }
}

以下解析器:

class StudentParser < Oj::Saj
  def hash_start(key)
    puts "hash_start(#{key.inspect})"
  end

  def hash_end(key)
    puts "hash_end(#{key.inspect})"
  end

  def array_start(key)
    puts "array_start(#{key.inspect})"
  end

  def array_end(key)
    puts "array_end(#{key.inspect})"
  end

  def add_value(value, key)
    puts "add_value(#{value.inspect}, #{key.inspect})"
  end
end

您将获得以下一系列事件:

hash_start(nil)
hash_start("student-1")
add_value("John Doe", "name")
add_value(42, "age")
array_start("knownAliases")
add_value("Blabby Joe", nil)
add_value("Stack Underflow", nil)
array_end("knownAliases")
hash_start("trainingGrades")
add_value("A+", "Advanced Zumba Dancing")
add_value("C-", "Introduction to Twitter Arguments")
hash_end("trainingGrades")
hash_end("student-1")
hash_start("student-2")
add_value("Rebecca Melecca", "name")
add_value(26, "age")
array_start("knownAliases")
add_value("Booger Becca", nil)
add_value("Tanktop Terror", nil)
array_end("knownAliases")
hash_start("trainingGrades")
add_value("A+", "Intermediate Groin Kickery")
add_value("A+", "Advanced Quantum Mechanics")
hash_end("trainingGrades")
hash_end("student-2")
hash_end(nil)

当你看到hash_start(nil)时,它意味着解析器找到了一个顶级对象(第一个开始大括号)。相反,hash_end(nil)表示顶级对象已被关闭,并且其内部被正确解析(即未找到解析错误)。

以这种方式解析意味着您必须跟踪嵌套(如果这对您有意义),添加正确值的键和值,等等。这会让它变得烦人和困难,但是如果你想在没有将所有内容提交到内存的情况下分割掉一些大文件,这是值得的。