JSON Tutorial: Syntax, Rules and Working With JSON in JavaScript and Python
JSON is the simplest and most widely used format for exchanging data; with a few short rules and two functions in each language, you can read, create and store any data.
If you work with APIs, mobile apps or even the configuration file of a project, sooner or later you will come across JSON. JSON is a text format for storing and moving data that people can read and every programming language can process with ease. This tutorial starts with the definition and the syntax rules, walks through a real example, then moves on to reading and writing JSON in JavaScript and Python, with special attention to accented letters and other non-English text, and finally covers common errors and how to track them down with the JSON Formatter. Every piece of code on this page has been run, and the outputs are real.
What is JSON?
JSON stands for JavaScript Object Notation. Its syntax comes from the way objects are written in JavaScript, but it is not tied to any language: Python, PHP, Java, C#, Go and practically every other language have ready-made tools for working with it. JSON is defined in the RFC 8259 and ECMA-404 standards, its files use the .json extension, and its media type on the web is application/json. Common uses:
- Web APIs: most modern services send their requests and responses as JSON.
- Configuration files: such as
package.jsonin Node.js projects orcomposer.jsonin PHP. - Data storage: in the browser's
localStorage, in the JSON columns of databases such as PostgreSQL and MySQL, and in document databases.
JSON syntax rules
Every JSON document is built from six types of value, which can be nested inside each other:
| Type | Example | Description |
|---|---|---|
| object | {"city": "Lisbon"} | Key-value pairs inside curly braces |
| array | [1, 2, 3] | An ordered list of values inside square brackets |
| string | "hello" | Text inside double quotes |
| number | 42, -3.5, 1.2e6 | Integer or decimal, without quotes |
| boolean | true, false | Lowercase only |
| null | null | The absence of a value |
The rules behind most errors:
- Keys are always strings in double quotes:
{"name": "Ali"}is correct and{name: "Ali"}is not. - Only double quotes are allowed;
'Ali'in single quotes is not valid JSON. - Trailing commas are not allowed: no comma may follow the last member of an object or an array.
- There are no comments: neither
//nor/* */. - Language-specific values are not allowed:
undefined,NaN,Infinity,TrueandNoneare not valid JSON. - Numbers cannot start with a zero:
012is invalid. That is why phone numbers, postal codes and similar IDs should be written as strings, such as"0123456789"; that way the leading zero is kept as well. - Special characters inside strings are escaped:
\"for a double quote,\\for a backslash,\nfor a new line and\uXXXXfor any Unicode character. - Spaces and line breaks between the parts do not matter; that is why JSON can be written both compact on a single line and neatly indented.
A real example: an online shop order
{
"orderId": 10245,
"createdAt": "2026-09-17T10:30:00Z",
"customer": {
"name": "Maria",
"phone": "0123456789",
"city": "Lisbon"
},
"items": [
{ "title": "Python for Beginners", "price": 32, "qty": 1 },
{ "title": "Wireless Mouse", "price": 45, "qty": 2 }
],
"paid": true,
"discountCode": null
}
This example shows every type of value. customer is a nested object and items is an array of objects. The prices are numbers, but the phone number is a string so that its leading zero is kept. JSON has no "date" type, so the order time is a string in the standard ISO 8601 format. And discountCode uses null to say that this order had no discount code. To read the price of the second item, you write order.items[1].price in JavaScript and order["items"][1]["price"] in Python.
Working with JSON in JavaScript
JavaScript has two built-in functions that are enough for most tasks: JSON.parse turns JSON text into an object, and JSON.stringify turns an object into JSON text.
Reading JSON with JSON.parse
const text = '{"name": "Maria", "age": 28, "skills": ["JavaScript", "Python"], "active": true}';
const user = JSON.parse(text);
console.log(user.name); // Maria
console.log(user.skills[0]); // JavaScript
console.log(user.active); // true
Creating JSON with JSON.stringify
const product = { title: "Café Guide", price: 32, tags: ["travel", "bestseller"], discount: null };
console.log(JSON.stringify(product));
console.log(JSON.stringify(product, null, 2));
The first call gives compact, single-line JSON, which suits sending over the network, and the second, with 2 as the third argument, gives readable output indented by two spaces. The second argument is for filtering or transforming values and usually stays null. The output:
{"title":"Café Guide","price":32,"tags":["travel","bestseller"],"discount":null}
{
"title": "Café Guide",
"price": 32,
"tags": [
"travel",
"bestseller"
],
"discount": null
}
Accented letters and other non-ASCII text stay exactly as they are in the output of JSON.stringify; they are not turned into \u escapes.
Fetching JSON from an API and saving it in localStorage
async function loadProduct() {
const res = await fetch("https://api.example.com/products/1");
return await res.json();
}
localStorage.setItem("cart", JSON.stringify({ items: [101, 205] }));
const cart = JSON.parse(localStorage.getItem("cart"));
console.log(cart.items.length); // 2
The res.json() method parses the response itself and returns an object; do not pass its result to JSON.parse again. localStorage only stores strings, so the object is converted with JSON.stringify when it is saved and with JSON.parse when it is read.
Values that JSON.stringify changes
const data = { a: undefined, b: () => 1, c: NaN, d: new Date(Date.UTC(2026, 8, 17)) };
console.log(JSON.stringify(data));
// {"c":null,"d":"2026-09-17T00:00:00.000Z"}
Keys whose value is undefined or a function are silently dropped, NaN and Infinity become null, and a date becomes an ISO string. When you parse the result again, the date stays a string and does not turn back into a Date on its own.
Handling errors
try {
JSON.parse("{'name': 'Ali'}");
} catch (err) {
console.log(err.name); // SyntaxError
console.log(err.message); // Expected property name or '}' in JSON at position 1 (line 1 column 2)
}
Any invalid text throws a SyntaxError, so process data that comes from outside inside try...catch. The message above is the output of Node.js, which matches Chrome; Firefox and Safari report the same error in different words.
Working with JSON in Python
In Python, the standard json module does all the work, and there is nothing to install. It has four main functions: json.loads and json.dumps work with strings, while json.load and json.dump work directly with files; the s in the first two stands for string.
Turning a JSON string into a dictionary
import json
text = '{"name": "Maria", "age": 28, "skills": ["JavaScript", "Python"], "active": true}'
user = json.loads(text)
print(user["name"]) # Maria
print(type(user)) # <class 'dict'>
print(user["active"]) # True
| JSON | Python | JavaScript |
|---|---|---|
| object | dict | Object |
| array | list | Array |
| string | str | String |
| number | int or float | Number |
true / false | True / False | true / false |
null | None | null |
Converting data to JSON, and the non-ASCII text problem
product = {"title": "Café Guide", "price": 32, "tags": ["travel", "bestseller"], "discount": None}
print(json.dumps(product))
print(json.dumps(product, ensure_ascii=False, indent=2))
The first line of output may surprise you:
{"title": "Caf\u00e9 Guide", "price": 32, "tags": ["travel", "bestseller"], "discount": null}
By default, json.dumps writes every non-ASCII character in the form \uXXXX, so é becomes \u00e9, and text in any other script is escaped character by character in the same way. This output is valid and every program reads it correctly, but people cannot read it, and it takes up more space. With ensure_ascii=False the text is written exactly as it is, and indent=2 lays the output out just like the JavaScript example.
Reading and writing JSON files
with open("product.json", "w", encoding="utf-8") as f:
json.dump(product, f, ensure_ascii=False, indent=2)
with open("product.json", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["tags"][0]) # travel
Two important points: pass ensure_ascii=False to json.dump as well, and always open the file with encoding="utf-8". On Windows, if you do not specify the encoding, Python may fall back to the system's default encoding, which is not UTF-8; the result is either a UnicodeEncodeError or a file full of garbled characters.
The output of print is not JSON
print(product)
# {'title': 'Café Guide', 'price': 32, 'tags': ['travel', 'bestseller'], 'discount': None}
A very common mistake is to paste the print output of a dictionary somewhere as JSON. That text is Python's own representation of the data: it has single quotes and None instead of null, so no JSON parser will accept it. To create JSON, always use json.dumps.
Handling errors in Python
try:
json.loads("{'name': 'Ali'}")
except json.JSONDecodeError as e:
print(e) # Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
json.JSONDecodeError is a subclass of ValueError, and its lineno, colno and pos attributes give the position of the error. If you use the requests library, response.json() does the same conversion for an API response. One subtle difference: by default, Python both accepts NaN in its input and writes it in its output, even though this value is not standard JSON; to prevent that, pass allow_nan=False to json.dumps.
Common JSON errors and what the messages mean
| Mistake | Invalid example | JavaScript message (Node.js and Chrome) | Python message |
|---|---|---|---|
| Single quotes or an unquoted key | {'name': 'Ali'} | Expected property name or '}' | Expecting property name enclosed in double quotes |
| Trailing comma in an object | {"a": 1,} | Expected double-quoted property name | Python 3.14: Illegal trailing comma before end of object; Python 3.12: Expecting property name enclosed in double quotes |
| Trailing comma in an array | [1, 2,] | Unexpected token ']' | Python 3.14: Illegal trailing comma before end of array; Python 3.12: Expecting value |
| Missing comma, or a comment after a value | {"a": 1 "b": 2} | Expected ',' or '}' after property value | Expecting ',' delimiter |
| A Python or JavaScript value | {"a": True} | Unexpected token 'T' | Expecting value |
| A number with a leading zero | {"phone": 0123456789} | Unexpected number | Expecting ',' delimiter |
| Unterminated string | {"a": "x} | Unterminated string | Unterminated string starting at |
| Empty input | Empty text | Unexpected end of JSON input | Expecting value |
The messages usually give the position as well, such as position 8 or line 1 column 9, and the real problem is often just before that point. If a JavaScript message starts with Unexpected token '<', you have probably received an HTML page instead of JSON, for example a 404 error page or a login page; the guide to the Unexpected token error walks through that case. A file saved with a BOM gives the error Unexpected UTF-8 BOM in Python; open it with encoding="utf-8-sig" or save it without the BOM.
Format and validate with the JSON Formatter
When a long JSON document arrives on a single line, or you cannot work out why it will not parse, the JSON Formatter is the quickest way forward. The tool works with the browser's own JSON parser, and the data never leaves your browser, so you can safely inspect API responses that contain tokens or user data. The steps:
- Paste the JSON into the Input JSON box; it is formatted immediately, and the Format button does the same.
- Choose the indentation from the Indent menu: 2 spaces, 4 spaces or Tab.
- If the JSON is invalid, a message starting with
Invalid JSON:shows the browser's error text, which usually includes the position of the problem; use the table above to work out what it means and fix it. - The Tree tab shows the data as a collapsible tree, and Expand all and Collapse all open or close every branch; very handy for finding a key in a large response.
- To send or store the data compactly, Minify removes all spaces and line breaks. Copy the result with Copy.
When your code needs types for the JSON it receives, JSON to Types generates TypeScript, Go or Python definitions from a sample; the guide on typing API responses in TypeScript walks through it.