Mastering Home Assistant Data Transformation: Parsing Complex MQTT JSON Payloads with Templates

NGC 224
DIY Smart Home Creator
Introduction: Taming the Wild West of External Data
Integrating diverse devices and services into Home Assistant often means confronting a common challenge: data inconsistency. While many integrations present data cleanly, custom sensors, industrial gateways, or external APIs pushed via MQTT frequently deliver raw, complex JSON payloads. These payloads might contain nested structures, arrays, or multiple data points bundled together, making them challenging to use directly in your automations or Lovelace dashboards. You're not alone if you've struggled to extract a single temperature reading from a sprawling JSON blob.
This guide dives deep into Home Assistant's powerful templating engine, specifically focusing on how to leverage mqtt_sensor
and template
sensors to dissect, transform, and refine even the most convoluted MQTT JSON payloads. We'll move beyond basic value extraction to build robust, actionable entities from raw data, enabling more intelligent automations and clearer insights into your smart home ecosystem.
Step-by-Step Setup: The Foundation – Basic `mqtt_sensor` for JSON Attributes
Our journey begins with the mqtt_sensor
. While it can directly extract a single value from a topic, its true power for complex JSON lies in the json_attributes_topic
and value_template
parameters. Let's assume you have a device publishing a JSON payload like this to home/sensor/mysensor/state
:
{
"temperature": 23.5,
"humidity": 60.2,
"pressure": 1012.3,
"location": "living_room",
"battery": {
"level": 85,
"status": "ok"
},
"read_time": "2023-10-27T10:30:00Z"
}
To get this data into Home Assistant, we'll start by making the entire JSON payload available as attributes of a primary sensor.
# configuration.yaml or sensors.yaml
- platform: mqtt
name: "My Sensor Data"
state_topic: "home/sensor/mysensor/state"
value_template: "{{ value_json.temperature | default('unavailable') }}"
unit_of_measurement: "°C"
device_class: "temperature"
json_attributes_topic: "home/sensor/mysensor/state"
This configuration creates an entity sensor.my_sensor_data
whose state will be the temperature. Crucially, json_attributes_topic
makes the entire JSON payload available as attributes of this sensor. You can verify this by checking the sensor's state in Developer Tools (/developer-tools/state
).
Step-by-Step Setup: Extracting Core State with `value_template`
Sometimes, the primary state of your mqtt_sensor
needs to be a specific value from the JSON. The value_template
uses Jinja2 templating to pick out precisely what you need. Let's say you want the primary sensor state to be the humidity.
# configuration.yaml or sensors.yaml
- platform: mqtt
name: "My Sensor Humidity"
state_topic: "home/sensor/mysensor/state"
value_template: "{{ value_json.humidity | default('unknown') }}" # Extract humidity
unit_of_measurement: "%"
device_class: "humidity"
json_attributes_topic: "home/sensor/mysensor/state" # Still make all attributes available
Now, sensor.my_sensor_humidity
will directly reflect the humidity value. The | default('unknown')
filter is a crucial best practice for robustness, preventing template errors if the humidity
key is ever missing from the payload.
Step-by-Step Setup: Advanced Transformation with `template` Sensors
What if you need to calculate a value from multiple attributes, apply conditional logic, or present data in a more user-friendly format that isn't directly available in the MQTT payload? This is where dedicated template
sensors shine. They can read attributes from other entities and perform complex operations.
Let's create a template sensor to display the battery level and status more descriptively, and another to calculate a heat index if temperature and humidity meet certain criteria.
# configuration.yaml or sensors.yaml
# Template sensor for descriptive battery status
- platform: template
sensors:
my_sensor_battery_status:
friendly_name: "My Sensor Battery Status"
value_template: >
{% set battery = state_attr('sensor.my_sensor_data', 'battery') %}
{% if battery is not none and battery.level is defined %}
Battery: {{ battery.level }}% ({{ battery.status | capitalize }})
{% else %}
Unknown
{% endif %}
icon_template: >
{% set battery_level = state_attr('sensor.my_sensor_data', 'battery').level | int(0) %}
{% if battery_level > 80 %}
mdi:battery-full
{% elif battery_level > 50 %}
mdi:battery-half
{% elif battery_level > 20 %}
mdi:battery-low
{% else %}
mdi:battery-alert
{% endif %}
# Template sensor for calculated heat index (simplified for example)
my_sensor_heat_index:
friendly_name: "My Sensor Heat Index"
unit_of_measurement: "°C"
device_class: "temperature"
value_template: >
{% set temp = states('sensor.my_sensor_data') | float %}
{% set hum = state_attr('sensor.my_sensor_data', 'humidity') | float %}
{% if temp > 25 and hum > 70 %}
{{ (temp + (hum / 5)) | round(1) }} # Simplified calculation
{% else %}
{{ temp | round(1) }} # Just show temperature if conditions not met
{% endif %}
These template sensors leverage the attributes parsed by sensor.my_sensor_data
, demonstrating how you can transform raw JSON data into meaningful, derived entities within Home Assistant.
Troubleshooting Section
Parsing external data is prone to errors. Here's how to debug common issues:
- Malformed JSON or MQTT Connection Issues:
- Symptom: Sensor state is 'unavailable' or 'unknown', no attributes appear.
- Fix: Use an MQTT client like MQTT Explorer to inspect the exact payload published to your MQTT topic (e.g.,
home/sensor/mysensor/state
). Ensure it's valid JSON. Check your Home Assistant logs (/config/logs
) for MQTT connection errors. - Tip: You can quickly validate JSON using online tools or a command-line utility like
jq
(echo 'your_json_string' | jq .
).
- Template Errors (e.g., 'UndefinedError', 'Nonetype' object is not subscriptable):
- Symptom: Sensor state is 'unknown', or logs show Jinja2 errors. This usually means a key or attribute path in your template doesn't exist at the time of evaluation.
- Fix: Go to Developer Tools -> Templates (
/developer-tools/template
). Paste yourvalue_template
oricon_template
code there and test it with dummy data or by referencing the actual sensor's attributes (e.g.,{{ state_attr('sensor.my_sensor_data', 'battery') }}
). Use| default('')
oris not none
checks liberally to handle missing data gracefully. - Example: Instead of
{{ value_json.nested.key }}
, use{{ value_json.nested.key | default('N/A') }}
. For attributes,{% set attr = state_attr('sensor.entity', 'attribute_name') %}{% if attr is not none %}...{% endif %}
.
- Incorrect Data Types:
- Symptom: Calculations fail, or comparisons don't work as expected (e.g., '25' > 100 is true because it's comparing strings).
- Fix: Always cast values to the correct type for comparisons or calculations using filters like
| float
,| int
, or| bool
. - Example:
{% if states('sensor.my_sensor_data') | float > 25.0 %}
Advanced Config / Optimization: Handling Nested Arrays and Dynamic Keys
Sometimes, your JSON payload might contain arrays of objects, or keys that change dynamically. Home Assistant's templating can handle this, though it gets more complex.
Consider a payload with a list of historical readings:
{
"device_id": "XYZ123",
"readings": [
{"timestamp": "2023-10-27T10:00:00Z", "temp": 23.0},
{"timestamp": "2023-10-27T10:05:00Z", "temp": 23.2}
],
"latest_reading": {"timestamp": "2023-10-27T10:30:00Z", "temp": 23.5}
}
To get the latest historical temperature (assuming the array is ordered latest first, or you want a specific index):
# Extract the temperature from the first item in the 'readings' array
- platform: mqtt
name: "Historical Temp Latest"
state_topic: "home/sensor/array_sensor/state"
value_template: "{{ value_json.readings[0].temp | default('unavailable') }}"
unit_of_measurement: "°C"
device_class: "temperature"
For more complex scenarios, like averaging values from an array or searching for a specific item, you would use a template
sensor with Jinja's for
loops:
# Template sensor to calculate average of historical temperatures (simple example)
- platform: template
sensors:
historical_temp_average:
friendly_name: "Historical Temperature Average"
unit_of_measurement: "°C"
device_class: "temperature"
value_template: >
{% set readings = state_attr('sensor.historical_temp_latest', 'readings') %}
{% if readings is not none and readings | length > 0 %}
{% set total_temp = 0 %}
{% for item in readings %}
{% set total_temp = total_temp + item.temp | float %}
{% endfor %}
{{ (total_temp / readings | length) | round(1) }}
{% else %}
unknown
{% endif %}
Real-World Example: Integrating a Custom HVAC Monitor
Let's imagine you've built an ESP32-based HVAC monitor that publishes a detailed JSON payload via MQTT to hvac/monitor/data
:
{
"zone": "main_floor",
"sensor_id": "HVAC_001",
"metrics": {
"supply_temp": 28.1,
"return_temp": 23.7,
"fan_speed_rpm": 1250,
"filter_pressure_diff": 0.12,
"current_state": "heating"
},
"errors": [
{"code": 101, "message": "Sensor drift detected", "severity": "warning"}
],
"uptime_minutes": 1440
}
Here's how you'd integrate this into Home Assistant, extracting key metrics and creating actionable entities:
# configuration.yaml
# Main MQTT Sensor to capture all data as attributes
- platform: mqtt
name: "HVAC Main Monitor"
state_topic: "hvac/monitor/data"
value_template: "{{ value_json.metrics.current_state | default('off') }}"
json_attributes_topic: "hvac/monitor/data"
icon: mdi:hvac
# Template Sensors to expose individual metrics
- platform: template
sensors:
hvac_supply_temperature:
friendly_name: "HVAC Supply Temperature"
unit_of_measurement: "°C"
device_class: "temperature"
value_template: "{{ state_attr('sensor.hvac_main_monitor', 'metrics').supply_temp | float | round(1) | default('unknown') }}"
hvac_return_temperature:
friendly_name: "HVAC Return Temperature"
unit_of_measurement: "°C"
device_class: "temperature"
value_template: "{{ state_attr('sensor.hvac_main_monitor', 'metrics').return_temp | float | round(1) | default('unknown') }}"
hvac_fan_speed:
friendly_name: "HVAC Fan Speed"
unit_of_measurement: "RPM"
icon_template: "mdi:fan"
value_template: "{{ state_attr('sensor.hvac_main_monitor', 'metrics').fan_speed_rpm | int | default('unknown') }}"
hvac_filter_status:
friendly_name: "HVAC Filter Status"
value_template: >
{% set pressure = state_attr('sensor.hvac_main_monitor', 'metrics').filter_pressure_diff | float %}
{% if pressure > 0.2 %}
Replace Filter!
{% elif pressure > 0.1 %}
Check Filter Soon
{% else %}
Good
{% endif %}
icon_template: >
{% set pressure = state_attr('sensor.hvac_main_monitor', 'metrics').filter_pressure_diff | float %}
{% if pressure > 0.2 %}
mdi:air-filter-warning
{% elif pressure > 0.1 %}
mdi:air-filter
{% else %}
mdi:air-filter-outline
{% endif %}
hvac_errors:
friendly_name: "HVAC System Errors"
value_template: >
{% set errors = state_attr('sensor.hvac_main_monitor', 'errors') %}
{% if errors is not none and errors | length > 0 %}
{% for error in errors %}
{{ error.code }}: {{ error.message }} ({{ error.severity | capitalize }})
{% if not loop.last %}; {% endif %}
{% endfor %}
{% else %}
No errors
{% endif %}
icon_template: >
{% set errors = state_attr('sensor.hvac_main_monitor', 'errors') %}
{% if errors is not none and errors | length > 0 %}
mdi:alert-octagon
{% else %}
mdi:check-circle
{% endif %}
This configuration creates 6 distinct entities from one complex MQTT payload: one primary sensor for the HVAC state, and five template sensors for specific metrics like supply temperature, return temperature, fan speed, filter status (derived), and a concatenated list of errors. This approach transforms a raw data stream into a highly usable and actionable set of Home Assistant entities.
Best Practices and Wrap-up
- Robustness with
default
andis not none
: Always anticipate missing keys or attributes. Use| default('fallback_value')
,is defined
, andis not none
checks to prevent template errors that can make your sensors unavailable. - Type Casting: Explicitly cast values with
| float
,| int
, or| bool
before performing calculations or comparisons to ensure correct behavior. - Organization: For complex configurations, use
!include
directives to separate your MQTT and template sensor definitions into dedicated files (e.g.,mqtt_sensors.yaml
,template_sensors.yaml
) within asensors
directory. This keeps yourconfiguration.yaml
clean and manageable. - Performance: While powerful, overly complex templates that update very frequently can consume CPU resources. Optimize your templates for efficiency, especially those involving loops or extensive string manipulation. Consider if some preprocessing can be done at the source (e.g., in your ESPHome device or Node-RED flow) before publishing to MQTT.
- Testing: The Developer Tools -> Templates tab is your best friend for debugging. Test small parts of your template logic there before deploying to your configuration.
- Security: Ensure your MQTT broker is properly secured (TLS, authentication) and avoid publishing sensitive information in plain text. If data must be secured end-to-end, consider encrypted MQTT or alternative secure transport methods.
- Documentation: Add comments to your YAML configurations, explaining the logic of complex templates, especially if you're deriving values or handling edge cases. Your future self (or collaborators) will thank you.
By mastering Home Assistant's MQTT and templating capabilities, you unlock a new level of control and insight, turning raw, disparate data streams into a cohesive, intelligent, and highly automated smart home experience. No more letting messy data hinder your automation dreams!

NGC 224
Author bio: DIY Smart Home Creator