在Ruby中按对象解析巨大的json对象

时间:2015-12-04 17:03:13

标签: ruby-on-rails ruby json parsing stream

问题

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

我想要什么?

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

我迄今为止取得的成就

我检查了两个宝石(yajljson-stream)并使用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)

但是这种方法仍然存在问题,@ parser.on_parse_complete仅在数组关闭后触发(因此在解析完json之后)。但另一方面,如果我用每行一个对象格式化json,它工作正常,函数 object_parsed 被调用两次,每行一次。

Json样本:

[
  {
    "v": {
      "M0": 2
    },
    "dims": {
      "D371665580_86": "M77",
      "D2088848381_86": "M5",
      "D372510617_86": "M42"
    }
  },
  {
    "v": {
      "M0": 2
    },
    "dims": {
      "D371665580_86": "M77",
      "D2088848381_86": "M5",
      "D372510617_86": "M42"
    }
  }
]

1 个答案:

答案 0 :(得分:0)

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

#!/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" } }

这里我们只是逐字节地读取流,只要满足非常接近的大括号,我们就会发出puts。这是有效且防弹的,假设你担心输入是一个由哈希组成的数组。

希望它有所帮助。