Ruby on rails 如何使用Oj SAX解析器Saj解析JSON

Ruby on rails 如何使用Oj SAX解析器Saj解析JSON,ruby-on-rails,ruby,json,Ruby On Rails,Ruby,Json,我想解析一个10-20MB的JSON文件,我想最好不要一次解析整个JSON文件,这样会占用大量内存。环顾四周后,似乎Saj或ScHandler的API很适合 唯一的问题是,我不能真正了解如何使用它们,文档也没有让它更清楚。我已经研究并定义了Oj::Saj的一个超级简单的子类,如下所示: class MySaj < Oj::Saj def hash_start(key) p key end end 这会导致我的JSON中的很多键被打印出来。但我仍然不知道如何实际访问属于我正

我想解析一个10-20MB的JSON文件,我想最好不要一次解析整个JSON文件,这样会占用大量内存。环顾四周后,似乎Saj或ScHandler的API很适合

唯一的问题是,我不能真正了解如何使用它们,文档也没有让它更清楚。我已经研究并定义了Oj::Saj的一个超级简单的子类,如下所示:

class MySaj < Oj::Saj
  def hash_start(key)
    p key
  end
end
这会导致我的JSON中的很多键被打印出来。但我仍然不知道如何实际访问属于我正在打印的键的值

我可以以某种方式访问散列本身吗?或者我应该如何访问散列?

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

hash\u start
array\u 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)
您可能会注意到,此输出大致相当于每个令牌一次回调(省略“,”和“:”)。基本上,您必须在回调中构建如何处理单个JSON元素的知识。按照这些思路,您还需要构建回调描述的层次结构。例如,当调用
hash\u start
时,在堆栈上推送一个空哈希;调用
hash\u end
时,弹出哈希或在层次结构中向后移动一级

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

如果您喜欢编写编译器后端,这是适合您的JSON解析解决方案。就我个人而言,我从来都不喜欢使用Sax,但对于大型文档,它可以非常方便地使用资源并具有很高的性能,这取决于处理程序的编写程度。准备好大量的调试和稍微不匹配的状态管理,因为这是SAX风格分析过程中的标准。
但是,您不应该太关注10-20MB JSON,因为它实际上不是很大。我已经用“常规”Oj(
load
dump
)处理了80+MB的JSON,而且没有任何问题。除非您在资源严重受限的机器上运行,否则标准Oj将非常适合您。

Saj是一种流式解析器。这意味着,在实践中,它不知道文件的全部内容并对其进行完整的解析——而是在遇到解析事件时通知您解析事件。您的想法是可靠的:文件越大,如果您希望从中选择,您就越能从这种方式的解析中获益

hash\u start
就是这样一个事件,当Oj看到一个对象的开头时触发(在Ruby land中将成为
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
您将获得以下事件序列:

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\u start(nil)
时,这意味着解析器已经找到了一个顶级对象(第一个大括号)。相反,
hash_end(nil)
表示顶级对象已关闭,其内部已正确解析(即未发现解析错误)


以这种方式解析意味着您必须跟踪嵌套(如果这对您有意义的话)以及以正确的值添加键和值等等。这使它变得烦人和困难,但如果您希望在不将所有内容都提交到内存的情况下从一个大文件中分割出一些内容,那么它是值得的。

您是否尝试过定义一个
add\u value
方法?感谢您提供了非常清晰的答案和建议!这是将在Heroku上运行的东西,因此我没有大量可用内存。似乎这东西已经够痛苦的了,我至少应该努力检查一下一次加载是否可行。谢谢你提供的全面而有用的答案!我发现这两个答案都很有用,但将Michael Gaskill的答案标记为被接受的答案,因为它对我的具体案例(10-20MB 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+"
  }
}
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)