在 .NET 中解析大型 JSON 文件

Parsing large JSON file in .NET

到目前为止,我已经使用了 Json.NET 的 "JsonConvert.Deserialize(json)" 方法,效果非常好,老实说,我只需要这个。

我正在开发一个后台(控制台)应用程序,它不断从不同的 URL 下载 JSON 内容,然后将结果反序列化为 .NET 对象列表。

 using (WebClient client = new WebClient())
 {
      string json = client.DownloadString(stringUrl);

      var result = JsonConvert.DeserializeObject<List<Contact>>(json);

 }

上面的简单代码片段可能看起来并不完美,但它确实起到了作用。当文件很大时(15,000 个联系人 - 48 MB 文件),JsonConvert.DeserializeObject 不是解决方案,该行会抛出 JsonReaderException 类型的异常。

下载的 JSON 内容是一个数组,这是示例的样子。 Contact 是反序列化 JSON 对象的容器 class。

[
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  }
]

我最初的猜测是内存不足。出于好奇,我尝试将其解析为 JArray,这也导致了同样的异常。

我已开始深入研究 Json.NET 文档并阅读类似的主题。由于我还没有设法产生一个可行的解决方案,我决定 post 在这里提问。

更新:逐行反序列化时,我遇到了同样的错误:“[.Path”,第 600003 行,位置 1。所以下载了其中两个并在 Notepad++ 中检查了它们。我注意到如果数组长度超过 12,000,则在第 12000 个元素之后,“[”将关闭并开始另一个数组。换句话说,JSON 看起来完全像这样:

[
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  }
]
[
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  },
  {
    "firstname": "sometext",
    "lastname": "sometext"
  }
]

我在 Python 中为 5 GB 的文件大小做了类似的事情。我将文件下载到某个临时位置并逐行读取它以形成一个类似于 SAX 工作方式的 JSON 对象。

对于使用 Json.NET 的 C#,您可以下载文件,使用流 reader 读取文件,然后将该流传递给 JsonTextReader 并使用 JTokens.ReadFrom(your JSonTextReader object) 将其解析为 JObject .

Json.NET 支持直接从流反序列化。这是一种使用 StreamReader 一次读取 JSON 字符串而不是将整个 JSON 字符串加载到内存中来反序列化 JSON 的方法。

using (WebClient client = new WebClient())
{
    using (StreamReader sr = new StreamReader(client.OpenRead(stringUrl)))
    {
        using (JsonReader reader = new JsonTextReader(sr))
        {
            JsonSerializer serializer = new JsonSerializer();

            // read the json from a stream
            // json size doesn't matter because only a small piece is read at a time from the HTTP request
            IList<Contact> result = serializer.Deserialize<List<Contact>>(reader);
        }
    }
}

参考:JSON.NET Performance Tips

正如您在更新中正确诊断的那样,问题是 JSON 有一个收盘 ],紧接着是开盘 [ 以开始下一组。这种格式使得 JSON 整体无效,这就是 Json.NET 抛出错误的原因。

幸运的是,这个问题似乎经常出现,Json.NET 实际上有一个特殊的设置来处理它。如果直接使用JsonTextReader读取JSON,可以将SupportMultipleContent标志设置为true,然后使用循环分别反序列化每个项目[=17] =]

这应该允许您以内存有效的方式成功处理非标准 JSON,无论有多少数组或每个数组中有多少项。

    using (WebClient client = new WebClient())
    using (Stream stream = client.OpenRead(stringUrl))
    using (StreamReader streamReader = new StreamReader(stream))
    using (JsonTextReader reader = new JsonTextReader(streamReader))
    {
        reader.SupportMultipleContent = true;

        var serializer = new JsonSerializer();
        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.StartObject)
            {
                Contact c = serializer.Deserialize<Contact>(reader);
                Console.WriteLine(c.FirstName + " " + c.LastName);
            }
        }
    }

完整演示在这里:https://dotnetfiddle.net/2TQa8p