Десериализация больших файлов
Есть у меня к примеру большой файл с объектами.
Мне нужно проверить содержит ли какой нибудь объект название города.
using (FileStream fs = new FileStream("city.list.json", FileMode.Open))
{
var citys = await JsonSerializer.DeserializeAsync<List<City>>(fs);
var result = citys.Where(x => x.name == city).FirstOrDefault();
if (result != null)
{
return true;
}
else
{
return false;
}
}
Как мне загружать к примеру по 1000 обьектов, и если ничего не нашло то загружать следующие 1000 объектов?
Или можно как то установить предел памяти к примеру 10 Мб?
Ответы (1 шт):
У меня не получилось с наскоку на System.Text.Json*, поэтому вот вам решение на Newtonsoft.Json.
Основная хитрость вот какая: с одной стороны, нельзя читать через JsonConvert или JsonSerializer.Deserialize, т. к. это прочитает весь файл в память. С другой стороны, читать по одному токену через JsonTextReader тоже неохота, т. к. придётся слишком подробно разбирать каждую запись, и проводить десериализацию практически вручную.
Поэтому я остановился на комбинированном решении. JsonSerializer умеет читать частичные данные из JsonTextReader'а, а значит, его можно попросить прочитать всего один город из списка. Таким образом, можно организовать ленивую последовательность городов.
Правда, нам придётся читать начало и конец массива по токенам, но это небольшая плата за экономию памяти.
Наши токены будет выглядеть так:
начало массива ([)
начало объекта ({)
- тут сам объект City
конец объекта (})
начало объекта ({)
- тут ещё один объект City
конец объекта (})
начало объекта ({)
- и так далее
конец объекта (})
конец массива (])
Итак, получается следующий код:
IEnumerable<City> ReadCitiesLazy(string path)
{
var serializer = new JsonSerializer();
using var tr = File.OpenText(path); // открываем файл
using var jr = new JsonTextReader(tr); // натравливаем на него токенизатор
jr.Read(); // жёстко проверяем формат: убеждаемся, что у нас тут идёт начало массива
if (jr.TokenType != JsonToken.StartArray)
throw new FormatException("Array start expected");
jr.Read(); // переходим к следующему токену
while (jr.TokenType == JsonToken.StartObject) // это или начало объекта
{ // если так, читаем объект одним махом
yield return serializer.Deserialize<City>(jr);
if (jr.TokenType != JsonToken.EndObject) // тут должен быть конец объекта
throw new FormatException("Object end expected");
jr.Read(); // переходим к следующему токену в цикле
}
if (jr.TokenType != JsonToken.EndArray) // не начало объекта => конец массива
throw new FormatException("Array end expected");
}
Всё, можно пользоваться:
foreach (var city in ReadCitiesLazy(path))
Console.WriteLine(city.Name);
*Вот этот пример показывает, как читать из файла в Utf8JsonReader, но я не нашёл, как десериализовать объект из Utf8JsonReader не вручную.