Разбор большого файла JSON в.NET
До сих пор я использовал метод JsonConvert.Deserialize(json) в Json.Net, который работал довольно хорошо и, честно говоря, мне не нужно ничего больше, чем это.
Я работаю над фоновым (консольным) приложением, которое постоянно загружает контент json с разных URL-адресов, а затем десериализует результат в список объекта.Net.
using (WebClient client = new WebClient())
{
string json = client.DownloadString(stringUrl);
var result = JsonConvert.DeserializeObject<List<Contact>>(json);
}
Простой фрагмент кода выше, вероятно, не кажется идеальным, но он делает свою работу. Если файл большой (15000 контактов - файл 48 МБ), JsonConvert.DeserializeObject не является решением, и в строке выдается тип исключения JsonReaderException.
Загруженный json - это массив, и вот так выглядит пример. Contact - это контейнерный класс для десериализованного объекта json.
[
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
}
]
Мое первоначальное предположение, что у него заканчивается память. Просто из любопытства я попытался разобрать его как JArray, что также вызвало то же исключение.
Я начал погружаться в документацию Json.Net и читать подобные темы. Поскольку мне пока не удалось найти рабочее решение, я решил разместить здесь вопрос.
Я был бы признателен за любой совет / фрагмент кода, который мог бы помочь мне в исследовании проблемы, получении дополнительной информации о ней и в конечном итоге поиске решения.
Спасибо:)
ОБНОВЛЕНИЕ: при десериализации построчно, я получил ту же ошибку: " [. Path '', строка 600003, позиция 1." Поэтому я скачал два из них и проверил их в Notepad++. Я заметил, что если длина массива больше 12000, после 12000-го элемента "[" закрывается и запускается другой массив. Другими словами, JSON выглядит именно так:
[
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
}
]
[
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
},
{
"firstname": "sometext",
"lastname": "sometext"
}
]
4 ответа
Как вы правильно диагностировали в своем обновлении, проблема в том, что JSON закрывается ]
сразу после открытия [
чтобы начать следующий сет. Этот формат делает JSON недействительным в целом, и поэтому Json.Net выдает ошибку. К счастью, эта проблема, кажется, возникает достаточно часто, так что Json.Net действительно имеет специальные настройки для ее решения. Если вы используете JsonTextReader
непосредственно для чтения JSON, вы можете установить SupportMultipleContent
флаг для true
, а затем используйте цикл для десериализации каждого элемента в отдельности. Это должно позволить вам успешно обрабатывать нестандартный JSON и эффективно использовать память независимо от того, сколько существует массивов или сколько элементов в каждом массиве.
using (WebClient client = new WebClient())
using (Stream stream = client.OpenRead(stringUrl))
using (StreamReader streamReader = new StreamReader(stream))
using (JsonTextReader reader = new JsonTextReader(streamReader))
{
reader.SupportMultipleContent = true;
var serializer = new JsonSerializer();
while (reader.Read())
{
if (reader.TokenType == JsonToken.StartObject)
{
Contact c = serializer.Deserialize<Contact>(reader);
Console.WriteLine(c.FirstName + " " + c.LastName);
}
}
}
Полная демонстрация здесь: https://dotnetfiddle.net/2TQa8p
Json.NET поддерживает десериализацию напрямую из потока. Вот способ десериализации вашего JSON с помощью StreamReader
чтение строки JSON по одной части за раз вместо загрузки всей строки JSON в память.
using (WebClient client = new WebClient())
{
using (StreamReader sr = new StreamReader(client.OpenRead(stringUrl)))
{
using (JsonReader reader = new JsonTextReader(sr))
{
JsonSerializer serializer = new JsonSerializer();
// read the json from a stream
// json size doesn't matter because only a small piece is read at a time from the HTTP request
IList<Contact> result = serializer.Deserialize<List<Contact>>(reader);
}
}
}
Я сделал аналогичную вещь в Python для размера файла 5 ГБ. Я скачал файл в каком-то временном месте и прочитал его построчно, чтобы сформировать объект JSON, похожий на то, как работает SAX. Для C# с использованием json.net вы можете загрузить файл, использовать потоковую программу чтения файла и передать этот поток в JsonTextReader и проанализировать его в JObject, используя JTokens.ReadFrom(ваш объект JSonTextReader).
Это все еще может быть актуально для некоторых сейчас, когда «новый»System.Text.Json
вне.
await using FileStream file = File.OpenRead("files/data.json");
var options = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
// Switch the JsonNode type with one of your own if
// you have a specific type you want to deserialize to.
IAsyncEnumerable<JsonNode?> enumerable = JsonSerializer.DeserializeAsyncEnumerable<JsonNode>(file, options);
await foreach (JsonNode? obj in enumerable) {
var firstname = obj?["firstname"]?.GetValue<string>();
}
Если вас интересует больше, например, как анализировать заархивированный JSON, я написал эту запись в блоге: Анализ файлов Json объемом 60 ГБ с использованием потоков в .NET.