我如何将 JSON 从输入流写入 S3 文件而不将其全部加载到内存中?

How would I write JSON to a S3 file from an input stream without loading it all into memory?

假设我想做一些简单的事情,例如将 CSV 文件转换为 JSON 文件。一种简单的方法是将整个 CSV 文件读入内存,然后使用 JSON.NET.

序列化结果
First,Last,Age
Jane,Doe,45
John,Smith,60

会变成:

[
  {
    "First": "Jane",
    "Last": "Doe",
    "Age: 45
  },
  {
    "First": "John",
    "Last": "Smith",
    "Age: 60
  }
]

但是,如果我的资源有限且数据集非常大,最好从 CSV 文件中一次读取 1000 行并将其写入 JSON 输出文件。然后继续附加到文件,而不必将整个数据集读入内存。

我可以想到一些低级的方法,例如手动 adding/removing 括号和逗号。但我希望有人能提出更优雅的方法。

假设您有两个流,一个 Stream csvStream 用于要读取的 CSV 文件,一个 Stream jsonStream 用于要写入的 JSON 文件,您可以从 CSV 流式传输到 JSON 通过组合微软的 TextFieldParser with Json.NET's JsonTextWriter 像这样:

public static partial class JsonExtensions
{
    const int buffersize = 4096;
    static readonly UTF8Encoding defaultEncoding = new UTF8Encoding(false, true);
    
    public static void CopyCSVToJson(Stream csvStream, Stream jsonStream, Formatting formatting = Formatting.Indented, Encoding encoding = default)
    {
        encoding = encoding ?? defaultEncoding;
        
        using (var textReader = new StreamReader(csvStream, encoding, true, buffersize, true))
        using (var textWriter = new StreamWriter(jsonStream, encoding, buffersize, true))
            CopyCSVToJson(textReader, textWriter, formatting);
    }
    
    public static void CopyCSVToJson(TextReader csvTextReader, TextWriter jsonTextWriter, Formatting formatting = Formatting.Indented)
    {
        using (var parser = new TextFieldParser(csvTextReader) { Delimiters = new[] { "," } })
        {
            if (parser.EndOfData)
                return;
            var headers = parser.ReadFields();
            using (var jsonWriter = new JsonTextWriter(jsonTextWriter) { Formatting = formatting })
            {
                jsonWriter.WriteStartArray();
                while (!parser.EndOfData)
                {
                    var fields = parser.ReadFields();
                    jsonWriter.WriteStartObject();
                    foreach (var (name, value) in headers.Zip(fields))
                    {
                        jsonWriter.WritePropertyName(name);
                        // Check if the value is an integer, a decimal, or a Boolean.
                        // Could add BigInteger if needed
                        if (long.TryParse(value, out var l))
                            jsonWriter.WriteValue(l);
                        else if (decimal.TryParse(value, out var d))
                            jsonWriter.WriteValue(d);
                        else if (bool.TryParse(value, out var b))
                            jsonWriter.WriteValue(b);
                        else
                            jsonWriter.WriteValue(value);
                    }
                    jsonWriter.WriteEndObject();
                }
                jsonWriter.WriteEndArray();
            }
        }
    }
}

然后你可以调用上面的例程,例如如下:

using (var csvStream = File.OpenRead(csvFileName))
using (var jsonStream = File.OpenWrite(jsonFileName))
{
    JsonExtensions.CopyCSVToJson(csvStream, jsonStream);
}
  • TextFieldParser 位于 Microsoft.VisualBasic.Core.dllMicrosoft.VisualBasic.dllMicrosoft.VisualBasic.FileIO 命名空间中。它完全可以从 c# 使用。

  • 复制方法假定第一行对应于 属性 名称(如您的问题所示),并尝试在写入之前将每个单元格解析为整数、小数或布尔值单元格的字符串值。

  • 复制方法假定两个流具有相同的编码。如果需要,您显然可以概括这一点。

演示 fiddle here.