使用 system.text.json 从分块字符串数组反序列化非常大的 json

Deserialize very large json from a chunked array of strings using system.text.json

我正在创建一个从 sql 到 mongo 的迁移工具,作为我正在使用 for json auto 的 sql 查询的一部分,这会导致 json 响应表单 sql 服务器。 使用 dapper 看起来像这样...

var jsonResults = _SqlDb.Query<string>(usersWithDynamicDataQuery, buffered:false, commandTimeout: _SqlCommandTimeoutSeconds);
var jsonResult = string.Concat(jsonResults);                
var userDocuments = JsonSerializer.Deserialize<List<UserData>>(jsonResult);

所以 sql 正在返回一个包含完整 json 响应块的列表 我需要找到更多的 "Memory Flexible" 方法,而不仅仅是 string.concat(..),因为我正在达到字符串内存分配的 CLR 限制:\

我总是可以使用 ROW_NUMBER().. 限制查询和分页,但我真的想在这里尽可能多地利用内存(我的机器上有 128GB)并进行迁移 swift 有大块数据...

这不是答案,但可能会导致道歉。我最近有一个类似的问题,与 HTTP 一样,诀窍不是构建中间字符串,正如您所确定的那样。我发现如果我改用流,我可能会完全错过中间人。 Kev Dockx 在这方面做了一些工作,并有一个有用的 nuget 称为 Marvin.StreamExtensions 用于处理 Json。但是,您需要从查询中生成流才能使其正常工作.....

var userDocuments = stream.ReadAndDeserializeFromJson<List<UserData>>();

查看这些链接以了解基于 Foreach 的解决方案?这是 Dapper 吗,从未使用过,但以下内容可能会有所帮助。 Explanation of dapper buffer/cache

It is possible to stream a large SQL Server database result set using Dapper?

Git hub stuff(从查询中生成一个流)但是你 "bufered : false" 很确定你可以 foreach 它(?): https://github.com/JocaPC/Dapper.Stream

可以通过构造 ReadOnlySequence<byte> from the list, then constructing a Utf8JsonReader from the sequence, and finally deserializing using the reader via JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions).

从表示分块 JSON 的字符串列表中反序列化单个 JSON 有效载荷

以下是一个最小的实现:

public static partial class JsonExtensions
{
    public static TValue Deserialize<TValue>(IEnumerable<string> buffers, JsonSerializerOptions options = null)
    {
        return Deserialize<TValue>(ToByteArrayChunks(buffers));
    }

    public static TValue Deserialize<TValue>(IEnumerable<byte []> buffers, JsonSerializerOptions options = null)
    {
        var sequence = ReadOnlySequenceFactory.Create(buffers);
        var reader = new Utf8JsonReader(sequence, options.GetReaderOptions());                      
        return JsonSerializer.Deserialize<TValue>(ref reader, options);
    }

    public static JsonReaderOptions GetReaderOptions(this JsonSerializerOptions options)
    {
        if (options == null)
            return new JsonReaderOptions();
        else
            return new JsonReaderOptions
            {
                AllowTrailingCommas = options.AllowTrailingCommas,
                CommentHandling = options.ReadCommentHandling,
                MaxDepth = options.MaxDepth
            };          
    }

    static readonly Encoding encoding = new UTF8Encoding(false);

    static IEnumerable<byte []> ToByteArrayChunks(IEnumerable<string> buffers)
    {
        // By using an encoder we can handle the situation in which surrogate pairs enbedded in JSON string literals
        // are split between chunks.
        var encoder = encoding.GetEncoder();
        foreach (var s in buffers)
        {
            ReadOnlySpan<char> charSpan = s;
            var count = encoder.GetByteCount(charSpan, false);
            var bytes = new byte[count];
            Span<byte> byteSpan = bytes;
            encoder.GetBytes(charSpan, byteSpan, false);
            yield return bytes;
        }
    }
}

public static class ReadOnlySequenceFactory
{
    // There is no public concrete implementation of ReadOnlySequenceSegment<T> so we must create one ourselves.
    // This is modeled on https://github.com/dotnet/runtime/blob/master/src/libraries/System.Text.Json/tests/BufferFactory.cs
    // by https://github.com/ahsonkhan
    class ReadOnlyMemorySegment<T> : ReadOnlySequenceSegment<T>
    {
        public static ReadOnlySequence<T> Create(IEnumerable<ReadOnlyMemory<T>> buffers)
        {
            ReadOnlyMemorySegment<T> first = null;
            ReadOnlyMemorySegment<T> current = null;
            foreach (var buffer in buffers)
            {
                var next = new ReadOnlyMemorySegment<T> { Memory = buffer };
                if (first == null)
                {
                    first = next;
                }
                else
                {
                    current.Next = next;
                    next.RunningIndex = current.RunningIndex + current.Memory.Length;
                }
                current = next;
            }
            if (first == null)
            {
                first = current = new ReadOnlyMemorySegment<T>();
            }

            return new ReadOnlySequence<T>(first, 0, current, current.Memory.Length);
        }
    }

    public static ReadOnlySequence<T> Create<T>(IEnumerable<T []> buffers)
    {
        return ReadOnlyMemorySegment<T>.Create(buffers.Select(b => new ReadOnlyMemory<T>(b)));
    }
}

备注:

  • 一个ReadOnlySequence<T>是从ReadOnlySequenceSegment<T> objects -- but this type is abstract and .NET Core 3.1 doesn't seem to include concrete public implementation. I modeled the implementation above on this one by Ahson Khan.

  • 的链表构造的
  • JsonSerializer 旨在从 UTF-8 编码的字节序列而不是从字符串或字符数组反序列化,因此如果您可以使数据库访问层 return 成为一个列表UTF-8 字节数组而不是字符串,您将获得更好的性能并避免将每个字符串块编码为字节的步骤。

    如果这不可能,并且您的输入肯定是一长串较小的字符串(2033 个字符),则可能值得研究使用内存或数组池来分配必要的 UTF-8 字节序列。

  • 虽然这种方法避免了分配单个巨大的 stringbyte [],但整个 JSON 有效负载仍然作为序列一次全部加载到内存中块。因此,这不是真正的流媒体解决方案。

  • 如果您对真正的流媒体解决方案感兴趣,并且可以直接作为 Stream 访问您的 JSON 数据,您可以查看 to by mtosh

演示 fiddle here.