【问题标题】:How to convert from huge JSON file to xml file in C#如何在 C# 中将巨大的 JSON 文件转换为 xml 文件
【发布时间】:2019-05-22 15:35:21
【问题描述】:

我正在尝试将一个巨大的 JSON 文件 (2GB) 转换为 xml 文件。我在阅读巨大的 JSON 文件时遇到了一些麻烦。

我一直在研究如何读取巨大的 JSON 文件。

我发现了这个:

Out of memory exception while loading large json file from disk

How to parse huge JSON file as stream in Json.NET?

Parsing large json file in .NET

我似乎在重复我的问题,但我有一些问题在这些帖子中没有解决。

所以,我需要加载巨大的 JSON 文件,社区提出这样的建议:

MyObject o;

using (StreamReader sr = new StreamReader("foo.json"))
using (JsonTextReader reader = new JsonTextReader(sr))
{
    var serializer = new JsonSerializer();
    reader.SupportMultipleContent = true;

    while (reader.Read())
    {
        if (reader.TokenType == JsonToken.StartObject)
        {
            // Deserialize each object from the stream individually and process it
            var o = serializer.Deserialize<MyObject>(reader);

            //Do something with the object
        }
    }
}

所以,我们可以逐个读取,逐个反序列化对象。

我会告诉你我的代码

JsonSerializer serializer = new JsonSerializer();

string hugeJson = "hugJSON.json";
using (FileStream s = File.Open(hugeJson , FileMode.Open))
{
    using (StreamReader sr = new StreamReader(s))
    {
         using (JsonReader reader = new JsonTextReader(sr))
         {
            reader.SupportMultipleContent = true;
            while (reader.Read())
            {
                 if (reader.TokenType == JsonToken.StartObject)
                 {
                      var jsonObject = serializer.Deserialize(reader);
                      string xmlString = "";

                       XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonObject.ToString(), "json");

                       using (var stringWriter = new StringWriter())
                       {
                            using (var xmlTextWriter = XmlWriter.Create(stringWriter))
                            {
                                doc.WriteTo(xmlTextWriter);
                                xmlTextWriter.Flush();
                                xmlString = stringWriter.GetStringBuilder().ToString();
                             }
                         }
                  }
              }
          }
     }
}


但是当我尝试doc.WriteTo(xmlTextWriter) 时,我得到Exception of type System.OutOfMemoryException was thrown.

我一直在尝试使用BufferedStream。这个类允许我管理大文件,但我还有另一个问题。

我正在阅读byte[] 格式。当我转换为字符串时,json 被拆分,我无法解析为 xml 文件,因为缺少字符

例如:

{ foo:[{
   foo:something,
   foo1:something,
   foo2:something
},
{
   foo:something,
   foo:som 

它被剪掉了。

有什么方法可以读取巨大的 JSON 并将其转换为 XML 而无需按部分加载 JSON?或者我可以按部分加载转换,但我不知道该怎么做。

有什么想法吗?

更新:

我一直在尝试使用此代码:

 static void Main(string[] args)
 {       
         string json = "";
         string pathJson = "foo.json";
         //Read file
         string temp = "";
         using (FileStream fs = new FileStream(pathJson, FileMode.Open))
         { 
             using (BufferedStream bf = new BufferedStream(fs))
             {
                 byte[] array = new byte[70000];
                 while (bf.Read(array, 0, 70000) != 0)
                 {

                      json = Encoding.UTF8.GetString(array);
                      temp = String.Concat(temp, json);


                 }
             }
         }


        XmlDocument doc = new XmlDocument();

        doc = JsonConvert.DeserializeXmlNode(temp, "json");


         using (var stringWriter = new StringWriter())
         using (var xmlTextWriter = XmlWriter.Create(stringWriter))
         {
             doc.WriteTo(xmlTextWriter);
             xmlTextWriter.Flush();
             xmlString = stringWriter.GetStringBuilder().ToString();
         }


         File.WriteAllText("outputPath", xmlString);


   }

此代码从 json 文件转换为 xml 文件。但是当我尝试转换一个大的 json 文件(2GB)时,我不能。该过程花费大量时间,并且字符串没有存储所有 json 的能力。我怎样才能存储它?有什么方法可以在不使用数据类型字符串的情况下进行这种转换?

更新: json格式为:

[{
    'key':[some things],
    'data': [some things],
    'data1':[A LOT OF ENTRIES],
    'data2':[A LOT OF ENTRIES],
    'data3':[some things],
    'data4':[some things]
}]

【问题讨论】:

  • 尽量避免内存 I/O,例如StringWriter,并将所有块输出到文件流。您可以继续附加到该文件流,而无需为每个块添加一个新文件。如果您可以完全避免反序列化,而是读取令牌和输出元素,这也会有很大帮助
  • 1) xmlString 生成后要做什么?您已经拥有XmlDocument doc 表示,为什么还需要xmlString? 2) 能否请edit 分享一个 JSON 样本?
  • @dbc xmlString 在这段代码中毫无价值。 2) 为什么需要 JSON 样本?我不能使用数据模型。程序必须读取任何大的 JSON。
  • @StenPetrov 有什么方法可以在不使用数据类型字符串的情况下进行转换?
  • 例如,您可能会遇到XmlNodeConverter can only convert JSON that begins with an object 中描述的问题。

标签: c# .net json.net


【解决方案1】:

.Net 中的内存不足异常可能由以下几个问题引起:

  1. 分配了过多的总内存。

    如果发生这种情况,请检查您是否在 64 位模式下运行,如 here 所述。如果没有,请按照here 的说明在 64 位模式下重建并重新测试。

  2. large object heap 上分配太多对象导致内存碎片。

  3. 分配一个大于.Net object size limit单个对象

  4. 未能释放非托管内存(此处不适用)。

在您的情况下,您可能尝试分配过多的总内存,但肯定分配了三个非常大的对象:内存中的temp JSON 字符串,内存中的xmlString XML 字符串和内存中的stringWriter

您可以通过 JSON 文件的流式转换直接构建 XDocumentXmlDocument 来显着减少内存占用并完全消除这些对象。然后,使用XDocument.Save()XmlDocument.Save() 将文档直接写入XML 文件。

为此,您需要分配自己的XmlNodeConverter,然后使用它构造一个JsonSerializer 并反序列化,如Deserialize JSON from a file 所示。以下方法可以解决问题:

public static partial class JsonExtensions
{
    public static XDocument LoadXNode(string pathJson, string deserializeRootElementName)
    {
        using (var stream = File.OpenRead(pathJson))
            return LoadXNode(stream, deserializeRootElementName);
    }

    public static XDocument LoadXNode(Stream stream, string deserializeRootElementName)
    {
        // Let caller dispose the underlying streams.
        using (var textReader = new StreamReader(stream, Encoding.UTF8, true, 1024, true))
            return LoadXNode(textReader, deserializeRootElementName);
    }

    public static XDocument LoadXNode(TextReader textReader, string deserializeRootElementName)
    {
        var settings = new JsonSerializerSettings 
        { 
            Converters = { new XmlNodeConverter { DeserializeRootElementName = deserializeRootElementName } },
        };
        using (var jsonReader = new JsonTextReader(textReader) { CloseInput = false })
            return JsonSerializer.CreateDefault(settings).Deserialize<XDocument>(jsonReader);
    }

    public static void StreamJsonToXml(string pathJson, string pathXml, string deserializeRootElementName, SaveOptions saveOptions = SaveOptions.None)
    {
        var doc = LoadXNode(pathJson, deserializeRootElementName);
        doc.Save(pathXml, saveOptions);
    }
}

然后按如下方式使用它们:

JsonExtensions.StreamJsonToXml(pathJson, outputPath, "json");

我在这里使用XDocument 而不是XmlDocument,因为我相信(但没有亲自检查)它使用更少的内存,例如正如 Ken Lassesen 在 Some hard numbers about XmlDocument, XDocument and XmlReader (x86 versus x64) 中报告的那样。

这种方法消除了前面提到的三个大对象,并大大减少了由于问题 #2 或 #3 而导致内存不足的机会。

演示小提琴here.


如果即使在确保您在 64 位模式下运行并使用上述方法直接在文件之间进行流式传输之后,内存仍然不足,那么可能只是您的 XML 太大而无法使用XDocumentXmlDocument 适合您计算机的虚拟内存空间。如果是这样,您将需要采用纯流式解决方案,在流式传输时将 JSON 动态转换为 XML。不幸的是,Json.NET 没有提供开箱即用的功能,因此您需要一个更复杂的解决方案。

