Кавычки и переносы строк в CSV: как проверить повреждённое поле
Практический разбор удвоенных кавычек и многострочной ячейки с проверенными JSON-результатами и точными отказами парсера.
Если одна CSV-запись занимает несколько строк текста, файл не обязательно повреждён. Перенос может находиться внутри поля в кавычках. Чтобы найти ошибку, нужно считать логические записи после CSV-разбора, а не физические строки файла. Простое разделение текста по запятым или переводам строк такой файл разбирает неверно.
Neraviko поддерживает многострочные поля и удвоенные внутренние кавычки. Парсер при этом строгий: после закрывающей кавычки допускается разделитель либо окончание записи. Лишний текст он не исправляет догадкой.
Два уровня экранирования
В CSV поле с запятой, кавычкой или переносом заключают в двойные кавычки. Буквальная двойная кавычка внутри такого поля записывается двумя кавычками подряд. RFC 4180, раздел 2, октябрь 2005; проверено 05.10.2026.
В JSON внутреннюю кавычку показывают как \", а перевод строки — как \n. Это другая система записи. При преобразовании CSV в JSON программа сначала получает содержимое ячейки, затем создаёт JSON-строку. Поэтому CSV-кавычки "" превращаются в JSON escape \", а не остаются двумя буквальными кавычками.
Обратная косая черта не является общим CSV-escape. Не заменяйте все "" на \" в исходном файле: это может нарушить CSV-синтаксис. Если исходник действительно использует нестандартный диалект с backslash-escape, нужен его поддерживаемый экспорт или другой parser; опции смены quote/escape в Neraviko версии 1 нет.
Проверенный многострочный пример
Все значения синтетические. В поле note ниже находится настоящий перевод строки:
id,note
A01,"сказал ""да""
новая строка"
В CSV → JSON выберите delimiter=comma, header=true, pretty=false, trailing_newline=false. Результат:
[{"id":"A01","note":"сказал \"да\"\nновая строка"}]
Пример проверен прямым локальным вызовом csv.to_json версии 1. В результате одна запись и две колонки. Видимый \n в JSON обозначает перевод строки внутри значения; он не начинает новую запись массива.
Проверка также выполнена для исходного переноса CRLF внутри поля. Текущая реализация нормализует CRLF и CR в LF. Значение остаётся многострочным, но исходные байты окончания строки не сохраняются. Если нужна байтовая копия исходника, храните оригинал отдельно: преобразование формата для такой задачи не подходит.
Найдите точный тип повреждения
У незакрытого поля, например A01,"unfinished, будет UNCLOSED_CSV_QUOTE. Проверьте исходную запись полностью: закрывающая кавычка может находиться дальше на следующей физической строке. Не добавляйте кавычку в конец каждой строки автоматически, иначе разделите одно поле на несколько записей.
Фрагмент A01,"done" x возвращает INVALID_CSV: после закрывающей кавычки появился пробел и буква, а не разделитель или окончание записи. Этот отказ проверен локально. Если пробел входит в значение, он должен находиться внутри кавычек. Если это артефакт выгрузки, исправляйте экспортёр или конкретную известную запись.
У корректно закрытых полей может оставаться другая ошибка: число ячеек не совпадает с заголовком. В таком случае приходит INCONSISTENT_CSV_COLUMNS. Нужно сверить ожидаемые границы колонок; правильные кавычки не создают отсутствующую ячейку.
Подготовьте контрольный набор
Используйте пять искусственных значений: обычный текст, текст с запятой, кавычку, настоящий перенос и пустую ячейку. Проверьте их содержимое после преобразования, а затем после импорта в целевую систему. Количество физических строк здесь не является метрикой количества данных.
Не удаляйте все кавычки ради «очистки». После этого запятая внутри текста может стать разделителем, а перенос — окончанием записи. Аналогично нельзя свести все пробельные символы к одному пробелу, если многострочность является частью данных.
Для обратного экспорта JSON → CSV передавайте исходные значения обычными JSON-строками. CSV-writer самостоятельно расставляет нужные кавычки; предварительное ручное экранирование даст лишние символы в значении.
Парсер ограничен 5 000 логическими записями, включая заголовок, 256 колонками и 65 536 байтами на поле. Многострочное поле не занимает несколько записей лимита. Лимит запроса — 262 144 байта. Операция выполняется на сервере; сырые входы и выходы по контракту не сохраняются и не логируются по умолчанию. Для диагностики достаточно синтетического фрагмента с тем же синтаксисом.