Как реализовать обработку больших JSON файлов, которые не помещаются в память
Что проверяют
Знаете ли вы, что File.ReadAllText плюс JsonSerializer.Deserialize держит в памяти весь файл и всё дерево объектов, и умеете ли читать JSON потоком. Следом спрашивают, чем Utf8JsonReader отличается от JsonSerializer и как быть, если нужные данные лежат глубоко внутри документа.
Как отвечать
Первым делом уточните форму файла: один огромный массив объектов, JSON Lines (объект на строку) или объект со вложенным массивом. От этого зависит инструмент.
JsonSerializer.DeserializeAsyncEnumerable<T>: для корневого массива это самый простой путь вSystem.Text.Json. ОткрываетеFileStream, пишетеawait foreachи получаете элементы по одному, в памяти только текущий объект и буфер чтения. Для JSON Lines в новых версиях .NET есть перегрузка с параметромtopLevelValues;Utf8JsonReader: низкоуровневый токенизатор без лишних аллокаций. Этоref struct, его нельзя держать черезawait, а при чтении из потока буфер подкармливают вручную, передаваяisFinalBlockи сохраняяCurrentState. Берут, когда нужна максимальная скорость или пара полей из огромного документа;- Newtonsoft.Json:
JsonTextReaderповерхStreamReader, дошли доStartObjectнужного уровня и вызвалиserializer.Deserialize<T>(reader), он прочитает ровно один объект. Для склеенных подряд объектов естьSupportMultipleContent = true; - конвейер: элементы сразу уходят в обработку пачками, например по тысяче записей в
SqlBulkCopyили черезChannel<T>, а не копятся вList<T>.
Назовите и ограничение: поточное чтение даёт один проход вперёд. Если нужны сортировка или соединение данных по всему файлу, это задача для базы или внешней сортировки.
Где спотыкаются
Говорят «читаем файл построчно». JSON не делится на строки, если это не JSON Lines: весь массив может лежать в одной строке на гигабайт.
Берут JsonDocument.Parse и считают это поточным чтением. Он строит документ целиком, просто экономнее, чем набор объектов.
Пишут разбор на JsonTextReader, который зовёт Value.ToString() на каждом токене. На null или вложенном объекте такой код падает, надёжнее десериализовать элемент целиком через сериализатор.