Перейти к содержанию

Items, JSON и нормализация полей

Одна карточка P-001 помогла понять работу узла. Теперь рассмотрим три предложения одновременно. В n8n данные между узлами передаются как items, и важно отличать число элементов от числа полей внутри одного объекта. Добавим нормализацию строк и получим ожидаемый набор из трёх карточек, сохранив связь каждой с исходной заявкой.

Приложения урока содержат собственные исходные items и ожидаемый результат. JavaScript приведён для узла Code, но не запускался. Он обрабатывает заранее известную внутреннюю форму proposal, созданную после адаптации. HTTP-заголовки и исходная оболочка webhook в эту операцию не входят.

Собственные файлы этого шага доступны в архиве fixtures и контрактов серии, папка lesson-04. Это приложения к объяснению, а не готовый экспорт workflow.

Объект, массив и item

JSON-объект связывает имена с конкретными значениями: идентификатором, темой, заголовком. Массив хранит последовательность объектов или других значений. Item — элемент потока n8n; для обычных данных его объект содержит ключ json. Три заявки удобно представить тремя items, а не одним объектом с полями proposal1, proposal2 и proposal3. Описание структуры данных объясняет эту оболочку и доступ к значениям текущего элемента.

[
  {"json": {"proposal": {"id": "P-001", "title": "  Проверка старых URL  ", "topic": " WEB ", "summary": "Разобрать реестр прежних адресов и ожидаемые ответы.", "approved": false}}},
  {"json": {"proposal": {"id": "P-002", "title": "Страница товара без запаса", "topic": "seo", "summary": "Объяснить полезное содержание страницы при временном отсутствии товара.", "approved": false}}},
  {"json": {"proposal": {"id": "P-003", "title": "Разбор web3-приложения", "topic": "web3", "summary": "Определить границы учебного примера перед подготовкой урока.", "approved": false}}}
]

Один item может содержать вложенный массив, но это всё ещё один элемент потока. Если разместить все три предложения в json.proposals, узел, читающий $json.proposal.title, их не увидит. Понадобится отдельное раскрытие массива или другой договор обработки. В этой серии выбираем форму «одна заявка — один item», поскольку её проще сопоставлять с маршрутом.

Пустой поток [] тоже допустим. Он означает отсутствие элементов для дальнейшей обработки. Вариант с одним пустым объектом внутри json имеет другое значение: это один элемент без нужных полей. Такое различие важно при поиске потерянной заявки. Возврат пустого потока не равен созданию карточки «ничего не найдено» и может остановить дальнейшую ветку.

Нормализация без выдумывания значений

У P-001 удаляем внешние пробелы заголовка и кода темы. Для кода темы применяем нижний регистр; сам заголовок сохраняет исходные буквы. WEB превращается в web, а «URL» не превращается в «url» только ради одинакового регистра. Разные поля требуют разных правил. Смысл текста и терминология статьи здесь не редактируются.

Перед строковым преобразованием проверяем тип. Число вместо заголовка не следует автоматически превращать в строку: внешняя форма уже нарушена. Неизвестная тема web3 остаётся строкой web3, поскольку нормализация не решает, существует ли редакционная очередь. Отсутствующее значение отмечается отдельно, а не заменяется удобным названием web.

Исходное значение Проверка Ожидаемое значение Следующее решение
" WEB " Строка web Известный маршрут
"web3" Строка web3 Ручная проверка темы
" " Пусто после trim Пустая строка и ошибка Уточнение входа
null Неверный тип null и ошибка Исправление формы
false в approved Не доверяем внешнему решению false Одобряет редактор

Последняя строка требует пояснения. Мы не преобразуем входное значение одобрения через Boolean(). Строка "false" в JavaScript является непустой и дала бы истинное значение. Кроме того, источник заявки вообще не уполномочен одобрять материал. Поэтому подготовленная запись всегда получает фиксированное approved: false, независимо от переданного одноимённого поля.

Режим Code и связь элементов

Узел Code предлагает режим для всего набора и режим для каждого item. Выбираем Run Once for All Items: код получает $input.all(), проходит по массиву и возвращает новый массив. Документация Code описывает оба режима. Ниже показана центральная часть преобразования; полный самостоятельный текст для выбранного узла находится в normalize-items.js.

