可以处理大输入(2 GB)的JSON解析器?

时间:2014-03-04 21:52:18

标签: c++ c json

到目前为止,我已经尝试过(没有成功):

  • QJsonDocument - “文档太大”(看起来最大尺寸是1 << 27个字节的人为上限)

  • Boost.PropertyTree - 占用30 GB RAM然后段错误

  • libjson - 占用一些内存,然后是段错误

接下来我会尝试yajl,但Json.NET处理这个问题没有任何问题,所以我不确定为什么它应该是C ++中的一个大问题。

5 个答案:

答案 0 :(得分:4)

结帐https://github.com/YasserAsmi/jvar。我用大型数据库(SF街道数据或其他东西,大约2GB)进行了测试。这很快。

答案 1 :(得分:0)

好吧,我并不为我的解决方案感到自豪,但我最终使用一些正则表达式将我的数据拆分为顶级键值对(每个只有几MB),然后只解析每一个与Qt的JSON解析器配对并将它们传递给我的原始代码。

Yajl本来就是我需要的东西,但我选择了丑陋的正则表达式黑客,因为:

  1. 将我的逻辑装入Yajl's callback structure会让我的代码重写得足够痛苦,这只是一次性的MapReduce工作,所以代码本身并不重要反正。

  2. 数据集由我控制,并保证始终与我的正则表达式一起使用。

  3. 由于各种原因,在Elastic MapReduce部署中添加依赖项是一个比它应该更大的麻烦(并且静态Qt编译是错误的),所以为了不做更多的工作,我倾向于保持依赖性最小化。

  4. 这仍然有效并且表现良好(包括时间和记忆)。

  5. 请注意,我使用的正则表达式恰好适用于我的数据,因为顶级键(只有顶级键)是整数;我下面的代码是不是一般解决方案,我不建议采用类似于SAX风格解析器的方法,其中上述原因#1和#2不适用。

    另请注意,此解决方案非常粗略(在解析数据的开始和结束的特殊情况之前拆分和操作JSON字符串),因为捕获整个键值对的原始表达式在其中一对中发生故障碰巧超过PCRE's backtracking limit(在这种情况下令人难以置信的是,这甚至是一件事,特别是因为它无法通过QRegularExpression或grep配置)。


    无论如何,这是代码;我深感惭愧:

    QFile file( argv[1] );
    file.open( QIODevice::ReadOnly );
    QTextStream textStream( &file );
    
    QString jsonKey;
    QString jsonString;
    QRegularExpression jsonRegex( "\"-?\\d+\":" );
    
    bool atEnd = false;
    
    
    while( atEnd == false )
    {
        QString regexMatch  = jsonRegex.match
        (
            jsonString.append( textStream.read(1000000) )
        ).captured();
    
        bool isRegexMatched = regexMatch.isEmpty() == false;
    
        if( isRegexMatched == false )
        {
            atEnd = textStream.atEnd();
        }
    
        if( atEnd || (jsonKey.isEmpty() == false && isRegexMatched) )
        {
            QString jsonObjectString;
    
            if( atEnd == false )
            {
                QStringList regexMatchSplit = jsonString.split( regexMatch );
    
                jsonObjectString = regexMatchSplit[0]
                    .prepend( jsonKey )
                    .prepend( LEFT_BRACE )
                ;
    
                jsonObjectString = jsonObjectString
                    .left( jsonObjectString.size() - 1 )
                    .append( RIGHT_BRACE )
                ;
    
                jsonKey    = regexMatch;
                jsonString = regexMatchSplit[1];
            }
            else
            {
                jsonObjectString = jsonString
                    .prepend( jsonKey )
                    .prepend( LEFT_BRACE )
                ;
            }
    
            QJsonObject jsonObject = QJsonDocument::fromJson
            (
                jsonObjectString.toUtf8()
            ).object();
    
            QString key = jsonObject.keys()[0];
    
    
    
            ... process data and store in boost::interprocess::map ...
    
    
        }
        else if( isRegexMatched )
        {
            jsonKey    = regexMatch;
            jsonString = jsonString.split( regexMatch )[1];
        }
    }
    

答案 2 :(得分:0)

我最近完成了(可能还有点测试版)这样的库:

https://github.com/matiu2/json--11

如果您使用json_class ..它会将其全部加载到内存中,这可能不是您想要的。

但是你可以通过编写自己的映射器来按顺序解析它。

包含的映射器,遍历JSON,将输入映射到JSON类:

https://github.com/matiu2/json--11/blob/master/src/mapper.hpp

您可以编写自己的数据,然后将数据流输入其中,以便将整批数据加载到内存中。

作为一个让你入门的例子,这只是以一些随机格式输出json数据,但不会填充任何内存(完全未经测试或编译):

#include "parser.hpp"
#include <fstream>
#include <iterator>
#include <string>

int main(int argc, char **) {

  std::ifstream file("hugeJSONFile.hpp");
  std::istream_iterator<char> input(file);

  auto parser = json::Parser(input);
  using Parser = decltype(parser);
  using std::cout;
  using std::endl;

  switch (parser.getNextType()) {
  case Parser::null:
    parser.readNull();
    cout << "NULL" << endl;
    return;
  case Parser::boolean:
    bool val = parser.readBoolean();
    cout << "Bool: " << val << endl;
  case Parser::array:
    parser.consumeOneValue();
    cout << "Array: ..." << endl;
  case Parser::object:
    parser.consumeOneValue();
    cout << "Map: ..." << endl;
  case Parser::number: {
    double val = parser.readNumber<double>();
    cout << "number: " << val << endl;
  }
  case Parser::string: {
    std::string val = parser.readString();
    cout << "string: " << val << endl;
  }
  case Parser::HIT_END:
  case Parser::ERROR:
  default:
    // Should never get here
    throw std::logic_error("Unexpected error while parsing JSON");
  }
  return 0;
}

附录

最初我曾计划让这个库永远不会复制任何数据。例如。读取一个字符串只是给你一个开始并结束输入中字符串数据的迭代器,但因为我们实际上需要解码字符串,我发现这种方法太不切实际了。

该库自动将JSON中的\ u0000代码转换为标准字符串中的utf8编码。

答案 3 :(得分:0)

处理记录时,您可以格式化json并使用换行作为对象之间的分隔符,然后分别解析每一行,例如:

"records": [
{ "someprop": "value", "someobj": { .....   } ... },
.
.
.

或:

"myobj": {
"someprop": { "someobj": {}, ... },
.
.
.

答案 4 :(得分:0)

我只是在Qt的5.12 JSON支持上遇到了同样的问题。幸运的是,从Qt 5.15(64位)开始读取大型JSON文件(我测试了1GB的文件)就可以正常工作。