Из API пришли заказы: имя клиента лежит внутри client, email написан с пробелом, а у одной записи клиента вообще нет. Нужно подготовить понятные поля и посчитать учебную сумму для каждой записи. Сделаем это через expressions в n8n: без AI-модели, Code node и внешних ключей.
Результат инструкции
Соберём цепочку Manual Trigger → Test Orders → Split Orders → Normalized. На выходе будут три заказа с собственными ID, очищенными именами и email, запасным текстом комментария и суммами 1500, 2400 и 1200. Исходные поля сохранятся рядом с новыми: можно сразу проверить, что преобразование ничего не перепутало.
У заказа без клиента появится «Без имени», а не ошибка чтения вложенного поля. Для неверных числовых типов будет subtotal_ready = false и subtotal = null. Это подготовка данных, не полноценная проверка заказа и не разрешение отправлять письмо или выставлять счёт.
Что понадобится
- Доступ к созданию отдельного workflow в n8n. Проверенная версия: 2.42.4, Edit Fields 3.4.
- Готовый JSON схемы из шага 2 и учебные данные. API-ключи, CRM и платный тариф для упражнения не нужны.
- Пустой холст: не вставляйте пример в работающую схему с платежами или рассылками.
Установка n8n локально через Docker
Шаг 1. Создайте пустой workflow
Откройте Overview → Create workflow. Не выбирайте существующую схему. В упражнении используется Manual Trigger: запуск будет по кнопке Execute workflow. Publish нажимать не нужно, потому что внешних событий, расписания и webhook здесь нет.
Шаг 2. Импортируйте четыре узла
Скачать n8n-expressions-orders-demo.json
Скопируйте весь JSON файла, щёлкните свободную область нового холста и нажмите Ctrl+V. Должны появиться имя ezgpt-expressions-orders-demo и четыре узла, соединённые одной цепочкой. Нажмите Zoom to Fit. Альтернативный способ в редакторе - More actions → Import from File. Вставляйте схему только один раз.
Все выражения уже находятся в Normalized. Дальше проверим их по одному: имя поля, тип и значение. В экспортированном JSON expression начинается с ={{, а в редакторе в режиме Expression обычно видны только {{ ... }}. Не добавляйте второй знак равенства к импортированным значениям.
Шаг 3. Проверьте исходные заказы
Откройте Test Orders. Это Edit Fields с Mode = JSON Output. В поле JSON должен быть такой объект:
{
"orders": [
{
"id": "A1",
"client": {
"name": " Анна ",
"email": "ANNA@EXAMPLE.INVALID "
},
"quantity": 2,
"unit_price": 750,
"comment": ""
},
{
"id": "A2",
"client": {
"name": "Борис",
"email": "boris@example.invalid"
},
"quantity": 3,
"unit_price": 800,
"comment": "Нужен договор"
},
{
"id": "A3",
"client": null,
"quantity": 1,
"unit_price": 1200
}
]
}
Имена и адреса вымышлены. У A1 специально оставлены пробелы и большие буквы в email. У A3 client равен null, а comment отсутствует. quantity и unit_price - числа, не строки: в этом примере не нужно автоматически угадывать их тип. Нажмите Execute step. На выходе пока один item, внутри которого массив orders.
Шаг 4. Получите отдельные записи
Откройте Split Orders: узел Split Out. Fields To Split Out = orders, Include = No Other Fields. Destination Field Name не задавайте. Нажмите Execute step. На выходе должно быть три items, и у каждого id, client, quantity и unit_price находятся на верхнем уровне.
Теперь Normalized получит заказы по отдельности. Если оставить массив внутри одного item, путь $json.id не найдёт ID заказа: данные всё ещё лежат в orders. Поэтому сначала проверяем структуру INPUT, а потом пишем выражение.
Подробнее о Split Out и JSON-массивах
Шаг 5. Возьмите ID текущего заказа
Откройте Normalized. Mode = Manual Mapping, Include Other Input Fields включён, Input Fields to Include = All. Первое поле называется order_id, тип String. Имя поля оставьте в Fixed, а его значение - в Expression:
{{ $json.id }}
$json - данные текущей входной записи. Для A1 выражение вернёт A1, для A2 - A2. Фигурные скобки относятся к значению, а не к названию order_id. Под полем редактор показывает результат выбранного входного item; это предварительный просмотр одной записи, а не доказательство правильности всех трёх.
Шаг 6. Прочитайте имя без ошибки на null
Поле customer_name имеет тип String и такое значение в Expression:
{{ typeof $json.client?.name === 'string' && $json.client.name.trim() ? $json.client.name.trim() : 'Без имени' }}
client?.name безопасно читает имя, когда client отсутствует или равен null. typeof проверяет, что пришла строка. trim() убирает пробелы по краям, но не меняет пробелы внутри имени. Если после очистки текста нет, используем «Без имени». У A1 получится «Анна», у A2 - «Борис», у A3 - запасное значение.
Запасная надпись помогает увидеть неполную запись. Она не исправляет источник и не устанавливает личность клиента. Не заменяйте пропуск случайным именем и не считайте такой заказ готовым к автоматической отправке.
Шаг 7. Подготовьте email и комментарий
У email_normalized тип String. Значение в Expression:
{{ typeof $json.client?.email === 'string' ? $json.client.email.trim().toLowerCase() : '' }}
В учебном примере A1 превратится в anna@example.invalid. У A3 получится пустая строка. Здесь весь адрес приводится к нижнему регистру по правилу нашего примера: для настоящей интеграции сначала согласуйте правила нормализации адресов. Эта операция не проверяет синтаксис email, существование ящика или согласие на письма.
У comment_text тоже тип String. Используйте выражение:
{{ typeof $json.comment === 'string' && $json.comment.trim() ? $json.comment.trim() : 'Комментарий не указан' }}
У A1 пустая строка, у A3 поля нет: оба получат «Комментарий не указан». A2 сохранит «Нужен договор». Проверка trim() важна: строка из трёх пробелов формально существует, но полезного текста в ней нет.
Шаг 8. Проверьте числовые типы
Поле subtotal_ready имеет тип Boolean, не String. Значение в Expression:
{{ Number.isFinite($json.quantity) && Number.isFinite($json.unit_price) && $json.quantity >= 0 && $json.unit_price >= 0 }}
Флаг будет true только для конечных чисел quantity и unit_price, которые не меньше нуля. Строка "2", null и отрицательное количество не проходят это правило. Ноль допускается намеренно. Не включайте Ignore Type Conversion Errors: скрывать ошибочные типы в этом упражнении не нужно.
Это узкая проверка двух полей, а не бизнес-валидатор: она не требует целого количества, не сверяет товар и не задаёт верхнюю границу суммы. Для рабочего процесса эти ограничения нужно определить отдельно.
Шаг 9. Рассчитайте сумму только при корректных числах
Поле subtotal имеет тип Number. В Expression стоит:
{{ Number.isFinite($json.quantity) && Number.isFinite($json.unit_price) && $json.quantity >= 0 && $json.unit_price >= 0 ? $json.quantity * $json.unit_price : null }}
Условие повторено явно: мы не читаем $json.subtotal_ready, поскольку это новое поле этого же узла, а $json здесь содержит входные данные. Если входные типы неверны, результат null, не ноль. Ноль мог бы выглядеть как настоящая сумма и скрыть ошибку.
Для A1 рассчитывается 2 × 750 = 1500, для A2 - 3 × 800 = 2400, для A3 - 1 × 1200 = 1200. Это учебная арифметика без налогов, скидок, валют и правил округления. Очень большие значения и денежные вычисления с дробями здесь не тестировались: не используйте выражение как готовую бухгалтерскую логику.
Шаг 10. Запустите цепочку и сверьте все записи
Закройте настройки и нажмите Execute workflow на холсте. Откройте Normalized → OUTPUT → JSON. Должно быть три items. В нашем проверенном запуске получилось:
Показать код (46 строк)
[
{
"id": "A1",
"client": {
"name": " Анна ",
"email": "ANNA@EXAMPLE.INVALID "
},
"quantity": 2,
"unit_price": 750,
"comment": "",
"order_id": "A1",
"customer_name": "Анна",
"email_normalized": "anna@example.invalid",
"comment_text": "Комментарий не указан",
"subtotal_ready": true,
"subtotal": 1500
},
{
"id": "A2",
"client": {
"name": "Борис",
"email": "boris@example.invalid"
},
"quantity": 3,
"unit_price": 800,
"comment": "Нужен договор",
"order_id": "A2",
"customer_name": "Борис",
"email_normalized": "boris@example.invalid",
"comment_text": "Нужен договор",
"subtotal_ready": true,
"subtotal": 2400
},
{
"id": "A3",
"client": null,
"quantity": 1,
"unit_price": 1200,
"order_id": "A3",
"customer_name": "Без имени",
"email_normalized": "",
"comment_text": "Комментарий не указан",
"subtotal_ready": true,
"subtotal": 1200
}
]
Проверьте пары id/order_id: A1/A1, A2/A2, A3/A3. Имена - Анна, Борис и Без имени. Все три subtotal_ready равны true; subtotal - 1500, 2400, 1200. Исходный client у A1 по-прежнему содержит пробелы и большие буквы: новые очищенные поля добавлены, оригинал не перезаписан.
Если видите только первую запись, переключите OUTPUT на JSON или Table и просмотрите весь результат. Нельзя проверять многострочную обработку только по предпросмотру A1 под expression.
Шаг 11. Проверьте неполные и неверные данные
В Test Orders временно замените JSON на этот тест:
{"orders":[{"id":"M1"},{"id":"M2","client":{"name":" ","email":null},"quantity":0,"unit_price":0,"comment":" "}]}
После полного запуска у M1 должны быть «Без имени», пустой email, запасной комментарий, subtotal_ready = false и subtotal = null. У M2 пустое имя тоже заменится, но нулевые quantity и unit_price дадут true и сумму 0. Пропуск и законный ноль не должны смешиваться.
Затем в исходном A1 из шага 3 временно поставьте quantity: "2" вместо quantity: 2 и выполните всю цепочку. A1 останется в результате, но его subtotal_ready станет false, subtotal - null. У остальных заказов ничего не должно измениться. Верните числовое 2 и весь исходный JSON шага 3.
Шаг 12. Отличите текущую запись от первой и восстановите пример
У order_id в Normalized временно поставьте другое значение в Expression:
{{ $('Split Orders').first().json.id }}
Выполните весь workflow. У всех трёх новых order_id будет A1, хотя исходные id останутся A1, A2, A3. first() явно берёт первый item выбранного узла. Это иногда нужно для единого значения настройки, но для ID текущего заказа здесь ошибочно. Верните {{ $json.id }}.
Для второй проверки переключите именно значение order_id в Fixed и вставьте текст {{ $json.id }}. После запуска в поле будет буквальная строка с фигурными скобками, а не ID. Верните Expression, исходное выражение и исходные данные.
Последний полный запуск должен снова дать правильные три ID и суммы 1500, 2400, 1200. Оставьте workflow ручным и неопубликованным. Вы получили проверенный способ брать значения из JSON, готовить запасные поля и отличать вычисление от обычного текста.
Если результат отличается
| Симптом | Что проверить | Исправление |
|---|---|---|
| Буквальный текст {{ ... }} | Режим значения | Переключить Fixed на Expression |
| У всех заказов один ID | Источник значения | $json.id вместо first() |
| Не находится id | Структура INPUT | Split Out для orders |
| Ошибка чтения имени | client = null | Безопасный путь и typeof |
| Пропали исходные поля | Normalized | Include Other Input Fields |
| Сумма null при "2" | Тип quantity | Проверить источник, не угадывать число |
Как подключать настоящий источник
Следующим источником может стать HTTP Request или webhook, но сначала посмотрите его INPUT: массив может лежать в другом поле, а строки чисел требуют отдельного согласованного преобразования. Не переносите наши пути на неизвестную структуру без проверки. Никаких внешних действий учебная схема не выполняет.
Получить JSON через HTTP Request
Перед письмами, CRM и счетами добавьте отдельную проверку обязательных полей, допустимых значений и прав доступа. Ошибочные записи отправляйте на ручной разбор. Не храните токены в JSON, не вставляйте реальные клиентские данные в учебный набор и не публикуйте их вместе с workflow.
Что проверено редакцией
10 октября 2026 года пример выполнен на n8n 2.42.4. Шесть CLI-сценариев в выделенной тестовой базе: исходные три заказа, неполные записи и нули, неверные типы и отрицательные значения, чтение первого item, буквальный текст вместо expression, пустой массив. Все проверки результата прошли. В отдельном workflow локального редактора выполнен исходный сценарий; скопированный OUTPUT по структуре и значениям JSON совпал с CLI-результатом. Четыре узла, шесть новых полей. Старые workflows не изменены, схема не опубликована, учебный контейнер снова остановлен. Cloud, настоящие API, отправка писем, дробные деньги, очень большие числа и мобильная вёрстка не тестировались. Обложка - редакционная иллюстрация.
Частые вопросы
Что такое expression в n8n простыми словами?
Это значение поля, которое вычисляется при работе узла. В нашем примере {{ $json.id }} берёт ID текущего входного заказа. Если оставить Fixed, тот же текст будет обычной строкой.
Почему $json не находит поле?
Проверьте INPUT текущего узла. До Split Out в упражнении есть массив orders, после него - отдельные записи с id. Путь должен соответствовать реальной структуре, а не предположению о ней.
Как обработать отсутствующее вложенное поле?
В примере имя читается через client?.name, затем проверяются строковый тип и текст после trim(). Для null и пустого имени возвращается «Без имени». Запасная надпись не исправляет источник.
Почему first() повторяет одно значение во всех строках?
first() выбирает первый item указанного узла. В нашем тесте это A1. Для ID каждого входного заказа используем $json.id и проверяем все три строки OUTPUT.
Нужны ли AI-модель или Code node?
Нет. В этой схеме достаточно Edit Fields и коротких expressions. Для сложной обработки нужен отдельный подход; автоматические отправки и внешние интеграции в пример не входят.