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

Deserializing large json from WebService using Json.NET

我从 WebService 收到一个大的 JSON 字符串,我正在寻找使用 C# 反序列化它的最佳内存优化方式。

JSON结构:

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

我想获得“结果”数组中的所有对象,但只是一个接一个对象,而不是现在的完整列表。 我已经在使用 StreamReader 来避免将完整的 json 字符串加载到内存中。 是否有任何选项可以只读取一个对象,进行一些处理然后读取下一个对象以避免“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;
    }

你可以做的是采用 and Deserialize json array stream one item at a time的基本方法,即流过JSON并反序列化并产生return 每个对象;但另外应用一些有状态的过滤表达式来反序列化仅匹配路径 d.results[*].

StartObject 个标记

为此,首先定义如下接口和扩展方法:

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);
    }
}

现在,要仅过滤与路径 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;
        }
    }
}

接下来,为每个结果创建以下数据模型:

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; }
}

现在您可以按如下方式逐步反序列化您的 JSON 流:

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

演示 fiddle here.