const title = typeof proposal.title === 'string'
  ? proposal.title.trim()
  : null;
const topic = typeof proposal.topic === 'string'
  ? proposal.topic.trim().toLowerCase()
  : null;

Имена proposal в этом фрагменте относятся к одному уже выбранному объекту. Их нельзя вставлять отдельно и ожидать полного результата: окружающая часть в приложении проверяет форму, собирает ошибки и возвращает items. Код не делает сетевых запросов и не записывает файлы. Добавленный source_item показывает позицию исходного предложения, а pairedItem связывает результат с соответствующим входным элементом n8n.

Наша операция сохраняет число items: три входа дают три результата. Ошибочная заявка получает список validation_errors, а не исчезает из массива. Это проектный выбор, который нужен для дальнейшего объяснения отклонённых входов. Фильтрация позже сможет разнести пригодные карточки и запросы уточнения, сохранив наблюдаемую причину для каждого идентификатора.

В нашей цепочке теперь заменяем Fixture P-001 на Code с именем Fixture proposals и режимом Run Once for All Items. В fixture-items.js находится полный текст: он возвращает три собственных items, показанных выше, без чтения файлов во время workflow. После Manual Trigger подключите Fixture proposals, после него — новый Code с именем Normalize proposals, тем же режимом и полным содержимым normalize-items.js. Прежние Select editorial fields и Prepare record отсоедините от этой линии: их задача вошла в новый преобразователь, а статус подготовки появится после маршрута.

Здесь особенно полезно различать чтение поля и его копирование. Выражение $json.proposal.title выбирает название у текущего элемента; оно не создаёт новую заявку и не ищет совпадение по заголовку. В полном нормализаторе каждый результат связан с исходным индексом через pairedItem, поэтому дальнейшие обращения к связанным данным имеют определённое происхождение. Если позже добавить сортировку или фильтрацию, нельзя бездумно использовать новый индекс как прежнюю связь: потребуется сохранить соответствие входам. Для текущего преобразования порядок и число элементов не меняются. Руководство связи items в Code описывает назначение pairedItem.

Разбор ожидаемого результата

У всех трёх исходных предложений заполнены строковые поля, поэтому ожидаемый список ошибок пуст. P-001 получает очищенный заголовок и тему web. P-002 остаётся с темой seo. P-003 остаётся с темой web3, хотя эта тема ещё не имеет маршрута. Одобрение у всех ложное. Такой результат показывает ровно нормализацию, без преждевременного редакционного выбора.

В отдельном образце заменим только заголовок P-001 пробелами. Ожидается title_empty, но идентификатор сохраняется. Для числовой темы ожидается topic_string_required и topic: null. Эти случаи нельзя описывать одной фразой «вход плохой»: первый требует дописать текст, второй — исправить способ формирования JSON. Предметная причина позволяет объяснить следующий шаг отправителю или редактору.

Сравните выход с исходными предложениями по идентификатору, а не по позиции в красивой таблице. При сортировке или добавлении новой заявки позиции могут измениться. Идентификатор сохраняет смысл связи, source_item помогает разобрать конкретную попытку преобразования. Ни одно из этих полей пока не является записью в базе или доказательством, что внешняя система обработала событие.

При самостоятельном повторении выберите JavaScript у обоих Code-узлов и после заполнения их полного текста выполните ручной запуск. В OUTPUT узла Normalize proposals ожидаются три items. Сравните JSON каждого с expected-items.json; особенно проверьте тему P-001 и сохранение web3 у P-003. Для отдельного варианта изменяйте один объект в Fixture proposals, а не код нормализации: так будет понятно, проверяется ли новое входное значение при прежнем правиле.

Состояние lesson-04/after.json добавляет ожидаемую нормализацию к уже описанному процессу. Ошибки исполнения в нём не зафиксированы, потому что исполнения не было. Далее используем очищенную тему и список ошибок для выбора ветки: пригодные заявки пойдут к редакционной очереди, остальные получат объяснимый ручной исход.