【问题标题】:Way to read (or edit) big JSON from / to stream从流中读取(或编辑)大 JSON 的方式
【发布时间】:2019-05-08 03:23:33
【问题描述】:

(尚未回答 - 至少有 3 个解决方案留在那里,而不是原来的问题。)
我一直在尝试解析和拆分大 JSON,但不想修改内容。
在 FloatParseHandling 发生变化之前,浮点转换会更改数字。

与使用普通 Stream.ReadToEnd -> 耗尽或耗尽空闲 RAM -> 崩溃或“停止”方法的 30 秒/5-7GB 相比,类似的循环可以仅使用 14MB 的 RAM 在 40 秒内拆分我机器上的 1/4GB JSON。

当时也想通过二进制比较来验证结果,但是很多数字都变了。

jsonReader.FloatParseHandling = FloatParseHandling.Decimal;

using Newtonsoft.Json; // intentionally ugly - complete working code

long batchSize = 500000, start = 0, end = 0, pos = 0;
bool neverEnd = true;
while (neverEnd) {
  end = start + batchSize - 1;
  var sr = new StreamReader(File.Open("bigOne.json", FileMode.Open, FileAccess.Read));
  var sw = new StreamWriter(new FileStream(@"PartNo" + start + ".json", FileMode.Create));
  using (JsonWriter writer = new JsonTextWriter(sw))
  using (var jsonR = new JsonTextReader(sr)) {
    jsonR.FloatParseHandling = FloatParseHandling.Decimal;
    while (neverEnd) {
      neverEnd &= jsonR.Read();
      if (jsonR.TokenType == JsonToken.StartObject
       && jsonR.Path.IndexOf("BigArrayPathStart") == 0) { // batters[0] ... batters[3]
        if (pos > end) break;
        if (pos++ < start) {
          do { jsonR.Read(); } while (jsonR.TokenType != JsonToken.EndObject);
          continue;
        }
      }

      if (jsonR.TokenType >= JsonToken.PropertyName){ writer.WriteToken(jsonR); }
      else if (jsonR.TokenType == JsonToken.StartObject) { writer.WriteStartObject(); }
      else if (jsonR.TokenType == JsonToken.StartArray) { writer.WriteStartArray(); }
      else if (jsonR.TokenType == JsonToken.StartConstructor) {
          writer.WriteStartConstructor(jsonR.Value.ToString());
      }
    }
    start = pos; pos = 0;
  }
}

【问题讨论】:

  • 您为什么要这样做?考虑到您正在处理 "big" JSON 数据,more efficient 以这样的方式对您的应用程序进行编码以便您不必必须 "重新读取 [token]" 并遇到潜在的重复字符串操作和/或对 .NET GC 的影响
  • 您可以很好地提出一个好问题,您应该编辑您的问题并:A) 添加一个描述错误行为的示例 json 输入。 B)将相关的解析代码添加到问题本身。 C) 避免在同一篇文章中提出多个不同的问题。
  • for '浮点转换改变数字'。有一个参数可以选择默认的十进制值类型,从浮点数到十进制。但是结果对象中的类型总是很普遍,所以如果你有正确的对象表示,就不应该有错误。 FloatParseHandling 上的 Json.net 文档
  • 很可能该文件已在精度损失的情况下生成。这里的根本问题是浮点数是一个近似值。
  • 这种方法不经常使用或记录,所以有人可能想选择那种速度较慢但内存很小的方法,这就是我将整个代码放在这里的原因(默认情况下更喜欢 32b 你最终会例如@4GB 崩溃)...

标签: c#


【解决方案1】:

Gason 翻译成 C# 可能是现在 C# 语言中最快的解析器,速度类似于 C++ 版本(Debug Build,Release 慢 2 倍),内存消耗大 2 倍: https://github.com/eltomjan/gason

(免责声明:我隶属于 Gason 的这个 C# 分支。)

解析器具有实验性功能 - 在解析最后一个数组中预定义的行数后退出,下一次在下一批的最后一项之后继续:

using Gason;

int endPos = -1;
JsonValue jsn;
Byte[] raw;

String json = @"{""id"":""0001"",""type"":""donut"",""name"":""Cake"",""ppu"":0.55, 
  ""batters"": [ { ""id"": ""1001"", ""type"": ""Regular"" },
                 { ""id"": ""1002"", ""type"": ""Chocolate"" },
                 { ""id"": ""1003"", ""type"": ""Blueberry"" }, 
                 { ""id"": ""1004"", ""type"": ""Devil's Food"" } ]
  }"
raw = Encoding.UTF8.GetBytes(json);
ByteString[] keys = new ByteString[]
{
    new ByteString("batters"),
    null
};
Parser jsonParser = new Parser(true); // FloatAsDecimal (,JSON stack array size=32)
jsonParser.Parse(raw, ref endPos, out jsn, keys, 2, 0, 2); // batters / null path...
ValueWriter wr = new ValueWriter(); // read only 1st 2
using (StreamWriter sw = new StreamWriter(Console.OpenStandardOutput()))
{
    sw.AutoFlush = true;
    wr.DumpValueIterative(sw, jsn, raw);
}
Parser.Parse(raw, ref endPos, out jsn, keys, 2, endPos, 2); // and now following 2
using (StreamWriter sw = new StreamWriter(Console.OpenStandardOutput()))
{
    sw.AutoFlush = true;
    wr.DumpValueIterative(sw, jsn, raw);
}

现在拆分长 JSON 是一个快速而简单的选项 - 整个 1/4GB, 30 秒/5.36GB。如果只解析前 100 行 250MB RAM。
在 Release Build 中,Newton 花费了 >29.3s(>10.8x 更好的性能)的

1st Parse:
{
  "id": "0001",
  "type": "donut",
  "name": "Cake",
  "ppu": 0.55,
  "batters": [
    {
      "id": "1001",
      "type": "Regular"
    },
    {
      "id": "1002",
      "type": "Chocolate"
    }
  ]
}
2nd Parse:
{
  "id": "0001",
  "type": "donut",
  "name": "Cake",
  "ppu": 0.55,
  "batters": [
    {
      "id": "1003",
      "type": "Blueberry"
    },
    {
      "id": "1004",
      "type": "Devil's Food"
    }
  ]
}

【讨论】:

  • 发布一个建议使用工具/库来解决问题的答案是可以的,只要它确实解决了问题。我们要求您在答案本身中展示它如何解决问题,但您已经做到了。但是,if you're affiliated with the product, we require that you include a disclaimer to note your affiliation。那是你最初没能做到的。我已经编辑了这个答案以使其符合我们的要求,并清理了一堆旧的 cmets。请不要回滚这些编辑。
  • 在比较相似的 JSON 时,假设导出 CSV 类型的排序路径和数据可以简单地作为文本进行比较(我之前提到的 GitHub 上的新 C# 文件夹 - ETEhomeTools / CSharp / MultiConvert)。 (免责声明:我是这个文件夹的发明者和作者。)
  • 现在在我翻译的解析器版本中也有排序和删除双胞胎方法,不完美但不占用资源或时间。
猜你喜欢
  • 2018-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-24
相关资源
最近更新 更多