json.loads
JSON text in, Python objects out — and a JSONDecodeError that tells you the exact line, column and character where the text stopped being JSON.
Common call
json.loads('{"a": 1}')
Returns
{'a': 1} — dict, list, str, int, float, bool or None
Replaces
eval() on data (unsafe) and hand-written parsing
Watch out
Python repr is not JSON: single quotes and True fail
json.loads(ss — The JSON document. bytes are decoded as UTF-8/16/32 (detected automatically).type: str | bytes | bytearray · required, *, clscls — Custom decoder class; the other keyword arguments are passed to it.type: JSONDecoder subclass · default: None=None, object_hookobject_hook — Called with every decoded dict; its return value replaces the dict.type: callable · default: None=None, parse_floatparse_float — Called with the text of every JSON float, e.g. decimal.Decimal for exact decimals.type: callable · default: None=None, parse_intparse_int — Called with the text of every JSON int.type: callable · default: None=None, parse_constantparse_constant — Called with '-Infinity', 'Infinity' or 'NaN' — raise here to reject them.type: callable · default: None=None, object_pairs_hookobject_pairs_hook — Called with the (key, value) pairs of every object, in order — sees duplicate keys. Takes priority over object_hook.type: callable · default: None=None, **kw)
→ dict | list | str | int | float | bool | None
Demo
Live evaluation
Type any JSON document. The result is the Python value — note True, None and the Python quotes.
Try:
Inputs
textstra JSON document
Code
import json json.loads('{"name": "Ada", "admin": true, "boss": null}')
Result
{'name': 'Ada', 'admin': True, 'boss': None}
The error position counts from 1 for line and column and from 0 for char. For "missing comma" the parser reads the value 1, skips the whitespace after it, then expects , or } — so the error points at column 9, the opening quote of "b": the first character that could not continue the object.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| s | str | bytes | bytearray | yes | The JSON document. bytes are decoded as UTF-8/16/32 (detected automatically). |
| object_hook | callable | no (None) | Called with every decoded dict; its return value replaces the dict. |
| object_pairs_hook | callable | no (None) | Called with the (key, value) pairs of every object, in order — sees duplicate keys. Takes priority over object_hook. |
| parse_float | callable | no (None) | Called with the text of every JSON float, e.g. decimal.Decimal for exact decimals. |
| parse_int | callable | no (None) | Called with the text of every JSON int. |
| parse_constant | callable | no (None) | Called with '-Infinity', 'Infinity' or 'NaN' — raise here to reject them. |
| cls | JSONDecoder subclass | no (None) | Custom decoder class; the other keyword arguments are passed to it. |
Return value
dict | list | str | int | float | bool | None — The Python value of the JSON document.
Common patterns
Parse or report
JSONDecodeError is a ValueError subclass with the location built in.
import json try: payload = json.loads(body) except json.JSONDecodeError as e: print(f"bad JSON at line {e.lineno}, column {e.colno}: {e.msg}")
Exact decimals for money
Parse floats as Decimal so 19.99 stays 19.99.
import json from decimal import Decimal prices = json.loads(text, parse_float=Decimal)
Objects straight into your own type
object_hook builds something other than a dict for every JSON object.
import json from types import SimpleNamespace user = json.loads(text, object_hook=lambda d: SimpleNamespace(**d)) user.name
Examples
1. Nested document
import json
json.loads('{"user": {"tags": ["a", "b"]}}')['user']['tags']
Returns
['a', 'b']2. bytes work too
import json
json.loads(b'{"ok": true}')
Returns
{'ok': True}3. Duplicate keys: last one wins
import json
json.loads('{"a": 1, "a": 2}')
Returns
{'a': 2}4. object_pairs_hook sees all pairs
import json
json.loads('{"a": 1, "a": 2}', object_pairs_hook=list)
Returns
[('a', 1), ('a', 2)]5. Decimal instead of float
import json
from decimal import Decimal
json.loads('{"price": 19.99}', parse_float=Decimal)
Returns
{'price': Decimal('19.99')}6. NaN and Infinity are accepted
import json
json.loads('[NaN, -Infinity]')
Returns
[nan, -inf]7. A BOM is rejected
import json
json.loads('\ufeff{}')
Returns
json.decoder.JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)Pitfalls
1. Passing a file name instead of file content
loads parses the string you give it. A path is just text, and "data.json" is not a JSON value.
loads(path)
import json json.loads('data.json')
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
load(open file)
import json from pathlib import Path Path('data.json').write_text('{"ok": true}') with open('data.json') as f: data = json.load(f) data
{'ok': True}
2. Rejecting NaN you did not expect
Python accepts NaN/Infinity by default, though they are not standard JSON. parse_constant lets you refuse them.
accepted silently
import json json.loads('{"score": NaN}')
{'score': nan}
parse_constant
import json def reject(name): raise ValueError(f'{name} is not allowed') json.loads('{"score": NaN}', parse_constant=reject)
ValueError: NaN is not allowed
3. Losing digits in long decimals
JSON floats become Python floats (53-bit). Integers are exact, decimals are not.
float
import json json.loads('0.1234567890123456789')
0.12345678901234568
parse_float=Decimal
import json from decimal import Decimal json.loads('0.1234567890123456789', parse_float=Decimal)
Decimal('0.1234567890123456789')
When to use
Use it
- You have JSON as a str or bytes (HTTP body, message, database column)
- Parsing untrusted data — loads never executes code
Reach for something else
- Reading from a file → json.load(f)
- Python literals (single quotes, True, None) → ast.literal_eval
- Text that has trailing data after the JSON → JSONDecoder().raw_decode
Notes
CPython impl
JSONDecoder.decode via the C scanner in Modules/_json.c — its error texts and positions differ in places from the pure-Python fallback in decoder.py
Positions
lineno and colno count from 1; pos counts code points from 0
Exceptions
JSONDecodeError (a ValueError) for bad JSON; TypeError when s is not str/bytes/bytearray
FAQ
JSON object keys must be in double quotes. The usual cause is Python-style text such as {'a': 1} (produced by str() of a dict) or a trailing comma before }. Produce JSON with json.dumps instead of str().