一尘不染

如何在Json.NET中将巨大的JSON文件解析为流?

c#

我有一个非常大的相同JSON对象的JSON文件(1000+ MB)。例如:

[
    {
        "id": 1,
        "value": "hello",
        "another_value": "world",
        "value_obj": {
            "name": "obj1"
        },
        "value_list": [
            1,
            2,
            3
        ]
    },
    {
        "id": 2,
        "value": "foo",
        "another_value": "bar",
        "value_obj": {
            "name": "obj2"
        },
        "value_list": [
            4,
            5,
            6
        ]
    },
    {
        "id": 3,
        "value": "a",
        "another_value": "b",
        "value_obj": {
            "name": "obj3"
        },
        "value_list": [
            7,
            8,
            9
        ]

    },
    ...
]

JSON根列表中的每个项目都采用相同的结构,因此可以单独进行反序列化。我已经编写了C#类来接收此数据,并且反序列化包含单个对象但不包含列表的JSON文件可以按预期工作。

最初,我试图直接在循环中反序列化我的对象:

JsonSerializer serializer = new JsonSerializer();
MyObject o;
using (FileStream s = File.Open("bigfile.json", FileMode.Open))
using (StreamReader sr = new StreamReader(s))
using (JsonReader reader = new JsonTextReader(sr))
{
    while (!sr.EndOfStream)
    {
        o = serializer.Deserialize<MyObject>(reader);
    }
}

这没有用,抛出了一个异常,清楚地表明一个对象是期望的,而不是列表。我的理解是,此命令将只读取JSON文件根级别中包含的单个对象,但是由于我们有对象 列表
,因此这是无效的请求。

我的下一个想法是反序列化为对象的C#列表:

JsonSerializer serializer = new JsonSerializer();
List<MyObject> o;
using (FileStream s = File.Open("bigfile.json", FileMode.Open))
using (StreamReader sr = new StreamReader(s))
using (JsonReader reader = new JsonTextReader(sr))
{
    while (!sr.EndOfStream)
    {
        o = serializer.Deserialize<List<MyObject>>(reader);
    }
}

这确实成功了。但是,它仅在某种程度上减少了高RAM使用率的问题。在这种情况下,看起来应用程序一次要对一个序列进行反序列化,因此不会将整个JSON文件读入RAM,但是由于C#List对象现在包含所有RAM中JSON文件中的数据。这仅解决了问题。

然后,我决定简单地尝试进入循环之前,从流的开头删除单个字符(以消除[sr.Read()。然后,第一个对象确实读取成功,但随后的对象读取成功,但“意外令牌”除外。我的猜测是,这是对象之间的逗号和空格,使读者无法使用。

如示例中所示,仅删除方括号是行不通的,因为对象确实包含其自己的原始列表。},就像您看到的那样,即使尝试用作分隔符也行不通,因为对象中包含子对象。

我的目标是能够一次从流中读取对象。读取一个对象,对其进行处理,然后将其从RAM中丢弃,然后读取下一个对象,依此类推。这样就无需将整个JSON字符串或数据的全部内容作为C#对象加载到RAM中。

我想念什么?


阅读 281

收藏
2020-05-19

共1个答案

一尘不染

这应该可以解决您的问题。基本上,它的工作方式与您的初始代码相同,不同之处{在于,它仅在读者点击流中的字符时反序列化对象,否则它会跳至下一个对象,直到找到另一个起始对象标记为止。

JsonSerializer serializer = new JsonSerializer();
MyObject o;
using (FileStream s = File.Open("bigfile.json", FileMode.Open))
using (StreamReader sr = new StreamReader(s))
using (JsonReader reader = new JsonTextReader(sr))
{
    while (reader.Read())
    {
        // deserialize only when there's "{" character in the stream
        if (reader.TokenType == JsonToken.StartObject)
        {
            o = serializer.Deserialize<MyObject>(reader);
        }
    }
}
2020-05-19