Десериализовать очень большой json из массива строк, используя system.text. json - PullRequest
2 голосов
/ 07 апреля 2020

Я создаю инструмент миграции с sql на понедельник go, в рамках sql запросов, которые я использую for json auto, что приводит к 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().., но я действительно хочу использовать как можно больше памяти ( У меня 128 ГБ на машине) и я делаю миграцию быстрой с большими кусками данных ...

Ответы [ 2 ]

2 голосов
/ 07 апреля 2020

Можно десериализовать одну полезную нагрузку JSON из списка строк, представляющих куски JSON, путем построения ReadOnlySequence<byte> из списка, а затем построения Utf8JsonReader из последовательности, и, наконец, десериализацию с использованием считывателя через JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions).

Ниже приведена минимальная реализация:

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

Примечания:

  • A ReadOnlySequence<T> создается из связанного списка ReadOnlySequenceSegment<T> объектов - но этот тип является абстрактным и NET Core 3.1, похоже, не включает конкретная публикация c реализации. Я смоделировал реализацию выше для этого от Ahson Khan .

  • JsonSerializer, предназначенного для десериализации из кодированных байтовых последовательностей UTF-8 а не из строк или символьных массивов, поэтому, если вы сможете заставить свой уровень доступа к базе данных возвращать список байтовых массивов UTF-8, а не строк, вы получите более высокую производительность и избежите этапа кодирования каждого фрагмента строки в байты.

    Если это невозможно, и ваш ввод определенно является длинным списком маленьких sh строк (2033 символа), возможно, стоит изучить использование пула памяти или массива для выделения необходимых последовательностей байтов UTF-8.

  • Несмотря на то, что при этом подходе не требуется выделять одну огромную string или byte [], вся полезная нагрузка JSON, тем не менее, загружается в память все сразу как последовательность кусков. Таким образом, это не настоящее потоковое решение.

  • Если вы заинтересованы в истинном потоковом решении и можете получить доступ к вашим JSON данным напрямую как Stream, вы можете посмотреть на этот ответ до Анализ файла JSON с. NET core 3.0 / System.text. Json с mto sh.

Демонстрационная скрипка здесь .

0 голосов
/ 07 апреля 2020

это не ответ, но может привести к извинениям. У меня недавно была похожая проблема с HTTP, и хитрость заключалась не в том, чтобы построить промежуточную строку, как вы определили. Я обнаружил, что если бы я использовал поток вместо этого, я мог бы просто полностью пропустить среднего человека. Kev Dockx проделал определенную работу в этой области и имеет полезный нюгет Marvin.StreamExtensions для обработки Json. Однако вам нужно создать поток из вашего запроса, чтобы он заработал .....

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

Проверьте эти ссылки для решений, основанных на Foreach? Является ли это Dapper, никогда не использовал его, но может быть полезно следующее. Объяснение dapper буфера / кэша

Возможно потоковое воспроизведение большого SQL набора результатов базы данных сервера с использованием Dapper?

Git хаб (генерирует поток из Query), но поскольку вы "bufered: false", почти наверняка вы можете просто использовать его (?): https://github.com/JocaPC/Dapper.Stream

...