json.JSONDecoder
decode(s) is json.loads for str; raw_decode(s, idx) parses one value starting exactly at idx and returns (value, end) — no complaint about what follows, and no skipping of whitespace in front.
Demo
import json json.JSONDecoder().raw_decode('{"a": 1} and more')
raw_decode stops right after the value: "12abc" gives (12, 2), and a leading space is an error at char 0 because nothing is skipped. That is why the loop skips whitespace itself before every call. With strict=True the raw line break is an "Invalid control character"; strict=False keeps it in the string as \n. An unescaped quote ends the JSON string early, so neither mode can parse that document. In object_hook, keys are shown as name=value, and objects that only exist briefly (the first "a" value) are converted too before the later key replaces them.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| strict | bool | no (True) | False allows raw control characters (tab, newline, U+0000–U+001F) inside strings. Nothing else becomes lenient. |
| object_hook | callable | no (None) | Called with every decoded dict, innermost first; its return value replaces the dict. |
| object_pairs_hook | callable | no (None) | Called with the list of (key, value) pairs of every object, duplicates included. Wins over object_hook. |
| parse_float | callable | no (None) | Called with the text of every JSON float (float by default). |
| parse_int | callable | no (None) | Called with the text of every JSON int (int by default). |
| parse_constant | callable | no (None) | Called with '-Infinity', 'Infinity' or 'NaN'. |
Return value
JSONDecoder — A reusable decoder; call .decode(s) or .raw_decode(s, idx=0).
Common patterns
import json def iter_json(text): dec = json.JSONDecoder() pos = 0 while True: while pos < len(text) and text[pos] in ' \t\n\r': pos += 1 if pos == len(text): return obj, pos = dec.raw_decode(text, pos) yield obj
import json start = text.index('{') data, end = json.JSONDecoder().raw_decode(text, start)
import json data = json.loads(text, strict=False) # same as JSONDecoder(strict=False).decode(text)
Examples
Pitfalls
import json text = ' {"a": 1}' json.JSONDecoder().raw_decode(text)
import json text = ' {"a": 1}' idx = len(text) - len(text.lstrip(' \t\n\r')) json.JSONDecoder().raw_decode(text, idx)
import json json.loads('{"a": 1}{"b": 2}')
import json text = '{"a": 1}{"b": 2}' dec = json.JSONDecoder() items, pos = [], 0 while pos < len(text): obj, pos = dec.raw_decode(text, pos) items.append(obj) items
import json json.JSONDecoder(strict=False).decode("{'a': 1}")
import ast ast.literal_eval("{'a': 1}")
When to use
- Several JSON values in one string (concatenated output, a log line with JSON in it)
- Knowing where a JSON value ends inside larger text
- A decoder with fixed hooks you reuse many times
- Plain parsing → json.loads (also accepts bytes, and checks for a BOM)
- One document per line → json.loads on each line
- Lenient JSON (comments, trailing commas) → a JSON5 / HJSON library
Notes
FAQ
If they are one per line, call json.loads on each line. If they are glued together or separated by arbitrary whitespace, loop with JSONDecoder().raw_decode(text, pos): it returns the value and the index where it ended, which is where the next one starts (skip whitespace first).