简体   繁体   English

使用 Json.NET 从 WebService 反序列化大型 json

[英]Deserializing large json from WebService using Json.NET

I'm receiving a large JSON string from a WebService and I'm looking for the best memory optimized way of deserializing it with C#.我从 WebService 接收到一个大的 JSON 字符串,我正在寻找最好的 memory 优化方法,使用 C# 对其进行反序列化。

JSON structure: JSON结构:

{
    "d": {
        "results": [
            {
                "metadata": {
                    "id": "",
                    "uri": "",
                    "type": ""
                },
                "ID": "",
                "Value1": "",
                "Value2": "",
                "Value3": ""
            },
            {
                "metadata": {
                    "id": "",
                    "uri": "",
                    "type": ""
                },
                "ID": "",
                "Value1": "",
                "Value2": "",
                "Value3": ""
            },
        ]
    }
}

I want to get all the object inside the "result" array but only one object after another and not like right now the complete list.我想在“结果”数组中获取所有 object 但只有一个 object 一个接一个,而不像现在的完整列表。 I'm already using a StreamReader to avoid loading the complete json string into memory.我已经在使用 StreamReader 来避免将完整的 json 字符串加载到 memory 中。 Is there any option to read only one object, do some processing and then read the next one to avoid "OutOfMemoryExceptions"?是否有任何选项可以只读取一个 object,进行一些处理然后读取下一个以避免“OutOfMemoryExceptions”?

WebResponse response = r.GetResponse();  
using (Stream dataStream = response.GetResponseStream())
{
    var serializer = new JsonSerializer();

    using (var sr = new StreamReader(dataStream))
    using (var jsonTextReader = new JsonTextReader(sr))
    {
        return ((RootObject)serializer.Deserialize<RootObject>(jsonTextReader)).RootObject2.Results;
    }

What you can do is to adopt the basic approach of Issues parsing a 1GB json file using JSON.NET and Deserialize json array stream one item at a time , which is to stream through the JSON and deserialize and yield return each object; What you can do is to adopt the basic approach of Issues parsing a 1GB json file using JSON.NET and Deserialize json array stream one item at a time , which is to stream through the JSON and deserialize and yield return each object; but in addition apply some stateful filtering expression to deserialize only StartObject tokens matching the path d.results[*] .但另外应用一些状态过滤表达式来反序列化仅匹配路径d.results[*]StartObject令牌。

To do this, first define the following interface and extension method:为此,首先定义以下接口和扩展方法:

public interface IJsonReaderFilter
{
    public bool ShouldDeserializeToken(JsonReader reader);
}

public static class JsonExtensions
{
    public static IEnumerable<T> DeserializeSelectedTokens<T>(Stream stream, IJsonReaderFilter filter, JsonSerializerSettings settings = null, bool leaveOpen = false)
    {
        using (var sr = new StreamReader(stream, leaveOpen : leaveOpen))
        using (var reader = new JsonTextReader(sr))
            foreach (var item in DeserializeSelectedTokens<T>(reader, filter, settings))
                yield return item;
    }

    public static IEnumerable<T> DeserializeSelectedTokens<T>(JsonReader reader, IJsonReaderFilter filter, JsonSerializerSettings settings = null)
    {
        var serializer = JsonSerializer.CreateDefault(settings);
        while (reader.Read())
            if (filter.ShouldDeserializeToken(reader))
                yield return serializer.Deserialize<T>(reader);
    }
}

Now, to filter only those items matching the path d.results[*] , define the following filter:现在,要仅过滤与路径d.results[*]匹配的项目,请定义以下过滤器:

class ResultsFilter : IJsonReaderFilter
{
    const string path = "d.results";
    const int pathDepth = 2;
    bool inArray = false;

    public bool ShouldDeserializeToken(JsonReader reader)
    {
        if (!inArray && reader.Depth == pathDepth && reader.TokenType == JsonToken.StartArray && string.Equals(reader.Path, "d.results", StringComparison.OrdinalIgnoreCase))
        {
            inArray = true;
            return false;
        }
        else if (inArray && reader.Depth == pathDepth + 1 && reader.TokenType == JsonToken.StartObject)
            return true;
        else if (inArray && reader.Depth == pathDepth && reader.TokenType == JsonToken.EndArray)
        {
            inArray = false;
            return false;
        }
        else
        {
            return false;
        }
    }
}

Next, create the following data model for each result:接下来,为每个结果创建以下数据 model:

public class Metadata
{
    public string id { get; set; }
    public string uri { get; set; }
    public string type { get; set; }
}

public class Result
{
    public Metadata metadata { get; set; }
    public string ID { get; set; }
    public string Value1 { get; set; }
    public string Value2 { get; set; }
    public string Value3 { get; set; }
}

And now you can deserialize your JSON stream incrementally as follows:现在您可以逐步反序列化您的 JSON stream 如下:

foreach (var result in JsonExtensions.DeserializeSelectedTokens<Result>(dataStream, new ResultsFilter()))
{
    // Process each result in some manner.
    result.Dump();
}

Demo fiddle here .演示小提琴在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM