> Как пропускать некорректный JSON и продолжать обработку (C#)

Уровень: senior · Роль: backend · Язык: C# · Категория: Технические вопросы

Компании: Ozon

Стек: C# /.NET

> Пример ответа

Короткий ответ

Используйте JsonDocument.Parse или JsonSerializer.Deserialize в блоке try-catch для перехвата JsonException. При потоковой обработке (NDJSON, массив объектов) читайте по одному элементу и пропускайте некорректные, логируя ошибку. Для больших объёмов применяйте Utf8JsonReader с ручным управлением позицией. Ключевой trade-off: строгий парсинг всего документа против потокового с восстановлением после ошибки.

Подробное объяснение

В .NET есть несколько уровней работы с JSON, и стратегия пропуска зависит от формата входных данных. Если это единый документ, то при ошибке парсинга восстановиться нельзя - придётся отбросить весь документ. Если это поток (например, построчный JSON или массив), можно изолировать каждый элемент и обрабатывать независимо.

Для построчного формата (NDJSON, JSON Lines) достаточно читать строку за строкой, парсить каждую отдельно и при JsonException логировать и продолжать. Это самый простой и надёжный подход.

Для JSON-массива большого размера JsonSerializer.Deserialize<List<T>> загрузит всё в память и упадёт на первой ошибке. Вместо этого используйте JsonDocument.Parse с JsonReaderOptions или Utf8JsonReader в цикле, переходя от элемента к элементу. При ошибке внутри элемента нужно вручную пропустить его токены до следующего на том же уровне вложенности.

JsonSerializerOptions с AllowTrailingCommas и ReadCommentHandling решают только синтаксические мелочи, но не спасают от структурно некорректного JSON. Для устойчивости к частичным ошибкам нужен именно потоковый подход с обработкой исключений на уровне элемента.

На практике

В реальных backend-системах часто приходят логи, события или сообщения из внешних источников, где часть записей битая. Типичные сценарии: ingestion pipeline, ETL, обработка очередей. Рекомендуется:

  • Валидировать каждый элемент отдельно, не полагаясь на валидность всего пакета.
  • Логировать сырую строку и позицию ошибки для последующего разбора.
  • Использовать JsonException.Path и LineNumber для диагностики.
  • Для высокой нагрузки применять System.Text.Json с Utf8JsonReader и PipeReader, чтобы не аллоцировать лишние строки.
  • Накапливать счётчики успешных и пропущенных записей для мониторинга.

Пример кода

CSHARP
using System.Text.Json;
public static IEnumerable<T> ParseSkippingInvalid<T>(IEnumerable<string> lines, ILogger logger)
{
foreach (var line in lines)
{
T? item = default;
try
{
item = JsonSerializer.Deserialize<T>(line);
}
catch (JsonException ex)
{
logger.LogWarning(ex, "Пропущена некорректная строка: {Line}", line);
continue;
}
if (item is not null)
yield return item;
}
}

Для массива с потоковым чтением:

CSHARP
public static async IAsyncEnumerable<T> ReadArraySkippingInvalid<T>(Stream stream)
{
var options = new JsonReaderOptions { AllowTrailingCommas = true };
using var reader = new Utf8JsonReader(await ReadAllBytesAsync(stream), options);
if (!reader.Read() || reader.TokenType != JsonTokenType.StartArray)
yield break;
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
T? item = default;
try
{
item = JsonSerializer.Deserialize<T>(ref reader);
}
catch (JsonException)
{
// Пропускаем текущий элемент до следующего на том же уровне
reader.Skip();
continue;
}
if (item is not null)
yield return item;
}
}

Как отвечать на собеседовании

Начните с уточнения формата: единый документ или поток. Объясните, что для потока нужно изолировать элементы и обрабатывать исключения на каждом. Упомяните JsonException, Utf8JsonReader и Skip(). Покажите понимание trade-off между строгостью и устойчивостью. Приведите пример с логированием и счётчиками. Если спросят про производительность, расскажите про PipeReader и избегание лишних аллокаций.

Что проверяет интервьюер

Понимание различий между парсингом целого документа и потоковой обработкой. Знание API System.Text.Json: JsonDocument, Utf8JsonReader, JsonSerializer. Умение обрабатывать исключения без падения всего pipeline. Осознание trade-off между строгостью валидации и отказоустойчивостью. Навык логирования и мониторинга ошибок. Опыт работы с большими объёмами данных и потоковыми форматами.

Типичные ошибки

Использование JsonSerializer.Deserialize<List<T>> для больших массивов с надеждой пропустить битые элементы - при первой ошибке падает весь вызов. Игнорирование JsonException.Path и LineNumber, что затрудняет диагностику. Попытка восстановить позицию в Utf8JsonReader без Skip(), что приводит к рассинхронизации. Логирование без ограничения размера, что забивает диски. Отсутствие счётчиков пропущенных записей, из-за чего проблемы остаются незамеченными. Смешивание валидации схемы и синтаксического парсинга в одном слое.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью