Главная → Академия → Node-RED: установка, flows, msg/JSON, отладка → Введение в JSONata: что это и зачем нужно

Введение в 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`:
`payload` -> вернет объект `{"device-id": "...", ...}`
  • Чтобы получить значение `timestamp` из `payload`:
`payload.timestamp` -> вернет число `1678886400000`
  • Чтобы получить `topic` сообщения:
`topic` -> вернет строку `"telemetry/office/room-101"`

Работа с вложенными объектами

Правило точечной нотации применяется и для вложенных структур. Просто продолжайте цепочку.

  • Чтобы получить уровень заряда батареи из объекта `status`:
`payload.status.battery_level` -> вернет число `98`

Обращение к элементам массива по индексу

Для доступа к элементам массива используются квадратные скобки `[]` с указанием индекса элемента (нумерация начинается с 0).

  • Чтобы получить первый элемент массива `values` (объект с данными о температуре):
`payload.values[0]` -> вернет `{ "type": "temperature", "value": 23.5, "unit": "°C" }`
  • Чтобы получить непосредственно значение температуры из первого элемента:
`payload.values[0].value` -> вернет число `23.5`
  • Чтобы получить единицу измерения влажности из второго элемента:
`payload.values[1].unit` -> вернет строку `"%"`

Использование скобочной нотации для свойств со спецсимволами

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

  • Чтобы получить `device-id` из `payload`:
`payload.['device-id']` -> вернет строку `"MODBUS-TH-01"`

Этот синтаксис незаменим при работе с данными из внешних систем, которые не придерживаются конвенции `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`:
  • Добавьте ноду `Change` в поток.
  • В правилах выберите "set" (`установить`).
  • В поле "to the value" (`в значение`) выберите тип `J:` (JSONata Expression).
  • Введите следующее выражение в текстовое поле:
  • {
    

    "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`:
  • Добавьте ноду `Switch` в поток.
  • В правиле проверки вместо `payload` выберите тип `J:` (JSONata Expression).
  • Введите следующее выражение:
  • `payload.temperature > 25 and payload.co2 > 1000`

  • В качестве оператора сравнения справа от выражения выберите `is true`.
  • Как это работает:
    • Нода `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" }`
    Выражение в ноде `Change` (для установки `msg.fahrenheit`): `payload.value 1.8 + 32`
    • Результат: Новое свойство `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: Это язык выражений для манипуляции JSON-данными, интегрированный в ядро Node-RED для упрощения трансформаций и маршрутизации.
  • Базовый синтаксис: Мы научились извлекать данные из любой части объекта `msg`, используя точечную нотацию для простых свойств, квадратные скобки для индексов массивов и специальную скобочную нотацию для ключей с нестандартными символами.
  • Практическое применение: На примерах нод `Change` и `Switch` мы увидели, как JSONata позволяет динамически формировать новые структуры данных и создавать сложные, многофакторные правила маршрутизации.
  • Основные операторы: Мы познакомились с арифметическими, строковыми и логическими операторами, которые являются фундаментом для построения любых выражений.
  • JSONata — это не просто "синтаксический сахар", а фундаментальный навык для любого специалиста, работающего с Node-RED на профессиональном уровне. Он позволяет строить сложные и в то же время элегантные и поддерживаемые системы автоматизации.

    В следующем уроке мы углубимся в более продвинутые возможности JSONata. Мы рассмотрим:

    • Встроенные функции: `($sum, $average, $count, $uppercase, $now)` и другие, которые позволяют выполнять агрегацию данных и сложные преобразования.
    • Условные выражения: Как использовать тернарный оператор (`условие ? значение_если_истина : значение_если_ложь`) для создания логики "if-then-else" внутри одного выражения.
    • Продвинутую работу с массивами: Фильтрацию, маппинг (преобразование каждого элемента) и редукцию (сведение массива к одному значению).

    Эти знания позволят вам решать еще более широкий круг задач по обработке данных с максимальной эффективностью.