Разбор большого файла 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);
}
Простой фрагмент кода выше, вероятно, не кажется идеальным, но он делает свою работу. Когда файл большой (15 000 контактов - файл 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"
}
]
Ответы
Ответ 1
Как вы правильно поставили диагноз в своем обновлении, проблема в том, что 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
Ответ 2
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);
}
}
}
Ссылка: Советы по производительности JSON.NET
Ответ 3
Я сделал аналогичную вещь в Python для размера файла 5 ГБ. Я скачал файл в какое-то временное место и прочитал его построчно, чтобы сформировать объект JSON, похожий на то, как работает SAX.
Для С# с использованием Json.NET вы можете загрузить файл, использовать программу чтения потоков, чтобы прочитать файл, и передать этот поток в JsonTextReader и проанализировать его в JObject, используя JTokens.ReadFrom(your JSonTextReader object)
.