What is JSON?
JSON (JavaScript Object Notation, pronounced “JAY-son”) is a lightweight, text-based format for storing and transporting structured data. It’s the lingua franca of the internet — virtually every API, config file, automation platform, and web service uses JSON to exchange data.
If you’re working with n8n, Home Assistant, APIs, or any modern automation tool, JSON is the single most important data format to understand.
Why JSON Matters for You
| Where You’ll See It | How You’ll Use It |
|---|---|
| n8n | Every item that flows between nodes is a JSON object. Expressions reference JSON fields. |
| Home Assistant | API calls, automation configs, sensor states, WebSocket messages — all JSON. |
| REST APIs | Request bodies and responses are JSON. |
| Config files | Many tools use JSON for configuration (package.json, tsconfig.json, etc.). |
| Databases | PostgreSQL, MongoDB, and others store/query JSON natively. |
JSON vs Other Formats
| Format | Best For | Pros | Cons |
|---|---|---|---|
| JSON | APIs, automation, data exchange | Universal, lightweight, easy to parse | No comments, strict syntax |
| YAML | Config files (HA, Docker Compose) | Readable, supports comments | Indentation-sensitive, can be ambiguous |
| CSV | Tabular data (spreadsheets) | Simple, compact | No nesting, no data types |
| XML | Legacy enterprise systems | Schema validation, attributes | Verbose, hard to read |
| TOML | Config files (Cargo, Hugo) | Clean for configs | Not great for deeply nested data |
Key takeaway: YAML is what you write (configs, HA automations). JSON is what moves (APIs, n8n data, HA WebSocket messages). You need to be fluent in both, but JSON is the one that carries your data between systems.
JSON Data Types
JSON has exactly 6 data types. That’s it. Everything in JSON is built from these:
| Type | What It Is | Example |
|---|---|---|
| String | Text, wrapped in double quotes | "Hello, world!" |
| Number | Integer or decimal (no quotes) | 42 or 3.14159 or -7 |
| Boolean | True or false (no quotes) | true or false |
| Null | Absence of value (no quotes) | null |
| Array | Ordered list, wrapped in [] | ["apple", "banana", "cherry"] |
| Object | Key-value pairs, wrapped in {} | {"name": "John", "age": 35} |
Important Rules
- Strings MUST use double quotes — single quotes are invalid:
"correct"not'wrong' - Keys MUST be strings with double quotes:
"name": "John"notname: "John" - No trailing commas —
{"a": 1, "b": 2,}is invalid (comma after 2) - No comments —
// this is a commentis NOT valid JSON - Numbers are just numbers — no quotes, no thousands separators:
4200not"4,200"
Examples of Each Type
{
"string_example": "I am text",
"number_example": 42,
"decimal_example": 3.14,
"negative_example": -17,
"boolean_true": true,
"boolean_false": false,
"null_example": null,
"array_example": ["apple", "banana", "cherry"],
"object_example": {
"nested_key": "nested value"
}
}
Objects: The Building Block
A JSON object is a collection of key-value pairs, wrapped in curly braces {}. This is the most important structure in JSON — almost everything you work with will be an object.
Basic Object
{
"name": "John Costabile",
"email": "john@example.com",
"age": 35,
"is_active": true
}
Accessing Values
In JavaScript / n8n expressions / most programming languages:
data.name // "John Costabile"
data["email"] // "john@example.com"
data.age // 35
data.is_active // true
In n8n expressions:
{{ $json.name }} // "John Costabile"
{{ $json.email }} // "john@example.com"
{{ $json["is_active"] }} // true
Keys with Spaces or Special Characters
If a key has spaces or special characters, you must use bracket notation:
{
"first name": "John",
"order-total": 150.00
}
data["first name"] // "John"
data["order-total"] // 150.00
Best practice: Avoid spaces and hyphens in keys. Use
snake_caseorcamelCaseinstead:first_name,orderTotal.
Arrays: Lists of Data
A JSON array is an ordered list of values, wrapped in square brackets []. Arrays can contain any type — strings, numbers, objects, or even other arrays.
Array of Strings
["red", "green", "blue"]
Array of Numbers
[10, 20, 30, 40, 50]
Array of Objects (Very Common)
[
{"name": "John", "role": "admin"},
{"name": "Dawn", "role": "editor"},
{"name": "Evie", "role": "viewer"}
]
Accessing Array Elements
Arrays are zero-indexed — the first element is at position 0:
users[0] // {"name": "John", "role": "admin"}
users[0].name // "John"
users[1].name // "Dawn"
users.length // 3 (number of elements)
In n8n expressions:
{{ $json.users[0].name }} // "John"
{{ $json.users[1].name }} // "Dawn"
Mixed-Type Arrays (Legal but Rare)
[42, "hello", true, null, {"key": "value"}, [1, 2, 3]]
Best practice: Keep arrays homogeneous — all elements should be the same type. Mixed-type arrays are valid JSON but make code harder to write and maintain.
Nesting: Objects Within Objects
Real-world JSON is almost always nested — objects inside objects, arrays inside objects, objects inside arrays. This is where JSON gets powerful (and where people get confused).
A Realistic Example: API Response
{
"status": "success",
"code": 200,
"data": {
"user": {
"id": 12345,
"name": "John Costabile",
"email": "john@example.com",
"address": {
"street": "3 Station Rd",
"suburb": "Red Hill",
"state": "VIC",
"postcode": "3937",
"country": "Australia"
},
"devices": [
{"type": "iphone", "model": "iPhone 15 Pro"},
{"type": "watch", "model": "Apple Watch Ultra 2"}
]
}
},
"metadata": {
"request_id": "abc-123-def",
"timestamp": "2026-07-15T10:30:00Z"
}
}
Accessing Nested Values
response.data.user.name // "John Costabile"
response.data.user.address.suburb // "Red Hill"
response.data.user.address.postcode // "3937"
response.data.user.devices[0].model // "iPhone 15 Pro"
response.data.user.devices[1].type // "watch"
response.metadata.request_id // "abc-123-def"
In n8n expressions:
{{ $json.data.user.name }} // "John Costabile"
{{ $json.data.user.address.suburb }} // "Red Hill"
{{ $json.data.user.devices[0].model }} // "iPhone 15 Pro"
The Mental Model
Think of nested JSON as a folder structure. Each . takes you one level deeper:
response
└── data
└── user
├── name → "John Costabile"
├── email → "john@example.com"
├── address
│ ├── street → "3 Station Rd"
│ ├── suburb → "Red Hill"
│ └── state → "VIC"
└── devices
├── [0]
│ ├── type → "iphone"
│ └── model → "iPhone 15 Pro"
└── [1]
├── type → "watch"
└── model → "Apple Watch Ultra 2"
Pro tip: When you’re staring at a complex JSON response and can’t figure out the path to the data you need, paste it into a JSON formatter (see Tools section below) and collapse/expand the tree to navigate visually.
Real-World Examples
Example 1: n8n Workflow Item
This is what a typical item looks like as it flows between n8n nodes:
{
"id": 9876,
"name": "Widget Pro",
"price": 49.99,
"in_stock": true,
"tags": ["electronics", "gadget", "popular"],
"supplier": {
"name": "Acme Corp",
"contact": {
"email": "orders@acme.com",
"phone": "+61-3-9999-0000"
}
},
"metadata": {
"created_at": "2026-07-15T08:00:00Z",
"updated_at": null
}
}
To get the supplier’s email in n8n:
{{ $json.supplier.contact.email }} // "orders@acme.com"
Example 2: Home Assistant API Call
When you call the HA REST API to turn on a light, the request body is JSON:
{
"entity_id": "light.lounge_lamp",
"brightness": 255,
"color_name": "blue"
}
And the HA API response is JSON:
{
"entity_id": "light.lounge_lamp",
"state": "on",
"attributes": {
"friendly_name": "Lounge Lamp",
"brightness": 255,
"color_name": "blue",
"supported_color_modes": ["hs", "rgb"],
"color_mode": "rgb"
},
"last_changed": "2026-07-15T01:30:00.000000+00:00"
}
Example 3: Home Assistant WebSocket Message
When HA sends a state change over WebSocket, it’s JSON:
{
"type": "event",
"event": {
"entity_id": "binary_sensor.8_8_8_8",
"new_state": {
"state": "off",
"attributes": {
"friendly_name": "8.8.8.8",
"device_class": "connectivity"
}
},
"old_state": {
"state": "on",
"attributes": {
"friendly_name": "8.8.8.8",
"device_class": "connectivity"
}
}
}
}
Example 4: Docker Compose / Config File
Docker Compose uses YAML, but many config files use JSON. Here’s a package.json:
{
"name": "kb-costabile",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"astro": "^5.0.0"
}
}
Example 5: Weather API Response
{
"location": {
"name": "Red Hill",
"region": "Victoria",
"country": "Australia",
"lat": -38.37,
"lon": 145.22
},
"current": {
"temp_c": 12.5,
"is_day": 0,
"condition": {
"text": "Partly cloudy",
"icon": "//cdn.weatherapi.com/weather/64x64/night/116.png"
},
"humidity": 78,
"feelslike_c": 10.2
},
"forecast": {
"forecastday": [
{
"date": "2026-07-15",
"day": {
"maxtemp_c": 14.0,
"mintemp_c": 7.5,
"condition": {"text": "Sunny"}
}
}
]
}
}
To get today’s max temperature in n8n:
{{ $json.forecast.forecastday[0].day.maxtemp_c }} // 14.0
Common Mistakes and How to Fix Them
1. Single Quotes Instead of Double Quotes
❌ {'name': 'John', 'age': 35}
✅ {"name": "John", "age": 35}
Why: JSON spec requires double quotes. Single quotes are valid in JavaScript but NOT in JSON.
2. Trailing Comma
❌ {"name": "John", "age": 35,}
✅ {"name": "John", "age": 35}
Why: The last key-value pair must NOT have a trailing comma. This is the #1 cause of JSON parse errors.
3. Unquoted Keys
❌ {name: "John", age: 35}
✅ {"name": "John", "age": 35}
Why: Keys MUST be strings with double quotes. This is valid JavaScript but invalid JSON.
4. Comments
❌ {"name": "John", // this is the user's name
"age": 35}
✅ {"name": "John", "age": 35}
Why: JSON does not support comments. If you need documentation, use a separate schema file or a key like "_comment": "This is the user profile".
5. Using undefined
❌ {"value": undefined}
✅ {"value": null}
Why: undefined is a JavaScript concept. JSON has null for “no value.”
6. Functions or Dates as Values
❌ {"callback": function() { return 1; }, "date": new Date()}
✅ {"callback": null, "date": "2026-07-15T10:30:00Z"}
Why: JSON only supports the 6 data types. Dates must be represented as ISO 8601 strings. Functions can’t be serialized.
7. Incorrect Number Formats
❌ {"price": "49.99", "quantity": "3"}
✅ {"price": 49.99, "quantity": 3}
Why: Numbers should not be wrapped in quotes. If you quote them, they become strings and numeric operations will fail.
Pro tip: If your JSON won’t parse, paste it into jsonformatter.org — it’ll highlight the exact line and character where the error is.
Working with JSON in Different Contexts
In n8n
n8n is built on JSON. Here are the key patterns:
Accessing data from the current node:
{{ $json.fieldName }}
{{ $json.nested.object.key }}
{{ $json.array[0].field }}
Accessing data from a specific node:
{{ $node["Node Name"].json.field }}
Using expressions in a Set node:
=Hello, my name is {{ $json.name }} and I live in {{ $json.address.suburb }}
Working with arrays (Item Lists):
- Use the Item Lists node to split an array into individual items
- Use the Aggregate node to combine multiple items into an array
In Home Assistant
Calling the REST API (curl):
curl -X POST http://homeassistant.local:8123/api/services/light/turn_on \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"entity_id": "light.lounge_lamp", "brightness": 255}'
Reading a sensor state (response):
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://homeassistant.local:8123/api/states/sensor.temperature
{
"entity_id": "sensor.temperature",
"state": "22.5",
"attributes": {
"unit_of_measurement": "°C",
"friendly_name": "Living Room Temperature"
}
}
In JavaScript/Python
JavaScript:
// Parse JSON string to object
const data = JSON.parse('{"name": "John", "age": 35}');
console.log(data.name); // "John"
// Convert object to JSON string
const json = JSON.stringify(data, null, 2);
console.log(json);
Python:
import json
# Parse JSON string to dict
data = json.loads('{"name": "John", "age": 35}')
print(data["name"]) # "John"
# Convert dict to JSON string
json_str = json.dumps(data, indent=2)
print(json_str)
In the Terminal with jq
jq is a command-line JSON processor. It’s incredibly powerful for filtering and transforming JSON in the terminal:
# Pretty-print JSON from an API response
curl -s https://api.example.com/data | jq .
# Extract a specific field
curl -s https://api.example.com/user | jq '.name'
# Extract nested field
curl -s https://api.example.com/user | jq '.address.suburb'
# Get first element of an array
curl -s https://api.example.com/users | jq '.[0].name'
# Filter array by condition
curl -s https://api.example.com/users | jq '.[] | select(.role == "admin")'
# Extract specific fields from all array elements
curl -s https://api.example.com/users | jq '.[] | {name, email}'
Tools for Working with JSON
| Tool | Type | Best For | Link |
|---|---|---|---|
| JSON Formatter | Web | Validating and pretty-printing JSON | jsonformatter.org |
| jq | CLI | Filtering and transforming JSON in terminal | jqlang.org |
| Postman/Insomnia | App | Testing APIs with JSON request bodies | postman.com |
| JSON Schema | Spec | Validating JSON structure | json-schema.org |
| n8n Editor | App | Visual JSON manipulation | n8n.io |
| VS Code | Editor | JSON editing with validation built in | code.visualstudio.com |
VS Code Tips
- Open any
.jsonfile and VS Code will validate it automatically (red squiggles = errors) Shift+Alt+Fto format/beautify JSON- Install the JSON extension for schema validation
- Use
//in.jsoncfiles (JSON with Comments) if you need comments
JSON Schema: Validating Structure
When APIs exchange JSON, you need to know the expected structure. JSON Schema is a standard for describing what a JSON object should look like:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"email": {"type": "string", "format": "email"},
"role": {"type": "string", "enum": ["admin", "editor", "viewer"]},
"devices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {"type": "string"},
"model": {"type": "string"}
},
"required": ["type", "model"]
}
}
},
"required": ["name", "email"]
}
This schema says: “The object must have name and email (required). age must be a non-negative integer. email must be a valid email format. role can only be admin, editor, or viewer. devices is an array of objects with type and model.”
You won’t write schemas often, but understanding them helps you read API documentation that includes them.
Best Practices
| Practice | Why | Example |
|---|---|---|
| Use consistent naming | Predictability | Pick snake_case OR camelCase and stick with it |
| Use snake_case for keys | Most APIs use this convention | first_name, created_at, is_active |
| Use ISO 8601 for dates | Universal, sortable, timezone-aware | "2026-07-15T10:30:00Z" |
| Use null, not empty strings | Clear distinction between “no value” and “empty” | "middle_name": null not "middle_name": "" |
| Keep nesting shallow | Readability and ease of access | Avoid more than 3-4 levels deep |
| Use arrays for lists | Consistent structure | "tags": ["news", "tech"] not "tag1": "news", "tag2": "tech" |
| Document with a schema | When building APIs, provide a JSON Schema | Helps consumers understand the structure |
| Validate before sending | Catch errors early | Use JSON.parse() / json.loads() in a try/catch |
| Pretty-print for readability | Easier debugging | JSON.stringify(data, null, 2) or json.dumps(data, indent=2) |
Your JSON Learning Plan
Day 1: Basics (1-2 hours)
- Read this guide start to finish ✅
- Go to jsonformatter.org — paste in the examples from this guide and experiment
- Try changing values, adding fields, and see what happens
- Break the JSON on purpose (remove a quote, add a trailing comma) and see the error messages
Day 2: Hands-on (1-2 hours)
- Open your n8n instance (or HA API) and look at the raw JSON in the node outputs
- Practice writing expressions to access different fields
- Try the
jqcommand line tool — pipe acurlcommand throughjq .to see formatted JSON - Fetch a public API (like
https://jsonplaceholder.typicode.com/users) and try to extract specific fields
Day 3: Advanced (1-2 hours)
- Build a small n8n workflow that fetches JSON from an API and transforms it
- Practice with nested arrays — loop through an array of objects and extract specific fields
- Try writing a JSON Schema for a simple object and validate data against it
- Read the API documentation for a service you use and understand the JSON structure
Practice Exercises
Exercise 1: Given this JSON, write the path to get the second user’s email:
{
"users": [
{"name": "John", "email": "john@test.com"},
{"name": "Dawn", "email": "dawn@test.com"}
]
}
Answer
users[1].email
In n8n: {{ $json.users[1].email }} → "dawn@test.com"
Exercise 2: Given this JSON, write the path to get the city:
{
"data": {
"profile": {
"address": {
"location": {
"city": "Red Hill"
}
}
}
}
}
Answer
data.profile.address.location.city
In n8n: {{ $json.data.profile.address.location.city }} → "Red Hill"
Exercise 3: Given this JSON, write the path to get the third tag of the first article:
{
"articles": [
{
"title": "Getting Started with n8n",
"tags": ["automation", "beginner", "tutorial"]
}
]
}
Answer
articles[0].tags[2]
In n8n: {{ $json.articles[0].tags[2] }} → "tutorial"
Quick Reference Card
JSON SYNTAX CHEAT SHEET
========================
Object: {"key": "value", "key2": 42}
Array: ["item1", "item2", "item3"]
String: "Hello, world!"
Number: 42 or 3.14 or -7
Boolean: true or false
Null: null
ACCESS PATTERNS
===============
Dot notation: data.name
Bracket notation: data["name"] (use for keys with spaces/special chars)
Array index: data.items[0] (zero-indexed)
Nested: data.user.address.suburb
COMMON OPERATIONS (JavaScript)
==============================
JSON.parse(string) → string to object
JSON.stringify(obj) → object to string
JSON.stringify(obj, null, 2) → pretty-printed string
COMMON OPERATIONS (Python)
==========================
json.loads(string) → string to dict
json.dumps(obj) → dict to string
json.dumps(obj, indent=2) → pretty-printed string
JQ CHEAT SHEET
==============
. → pretty-print entire JSON
.key → get value of key
.nested.key → get nested value
.[0] → first array element
.[] → all array elements
.[] | select(.field == "x") → filter array
.[] | {name, email} → extract specific fields
length → array/object length
keys → array of keys in object
Final Thoughts
JSON is the universal language of data exchange. If you’re working with n8n, Home Assistant, APIs, or any modern automation tool, you’re already swimming in JSON — you just need to learn to read it.
The key insights:
- There are only 6 types — string, number, boolean, null, array, object. Everything is built from these.
- Objects are key-value pairs (
{}), arrays are ordered lists ([]). - Nesting is just going deeper —
data.user.address.suburbis like navigating a folder structure. - The syntax rules are strict — double quotes only, no trailing commas, no comments. Get one character wrong and it won’t parse.
- Practice beats theory — open n8n, look at the JSON in your node outputs, and write expressions to access different fields. That’s the fastest way to learn.
Once you’re comfortable with JSON, everything else in n8n (and HA, and APIs) becomes dramatically easier.
This guide was compiled from JSON specification (RFC 8259), MDN Web Docs, and practical experience with n8n, Home Assistant, and REST APIs. Last updated: July 2026.