使用Json.Net反序列化大于内存容量的流数据

时间:2016-09-11 18:12:12

标签: c# json json.net

我使用以下代码解压缩包含由HttpClient检索的压缩Json提要的本地ZIP文件。

 ProgressStream progressStream = null;
 API_Json_Special_Feeds.RootObject root = null;
 private void import_File(string file)
    {
        isImporting = true;
        Console.WriteLine("Importing " + Path.GetFileName(file));
        using (FileStream read = File.OpenRead(file))
        {
            progressStream = new ProgressStream(read);
            using (GZipStream zip = new GZipStream(progressStream, CompressionMode.Decompress))
            {


                UTF8Encoding temp = new UTF8Encoding(true);
                var serializer = new JsonSerializer();
                StreamReader sr = new StreamReader(zip);
                using (var jsonTextReader = new JsonTextReader(sr))
                {

                    root = serializer.Deserialize<API_Json_Special_Feeds.RootObject>(jsonTextReader);
                    //I'd like to manipulate root between these lines
                    foreach (API_Json_Special_Feeds.Item item in root.items)
                    {
                        Special_Feed_Data.special_Feed_Items.Add(item);
                    }
                }
                progressStream.Dispose();
            }
        }
 }

文件相当大,压缩300-600MB,未压缩9-11GB。如您所见,我插入了一个中间流,以便检查吞吐量。这一切都适用于我的64GB机器,但客户端只有8GB可以使用。尝试在具有8G RAM的机器上解压缩和序列化9-11G并不会很有趣。

我是Json的新手,所以我最初的想法是对数据进行某种过滤或分页,因为它被反序列化,可能与我用来测量流吞吐量的方法相同:< / p>

  private void timer()
    {
        bool isRunning = true;
        while (isRunning)
        {
            if (progressStream != null)
            {
                kBytes_Read = ((double)progressStream.BytesRead / (double)1024);
                mem_Used = get_Memory_Used();
                if (root != null)
                   Console.WriteLine("Root contains " + root.items.Count.ToString() + " items");
                //This doesn't work, because root is null until ALL of the data is deserialized
            }
            Thread.Sleep(450);
        }
    }

在我看来,我看到Json.net一次反序列化一条记录并将其添加到root中的项目列表中。这样做的问题在于&#34; root&#34;求值为null直到流完成 - 我无法找到访问反序列化数据的方法,直到反序列化方法完成。

问题在反序列化仍在进行中时,有没有办法访问已经序列化为Root.Items的数据?如果没有,那么如何停止/分页/暂停大数据的反序列化以使其不会爆炸?

我感谢您的时间,并提前了解您可以提供的任何想法或建议。

1 个答案:

答案 0 :(得分:0)

您必须避免将整个根对象反序列化到内存中。您可以使用相同的JsonTextReader执行此操作,因为它逐个解析json令牌,但您必须进行一些小的手动解析。这是一个例子:

    static void Main(string[] args)
    {
        // our fake huge object
        var json = @"{""root"":{""items"":[{""data"":""value""},{""data"":""value""}]}}";
        using (var reader = new JsonTextReader(new StringReader(json))) {
            bool insideItems = false;
            while (reader.Read()) {
                // reading tokens one by one
                if (reader.TokenType == JsonToken.PropertyName) {
                    // remember, this is just an example, so it's quite crude
                    if ((string) reader.Value == "items") {
                        // we reached property named "items" of some object. We assume this is "items" of our root object
                        insideItems = true;
                    }
                }
                if (reader.TokenType == JsonToken.StartObject && insideItems) {
                    // if we reached start of some json object, and we have already reached "items" property before - we assume
                    // we are inside "items" array
                    // here, deserialize items one by one. This way you will consume almost no memory at any given time
                    var item = JsonSerializer.Create().Deserialize<DataItem>(reader);
                    Console.WriteLine(item.Data);
                }                    
            }
        }
    }

    public class DataItem {
        public string Data { get; set; }
    }
}

请记住,这只是一个例子。在现实生活中,你需要做更仔细的手动解析(检查“items”属性是否确实是你的根对象,检查它是否是一个数组等等),但总的想法是一样的。