Ruby on rails 在Ruby中逐个对象解析巨大的json对象

Ruby on rails 在Ruby中逐个对象解析巨大的json对象,ruby-on-rails,ruby,json,parsing,stream,Ruby On Rails,Ruby,Json,Parsing,Stream,问题 我有一个json文件,由大量的小json对象组成。现在,如果我尝试用常规方法解析它,将文件读取到内存中,然后调用对其进行的任何json解析(例如json.parse或Oj.parse),它将消耗我所有的系统可用内存,并且不会完成 我想要什么? 通过流解析它的某种方式,每次它完成一个对象时,它都会用该对象回调一个函数。有了这个,我相信内存的使用将是非常低和恒定的 我迄今为止所取得的成就 我检查了两个gem(和),并使用yajl找到了以下解决方案: def post_init @parse

问题

我有一个json文件,由大量的小json对象组成。现在,如果我尝试用常规方法解析它,将文件读取到内存中,然后调用对其进行的任何json解析(例如json.parse或Oj.parse),它将消耗我所有的系统可用内存,并且不会完成

我想要什么?

通过流解析它的某种方式,每次它完成一个对象时,它都会用该对象回调一个函数。有了这个,我相信内存的使用将是非常低和恒定的

我迄今为止所取得的成就

我检查了两个gem(和),并使用yajl找到了以下解决方案:

def post_init
  @parser = Yajl::Parser.new(:symbolize_keys => true)
end

def object_parsed(obj)
  puts "Sometimes one pays most for the things one gets for nothing. - Albert Einstein"
  puts obj.inspect
end

def connection_completed
  # once a full JSON object has been parsed from the stream
  # object_parsed will be called, and passed the constructed object
  @parser.on_parse_complete = method(:object_parsed)
end

# Parse itself
post_init
connection_complete
@parse << File.read("data.json",2048)

我会忘记规则,采用以下方法:

#!/usr/bin/env ruby

require 'stringio' # for tests

input = '[{"menu": {
          "id": "file",
          "value": "File"
          }
  },
  {"menu": {
          "id": "file2",
          "value": "File2"
          }
  }]'

io = StringIO.new input # here a file stream is opened

loop.inject(counter: 0, string: '') do |acc|
  char = io.getc

  break if char.nil? # EOF
  next acc if acc[:counter].zero? && char != '{' # between objects

  acc[:string] << char

  if char == '}' && (acc[:counter] -= 1).zero?
    # ⇓⇓⇓ # CALLBACK, feel free to JSON.parse here
    puts acc[:string].gsub(/\p{Space}+/, ' ') 
    next {counter: 0, string: ''} # from scratch
  end

  acc.tap do |result|
    result[:counter] += 1 if char == '{'
  end
end

#⇒ {"menu": { "id": "file", "value": "File" } }
#  {"menu": { "id": "file2", "value": "File2" } }
#/usr/bin/env ruby
测试需要“stringio”
输入='[{“菜单”:{
“id”:“文件”,
“值”:“文件”
}
},
{“菜单”:{
“id”:“文件2”,
“值”:“文件2”
}
}]'
io=StringIO.new input#此处打开一个文件流
loop.inject(计数器:0,字符串:“”)do | acc|
char=io.getc
如果字符为零,则中断?#EOF
下一个acc如果acc[:计数器]。零?&&char!='对象之间的{'#

acc[:string]我会忘记规则,采用以下方法:

#!/usr/bin/env ruby

require 'stringio' # for tests

input = '[{"menu": {
          "id": "file",
          "value": "File"
          }
  },
  {"menu": {
          "id": "file2",
          "value": "File2"
          }
  }]'

io = StringIO.new input # here a file stream is opened

loop.inject(counter: 0, string: '') do |acc|
  char = io.getc

  break if char.nil? # EOF
  next acc if acc[:counter].zero? && char != '{' # between objects

  acc[:string] << char

  if char == '}' && (acc[:counter] -= 1).zero?
    # ⇓⇓⇓ # CALLBACK, feel free to JSON.parse here
    puts acc[:string].gsub(/\p{Space}+/, ' ') 
    next {counter: 0, string: ''} # from scratch
  end

  acc.tap do |result|
    result[:counter] += 1 if char == '{'
  end
end

#⇒ {"menu": { "id": "file", "value": "File" } }
#  {"menu": { "id": "file2", "value": "File2" } }
!/usr/bin/env ruby
测试需要“stringio”
输入='[{“菜单”:{
“id”:“文件”,
“值”:“文件”
}
},
{“菜单”:{
“id”:“文件2”,
“值”:“文件2”
}
}]'
io=StringIO.new input#此处打开一个文件流
loop.inject(计数器:0,字符串:“”)do | acc|
char=io.getc
如果char.nil,则中断?#EOF
下一个acc如果acc[:counter].zero?&&char!='{'#在对象之间

acc[:字符串]@Jordan您提供的示例有什么问题?由callback决定如何处理恶意数据。您希望任何json解析器都能对这种类型的输入进行smth有意义的处理吗?我代码中的输入不是恶意数据,它是100%有效的json。任何正确的json解析器都能处理它。哦,真的,对不起。附加检查“我们是内部引号”是必需的。然后您还需要检查转义引号。这是JSON解析器不使用正则表达式的原因。我知道JSON解析器不使用正则表达式的原因。好吧,我没有将其称为“防弹”的原因但是对于OP的目标,而不是学术研究,这个解决方案可能是最好和最简单的。顺便说一句,这个方法不使用regexps。AFAIK,所有流解析器都使用相同的方法,因为构建语法/使用访问者在大输入上可能会非常昂贵。@Jordan您提供的示例有什么问题吗?这取决于回调to决定如何处理恶意数据。你认为任何json解析器都会对这种类型的输入进行有意义的smth处理吗?我代码中的输入不是恶意数据,而是100%有效的json。任何正确的json解析器都能处理它。哦,真的,对不起。额外检查“我们在引号内”是必需的。然后您还需要检查转义引号。这是JSON解析器不使用正则表达式的原因。我知道JSON解析器不使用正则表达式的原因。好的,我称之为“防弹”失败但是对于OP的目标,而不是学术研究,这个解决方案可能是最好和最简单的。顺便说一句,这个方法不使用regexps。AFAIK,所有流解析器都使用相同的方法,因为构建语法/使用访问者在大输入上可能非常昂贵。