【问题标题】:Deserialize json array stream one item at a time一次反序列化json数组流一项
【发布时间】:2013-12-20 20:43:51
【问题描述】:

我将一个大对象数组序列化为一个 json http 响应流。现在我想一次一个地从流中反序列化这些对象。是否有任何 c# 库可以让我这样做?我查看了 json.net,但似乎我必须一次反序列化完整的对象数组。

[{large json object},{large json object}.....]

澄清:我想一次从流中读取一个json对象并反序列化它。

【问题讨论】:

  • 你究竟为什么想要这样的行为?
  • 因为我不想将整个大对象数组保留在内存中。
  • 您可能需要使用 Json.NET 中的 JsonTextReader 并手动读取令牌。

标签: c# json serialization stream json.net


【解决方案1】:

为了逐步读取 JSON,您需要将 JsonTextReaderStreamReader 结合使用。但是,您不一定必须从阅读器手动读取所有 JSON。您应该能够利用 Linq-To-JSON API 从阅读器中加载每个大对象,以便更轻松地使用它。

举个简单的例子,假设我有一个如下所示的 JSON 文件:

[
  {
    "name": "foo",
    "id": 1
  },
  {
    "name": "bar",
    "id": 2
  },
  {
    "name": "baz",
    "id": 3
  }
]

从文件中以增量方式读取它的代码可能如下所示。 (在您的情况下,您可以用您的响应流替换 FileStream。)

using (FileStream fs = new FileStream(@"C:\temp\data.json", FileMode.Open, FileAccess.Read))
using (StreamReader sr = new StreamReader(fs))
using (JsonTextReader reader = new JsonTextReader(sr))
{
    while (reader.Read())
    {
        if (reader.TokenType == JsonToken.StartObject)
        {
            // Load each object from the stream and do something with it
            JObject obj = JObject.Load(reader);
            Console.WriteLine(obj["id"] + " - " + obj["name"]);
        }
    }
}

上面的输出如下所示:

1 - foo
2 - bar
3 - baz

【讨论】:

  • 这与我想出的解决方案基本相同。除了我做一个新的 JsonSerializer().Deserialize(reader);而不是 JObject.Load。不过,我不确定 JsonTextReader 是如何管理流数据的。
  • 您可以随时查看source code 来找出答案。
  • 这让我朝着正确的方向前进,但我不得不这样做 JsonSerializer.Create().Deserialize<T>(reader, desiredType);,因为 JObject.Load 从来没有工作过(也没有抛出错误——这是最奇怪的)。
  • 请记住,与 serializer.Deserialize 相比,使用 JObject.Load 的性能不高。所以总是使用反序列化
【解决方案2】:

我已经简化了我的解析器/反序列化器的样本/测试之一,以更直接地回答这个问题的用例。

这是测试数据:

https://github.com/ysharplanguage/FastJsonParser/tree/master/JsonTest/TestData

(参见fathers.json.txt)

下面是示例代码:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    // Our stuff
    using System.Text.Json;

//...

    public class FathersData
    {
        public Father[] fathers { get; set; }
    }

    public class Someone
    {
        public string name { get; set; }
    }

    public class Father : Someone
    {
        public int id { get; set; }
        public bool married { get; set; }
        // Lists...
        public List<Son> sons { get; set; }
        // ... or arrays for collections, that's fine:
        public Daughter[] daughters { get; set; }
    }

    public class Child : Someone
    {
        public int age { get; set; }
    }

    public class Son : Child
    {
    }

    public class Daughter : Child
    {
        public string maidenName { get; set; }
    }

