到目前为止,我已经使用了 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 对象的容器类。
[
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
}
]
我最初的猜测是内存不足。出于好奇,我尝试将其解析为 JArray,这也导致了相同的异常。
我已经开始深入研究 Json.NET 文档并阅读类似的线程。由于我还没有找到可行的解决方案,所以我决定在这里发布一个问题。
更新:在逐行反序列化时,我得到了同样的错误:“[.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"
}
]
正如您在更新中正确诊断的那样,问题在于 JSON 有一个结束
]
,紧接着是一个开始 [
以开始下一组。这种格式使得 JSON 在作为一个整体时无效,这就是 Json.NET 抛出错误的原因。
幸运的是,这个问题似乎经常出现,以至于 Json.NET 实际上有一个特殊的设置来处理它。如果直接使用
JsonTextReader
读取 JSON,可以将 SupportMultipleContent
标志设置为 true
,然后使用循环单独反序列化每个项目。
这应该允许您以内存有效的方式成功处理非标准 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
Json.NET 支持直接从流反序列化。这是一种反序列化 JSON 的方法,使用
StreamReader
一次读取一个 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);
}
}
}
我在 Python 中对 5 GB 的文件做了类似的事情。我将文件下载到某个临时位置并逐行读取它以形成一个类似于 SAX 工作方式的 JSON 对象。
对于使用 Json.NET 的 C#,您可以下载文件,使用流读取器读取文件,然后将该流传递给 JsonTextReader 并使用
JTokens.ReadFrom(your JSonTextReader object)
将其解析为 JObject。
既然“新”
System.Text.Json
已经推出,这可能仍然与某些人相关。
await using FileStream file = File.OpenRead("files/data.json");
var options = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
// Switch the JsonNode type with one of your own if
// you have a specific type you want to deserialize to.
IAsyncEnumerable<JsonNode?> enumerable = JsonSerializer.DeserializeAsyncEnumerable<JsonNode>(file, options);
await foreach (JsonNode? obj in enumerable) {
var firstname = obj?["firstname"]?.GetValue<string>();
}
如果您对更多内容感兴趣,例如如何解析压缩的 JSON,我写了这篇博文:Parsing 60GB Json Files using Streams in .NET。
这是使用开源库Cinchoo ETL解析大型 JSON 文件的另一种简单方法(在后台使用 JSON.NET 以流方式解析 json)
using (WebClient client = new WebClient())
using (Stream stream = client.OpenRead("*** YOUR JSON FILE URL ***"))
using (StreamReader streamReader = new StreamReader(stream))
using (var r = new ChoJSONReader<MyObject>(streamReader)
)
{
foreach (var rec in r)
Console.WriteLine(rec.Dump());
}
示例小提琴:https://dotnetfiddle.net/i5qJ5R
免责声明:我是这个库的作者