Подтвердить что ты не робот

Разбор большого файла 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"
  }
]
4b9b3361

Ответ 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).