//...

    static void FilteredFatherStreamTestSimplified()
    {
        // Get our parser:
        var parser = new JsonParser();

        // (Note this will be invoked thanks to the "filters" dictionary below)
        Func<object, object> filteredFatherStreamCallback = obj =>
        {
            Father father = (obj as Father);
            // Output only the individual fathers that the filters decided to keep (i.e., when obj.Type equals typeof(Father)),
            // but don't output (even once) the resulting array (i.e., when obj.Type equals typeof(Father[])):
            if (father != null)
            {
                Console.WriteLine("\t\tId : {0}\t\tName : {1}", father.id, father.name);
            }
            // Do not project the filtered data in any specific way otherwise,
            // just return it deserialized as-is:
            return obj;
        };

        // Prepare our filter, and thus:
        // 1) we want only the last five (5) fathers (array index in the resulting "Father[]" >= 29,995),
        // (assuming we somehow have prior knowledge that the total count is 30,000)
        // and for each of them,
        // 2) we're interested in deserializing them with only their "id" and "name" properties
        var filters = 
            new Dictionary<Type, Func<Type, object, object, int, Func<object, object>>>
            {
                // We don't care about anything but these 2 properties:
                {
                    typeof(Father), // Note the type
                    (type, obj, key, index) =>
                        ((key as string) == "id" || (key as string) == "name") ?
                        filteredFatherStreamCallback :
                        JsonParser.Skip
                },
                // We want to pick only the last 5 fathers from the source:
                {
                    typeof(Father[]), // Note the type
                    (type, obj, key, index) =>
                        (index >= 29995) ?
                        filteredFatherStreamCallback :
                        JsonParser.Skip
                }
            };

        // Read, parse, and deserialize fathers.json.txt in a streamed fashion,
        // and using the above filters, along with the callback we've set up:
        using (var reader = new System.IO.StreamReader(FATHERS_TEST_FILE_PATH))
        {
            FathersData data = parser.Parse<FathersData>(reader, filters);

            System.Diagnostics.Debug.Assert
            (
                (data != null) &&
                (data.fathers != null) &&
                (data.fathers.Length == 5)
            );
            foreach (var i in Enumerable.Range(29995, 5))
                System.Diagnostics.Debug.Assert
                (
                    (data.fathers[i - 29995].id == i) &&
                    !String.IsNullOrEmpty(data.fathers[i - 29995].name)
                );
        }
        Console.ReadKey();
    }

其余位可在此处获得:

https://github.com/ysharplanguage/FastJsonParser

'HTH,

【讨论】:

    【解决方案3】:

    这是我的解决方案(结合不同来源,但主要基于Brian Rogers 解决方案)将巨大的 JSON 文件(对象数组)转换为任何通用对象的 XML 文件。

    JSON 看起来像这样:

       {
          "Order": [
              { order object 1},
              { order object 2},
              {...}
              { order object 10000},
          ]
       }
    

    输出 XML:

    <Order>...</Order>
    <Order>...</Order>
    <Order>...</Order>
    

    C#代码:

    XmlWriterSettings xws = new XmlWriterSettings { OmitXmlDeclaration = true };
    using (StreamWriter sw = new StreamWriter(xmlFile))
    using (FileStream fs = new FileStream(jsonFile, FileMode.Open, FileAccess.Read))
    using (StreamReader sr = new StreamReader(fs))
    using (JsonTextReader reader = new JsonTextReader(sr))
    {
        //sw.Write("<root>");
        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.StartArray)
            {
                while (reader.Read())
                {
                    if (reader.TokenType == JsonToken.StartObject)
                    {
                        JObject obj = JObject.Load(reader);
                        XmlDocument doc = JsonConvert.DeserializeXmlNode(obj.ToString(), "Order");
                        sw.Write(doc.InnerXml); // a line of XML code <Order>...</Order>
                        sw.Write("\n");
                        //this approach produces not strictly valid XML document
                        //add root element at the beginning and at the end to make it valid XML                                
                    }
                }
            }
        }
        //sw.Write("</root>");
    }
    

    【讨论】:

      【解决方案4】:

      使用Cinchoo ETL - 一个开源库,您可以高效地解析大型 JSON,并且内存占用少。由于对象是在基于流的拉模型中构造和返回的

      using (var p = new ChoJSONReader(** YOUR JSON FILE **))
      {
                  foreach (var rec in p)
                  {
                      Console.WriteLine($"Name: {rec.name}, Id: {rec.id}");
                  }
      }
      

      如需了解更多信息,请访问 codeproject 文章。

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多