Введение в JSONata: что это и зачем нужно
Что такое JSONata и ее роль в Node-RED
В предыдущих уроках мы научились управлять потоком сообщений с помощью нод `Change` и `Switch`. Однако по мере усложнения задач автоматизации возрастает и сложность данных, которыми мы оперируем. Показания с датчиков, ответы от API, команды от интерфейсов пользователя — все это приходит в виде JSON-объектов разной структуры. Перед инженером встает задача: как эффективно и надежно извлекать, преобразовывать и комбинировать эти данные?
Одним из решений является использование ноды `Function` и написание кода на JavaScript. Но этот подход требует знаний программирования и может усложнить поддержку потоков. Для решения этой проблемы в ядро Node-RED встроен мощный инструмент — JSONata.
> 💡 Подсказка: Для экспериментов с JSONata в реальном времени используйте официальный онлайн-редактор JSONata Exerciser. Это отличный способ протестировать сложные выражения перед их внедрением в поток Node-RED. Вы можете вставить свой JSON-объект и в интерактивном режиме подбирать правильное выражение для извлечения или трансформации данных.
JSONata — это легковесный язык запросов и преобразований, специально созданный для работы с данными в формате JSON. Его можно сравнить с языком SQL, который используется для работы с реляционными базами данных.| Характеристика | SQL (для баз данных) | JSONata (для JSON) |
| :--- | :--- | :--- |
| Объект работы | Структурированные таблицы с рядами и колонками | Структурированные JSON-документы с объектами и массивами |
| Основная задача | `SELECT`, `UPDATE`, `INSERT`, `DELETE` данные | Выборка, трансформация, фильтрация, агрегация данных |
| Пример | `SELECT temperature FROM sensors WHERE room = 'office'` | `payload[room='office'].temperature` |
Роль JSONata в Node-RED — предоставить инженеру-автоматизатору декларативный, то есть описательный, способ манипуляции данными без необходимости прибегать к императивному программированию на JavaScript. Вместо того чтобы писать пошаговую инструкцию "возьми это поле, умножь его на 10, добавь к нему другое поле и положи результат сюда", вы просто описываете конечный результат, который хотите получить.
Ключевое отличие JSONata от простого доступа к свойствам объекта `msg` (например, `msg.payload.temperature`) заключается в том, что JSONata — это не просто указатель пути, а полноценный язык выражений. Он позволяет:
- Выполнять математические операции.
- Объединять строки.
- Применять встроенные функции (например, для вычисления среднего значения или преобразования регистра строк).
- Реструктурировать JSON-объекты, создавая совершенно новые форматы данных.
- Фильтровать массивы по сложным условиям.
- Выполнять условные преобразования (аналог `if-then-else`).
Интеграция JSONata непосредственно в стандартные ноды (`Change`, `Switch`, `Inject`, `Template`) превращает их из простых инструментов в гибкие процессоры данных, что кардинально повышает мощь и выразительность ваших потоков, сохраняя при этом их визуальную простоту и читаемость.
---
Базовый синтаксис: выборка данных из объекта msg
Основа работы с JSONata — это умение точно указывать путь к нужным данным внутри входящего объекта `msg`. По умолчанию любое JSONata-выражение в Node-RED выполняется в контексте всего объекта `msg`, что дает нам доступ ко всем его свойствам: `payload`, `topic`, `_msgid` и другим.
📋 Ключевые понятия:
- Контекст: Объект, относительно которого выполняется выражение. В Node-RED это `msg`.
- Путь (Path): Последовательность ключей и индексов, ведущая к конкретному значению в JSON-структуре.
- Выражение (Expression): Комбинация путей, операторов и функций, которая вычисляется и возвращает результат.
Рассмотрим основные способы выборки данных на примере типового сообщения от мультисенсора, подключенного к контроллеру.
Пример входящего сообщения `msg`:{
"_msgid": "a1b2c3d4.5e4f56",
"topic": "telemetry/office/room-101",
"payload": {
"device-id": "MODBUS-TH-01",
"timestamp": 1678886400000,
"location": "Room 101",
"values": [
{ "type": "temperature", "value": 23.5, "unit": "°C" },
{ "type": "humidity", "value": 45.2, "unit": "%" }
],
"status": {
"battery_level": 98,
"signal_strength": -75
}
}
}
Доступ к свойствам через точечную нотацию (Dot Notation)
Это самый распространенный и интуитивно понятный способ. Используйте точку `.` для навигации по иерархии объекта.
- Чтобы получить весь `payload`:
- Чтобы получить значение `timestamp` из `payload`:
- Чтобы получить `topic` сообщения:
Работа с вложенными объектами
Правило точечной нотации применяется и для вложенных структур. Просто продолжайте цепочку.
- Чтобы получить уровень заряда батареи из объекта `status`:
Обращение к элементам массива по индексу
Для доступа к элементам массива используются квадратные скобки `[]` с указанием индекса элемента (нумерация начинается с 0).
- Чтобы получить первый элемент массива `values` (объект с данными о температуре):
- Чтобы получить непосредственно значение температуры из первого элемента:
- Чтобы получить единицу измерения влажности из второго элемента:
Использование скобочной нотации для свойств со спецсимволами
Если имя свойства содержит дефис, пробел или другие специальные символы, точечная нотация вызовет ошибку. В этом случае имя свойства необходимо заключить в кавычки и использовать квадратные скобки.
- Чтобы получить `device-id` из `payload`:
Этот синтаксис незаменим при работе с данными из внешних систем, которые не придерживаются конвенции `camelCase` или `snake_case` в именовании ключей.
Освоение этих простых правил выборки — это первый и самый важный шаг в работе с JSONata. Он позволяет вам "дотянуться" до любого фрагмента данных в самом сложном JSON-объекте.
---
Применение в нодах: Change и Switch
Теория без практики мертва. Давайте посмотрим, как JSONata раскрывает свой потенциал в двух самых часто используемых нодах для манипуляции данными.
> 🔗 Связанный материал: Мы уже рассматривали ноды `Change` и `Switch` в уроках COURSE-06-M03-L02 и COURSE-06-M03-L03. Теперь вы видите, как JSONata расширяет их функциональность, позволяя перейти от статических правил к динамическим трансформациям и сложной логике.
Пример 1: Форматирование данных в ноде `Change`
Представим, что с релейного модуля, подключенного по Modbus, мы получаем показания тока в миллиамперах (мА) в виде простого числа в `msg.payload`. Наша задача — преобразовать это сообщение в стандартизированный формат, понятный для системы мониторинга: с явным значением в амперах (А) и метаданными.
Входящее сообщение `msg`:{
"payload": 750,
"topic": "modbus/relay-module-1/ch-3/current"
}
Задача: Преобразовать `msg.payload` в следующий формат:
{
"value": 0.75,
"unit": "A",
"source": "relay-ch-3"
}
Решение с помощью ноды `Change`:
{
"value": payload / 1000,
"unit": "A",
"source": "relay-ch-3"
}
Как это работает:
- Нода `Change` вычисляет JSONata-выражение.
- `payload / 1000` берет входящее значение (`750`), делит его на 1000 и получает `0.75`.
- Все выражение конструирует новый JSON-объект, который полностью заменяет собой исходный `msg.payload`. Тема сообщения (`msg.topic`) при этом не затрагивается и передается дальше без изменений.
Пример 2: Сложная маршрутизация в ноде `Switch`
Стандартные правила ноды `Switch` позволяют проверять одно условие за раз. Но что, если нам нужно принять решение на основе комбинации нескольких факторов? Например, включить принудительную вентиляцию в помещении, только если температура превысила 25°C И уровень CO₂ превысил 1000 ppm.
Входящее сообщение `msg` от мультисенсора:{
"payload": {
"temperature": 26.1,
"humidity": 55,
"co2": 1150
}
}
Задача: Пропустить сообщение дальше по потоку только если `temperature > 25` И `co2 > 1000`.
Решение с помощью ноды `Switch`:
`payload.temperature > 25 and payload.co2 > 1000`
- Нода `Switch` вычисляет JSONata-выражение.
- `payload.temperature > 25` вернет `true` (26.1 > 25).
- `payload.co2 > 1000` вернет `true` (1150 > 1000).
- Логический оператор `and` вернет `true`, так как оба условия истинны.
- Сообщение будет отправлено на первый выход ноды `Switch`. Если хотя бы одно из условий было бы ложным, выражение бы вернуло `false`, и сообщение не прошло бы.
Эти два примера демонстрируют, как JSONata позволяет инкапсулировать сложную логику преобразования и маршрутизации внутри одной ноды, делая потоки более компактными, читаемыми и эффективными.
---
Базовые операторы и выражения
Мы уже коснулись некоторых операторов в предыдущих примерах. Теперь давайте систематизируем основные строительные блоки, из которых состоят JSONata-выражения.
> ⚠️ Внимание: JSONata строго типизирован в вопросах истинности (truthiness). Пустая строка `''`, число `0`, пустой массив `[]` и пустой объект `{}` считаются ложными (`false`). Любая непустая строка (даже `"false"` или `"0"`), любое ненулевое число и любой непустой объект/массив считаются истинными (`true`). Будьте внимательны при проверке небулевых значений в условных выражениях.
Арифметические операторы
JSONata поддерживает стандартный набор арифметических операторов для работы с числами.
- `+` (сложение)
- `-` (вычитание)
- `/` (деление)
- `%` (остаток от деления)
- Входящие данные: `msg.payload = { "value": 25, "unit": "C" }`
- Результат: Новое свойство `msg.fahrenheit` будет равно `77`.
Строковая конкатенация
Для объединения (конкатенации) строк используется оператор амперсанда `&`. Это полезно для формирования логов, уведомлений или динамических топиков MQTT.
Пример: Создание человекочитаемого сообщения для лога.- Входящие данные: `msg.payload = { "sensor": "door-main", "state": "opened" }`
- Выражение: `'ALERT: Sensor ' & payload.sensor & ' changed state to ' & payload.state & '!'`
- Результат: Строка `"ALERT: Sensor door-main changed state to opened!"`.
Обратите внимание, что числа автоматически преобразуются в строки при конкатенации.
`'Value: ' & 100` -> `"Value: 100"`
Операторы сравнения
Операторы сравнения возвращают булево значение (`true` или `false`) и являются основой для логики в ноде `Switch` или в условных выражениях.
| Оператор | Описание | Пример |
| :------- | :----------------- | :-------------------------- |
| `==` | Равно | `payload.status == "OK"` |
| `!=` | Не равно | `payload.errorCode != 0` |
| `>` | Больше | `payload.temperature > 30` |
| `<` | Меньше | `payload.voltage < 220` |
| `>=` | Больше или равно | `payload.level >= 95` |
| `<=` | Меньше или равно | `payload.pressure <= 1.5` |
Булева логика
Для комбинирования нескольких условий используются логические операторы `and` и `or`.
- `and` (И): Возвращает `true`, только если оба операнда истинны.
- `or` (ИЛИ): Возвращает `true`, если хотя бы один из операндов истинен.
- Входящие данные: `msg.payload = { "temp": 92, "pressure": 2.8, "leak_detected": false }`
- Выражение для `Switch`: `payload.temp > 90 or payload.pressure > 3.0 or payload.leak_detected = true`
- Результат: Выражение вернет `true` (потому что `payload.temp > 90` истинно), и сообщение пройдет на выход, сигнализируя об аварийной ситуации.
Сочетая эти базовые операторы, вы уже можете строить довольно сложные и полезные выражения для обработки данных прямо в стандартных нодах, значительно сокращая потребность в написании кода.
---
Итоги и следующие шаги
В этом уроке мы сделали первый, но самый важный шаг в освоении JSONata — мощного инструмента для декларативной обработки данных в Node-RED.
Мы выяснили, что JSONata — это, по сути, "SQL для JSON", позволяющий нам элегантно и эффективно запрашивать и трансформировать данные без использования ноды `Function` и JavaScript. Это делает потоки более читаемыми, надежными и доступными для инженеров с разным уровнем подготовки.
Ключевые моменты, которые мы усвоили:
JSONata — это не просто "синтаксический сахар", а фундаментальный навык для любого специалиста, работающего с Node-RED на профессиональном уровне. Он позволяет строить сложные и в то же время элегантные и поддерживаемые системы автоматизации.
В следующем уроке мы углубимся в более продвинутые возможности JSONata. Мы рассмотрим:
- Встроенные функции: `($sum, $average, $count, $uppercase, $now)` и другие, которые позволяют выполнять агрегацию данных и сложные преобразования.
- Условные выражения: Как использовать тернарный оператор (`условие ? значение_если_истина : значение_если_ложь`) для создания логики "if-then-else" внутри одного выражения.
- Продвинутую работу с массивами: Фильтрацию, маппинг (преобразование каждого элемента) и редукцию (сведение массива к одному значению).
Эти знания позволят вам решать еще более широкий круг задач по обработке данных с максимальной эффективностью.