那么,你有什么选择?

  1. 您可以创建自己的XmlNodeConverter.cs 版本并重写ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager) 以直接写入XmlWriter 而不是IXmlDocument

    虽然可能需要几天的努力,但难度似乎超过了单个 stackoverflow 答案的难度。

  2. 您可以使用JsonReaderWriterFactory 返回的阅读器即时将 JSON 转换为 XML,然后将该阅读器直接传递给 XmlWriter.WriteNode(XmlReader)。这个工厂返回的reader和writers在DataContractJsonSerializer内部使用,也可以直接使用。

  3. 如果您的 JSON 具有固定架构(您的问题不清楚),您有更多直接的选择。增量反序列化到一些 c# 数据模型,如 Parsing large json file in .NET 所示,然后将该模型重新序列化为 XML 可能比加载到一些通用 DOM 中使用更少的内存比如XDocument

方案#2可以很简单地实现,如下:

using (var stream = File.OpenRead(pathJson))
using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max))
{
    using (var xmlWriter = XmlWriter.Create(outputPath))
    {
        xmlWriter.WriteNode(jsonReader, true);
    }
}

但是,由此生成的 XML 远不如 XmlNodeConverter 生成的 XML 漂亮。例如,给定简单的输入 JSON

{"Root":[{
    "key":["a"],
    "data": [1, 2]
}]}

XmlNodeConverter 将创建以下 XML:

<json>
  <Root>
    <key>a</key>
    <data>1</data>
    <data>2</data>
  </Root>
</json>

JsonReaderWriterFactory 将创建以下内容(为清楚起见,缩进):

<root type="object">
  <Root type="array">
    <item type="object">
      <key type="array">
        <item type="string">a</item>
      </key>
      <data type="array">
        <item type="number">1</item>
        <item type="number">2</item>
      </data>
    </item>
  </Root>
</root>

生成的 XML 的确切格式可以在 Mapping Between JSON and XML 中找到。

不过,一旦您拥有有效的 XML,就会有流式 XML 到 XML 转换解决方案,可让您将生成的 XML 转换为最终所需的格式,包括:

是否可以换一种方式?

不幸的是

JsonReaderWriterFactory.CreateJsonWriter().WriteNode(xmlReader, true);

实际上并不适合将任意 XML 转换为 JSON,因为它只允许使用Mapping Between JSON and XML 指定的精确模式 转换 XML。

此外,当从任意 XML 转换为 JSON 时,存在数组识别的问题:JSON 有数组,XML 没有,它只有重复元素。要识别重复元素(或同名元素可能不相邻的元素元组)并将它们转换为 JSON 数组,需要缓冲 XML 输入或 JSON 输出(或复杂的两遍算法)。 Mapping Between JSON and XML 通过要求 type="object"type="array" 属性来避免该问题。

【讨论】:

  • 我试过这个。我已经能够转换一些比以前更大的 JSON 文件。但我仍然有内存错误。我不能用 2GB 转换 JSON。该程序在此行中将System.OutOfMemoryException 抛出:return JsonSerializer.CreateDefault(settings).Deserialize&lt;XDocument&gt;(jsonReader);
  • @dbc 感谢您的回答。我试过JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max) 它有效!我可以从 json 文件转换为 xml 文件。所以,我的选择是#2,它已经解决了。
  • @Maverick94 - 我记得 JsonReaderWriterFactory.CreateJsonWriter().WriteNode(xmlReader, true); 不允许将任意 XML 转换为 JSON,它只允许使用 Mapping Between JSON and XML 指定的精确模式转换 XML。
  • @Maverick94 - 当从任意 XML 转换为 JSON 时,数组识别 存在问题:JSON 有数组,XML 没有,它只有重复元素。要识别重复元素并将它们转换为 JSON 数组,需要缓冲整个 XML 输入或整个 JSON 输出(或复杂的两遍算法)。 Mapping Between JSON and XML 通过要求 type="object"type="array" 属性来避免该问题。
猜你喜欢
  • 1970-01-01
  • 2011-02-15
  • 2012-06-17
  • 1970-01-01
  • 2023-03-09
  • 2013-11-05
  • 2014-10-26
  • 2014-11-11
  • 2016-04-12
相关资源
最近更新 更多