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.

json classPython 2.6+Live demo
Common call
obj, end = json.JSONDecoder().raw_decode(text, pos)
Returns
decode → the value; raw_decode → (value, index after it)
Replaces
splitting concatenated JSON by hand
Watch out
raw_decode fails on leading whitespace; both methods need str, not bytes
json.JSONDecoder(*, object_hookobject_hook — Called with every decoded dict, innermost first; its return value replaces the dict.type: callable · default: None=None, parse_floatparse_float — Called with the text of every JSON float (float by default).type: callable · default: None=None, parse_intparse_int — Called with the text of every JSON int (int by default).type: callable · default: None=None, parse_constantparse_constant — Called with '-Infinity', 'Infinity' or 'NaN'.type: callable · default: None=None, strictstrict — False allows raw control characters (tab, newline, U+0000–U+001F) inside strings. Nothing else becomes lenient.type: bool · default: True=True, object_pairs_hookobject_pairs_hook — Called with the list of (key, value) pairs of every object, duplicates included. Wins over object_hook.type: callable · default: None=None)
→ JSONDecoder

Demo

Live evaluation
Parse one value from the start of the text. The second item is the index just after it — whatever follows is ignored.
Try:
Inputs
textstrJSON, then anything
Code
import json
json.JSONDecoder().raw_decode('{"a": 1} and more')
Result
({'a': 1}, 8)

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

NameTypeRequiredDescription
strictboolno (True)False allows raw control characters (tab, newline, U+0000–U+001F) inside strings. Nothing else becomes lenient.
object_hookcallableno (None)Called with every decoded dict, innermost first; its return value replaces the dict.
object_pairs_hookcallableno (None)Called with the list of (key, value) pairs of every object, duplicates included. Wins over object_hook.
parse_floatcallableno (None)Called with the text of every JSON float (float by default).
parse_intcallableno (None)Called with the text of every JSON int (int by default).
parse_constantcallableno (None)Called with '-Infinity', 'Infinity' or 'NaN'.

Return value

JSONDecoder — A reusable decoder; call .decode(s) or .raw_decode(s, idx=0).

Common patterns

Parse concatenated JSON
Streams and logs sometimes glue documents together; raw_decode walks through them.
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
Pull JSON out of surrounding text
Start at the first brace and ignore whatever comes after the object.
import json
start = text.index('{')
data, end = json.JSONDecoder().raw_decode(text, start)
Tolerate raw control characters
For producers that write tabs or newlines unescaped inside strings.
import json
data = json.loads(text, strict=False)  # same as JSONDecoder(strict=False).decode(text)

Examples

1. raw_decode: value and end index
import json json.JSONDecoder().raw_decode('{"a": 1} trailing text')
Returns
({'a': 1}, 8)
2. Start at an offset
import json text = 'log: {"level": "warn"} end' json.JSONDecoder().raw_decode(text, text.index('{'))
Returns
({'level': 'warn'}, 22)
3. decode() rejects trailing data
import json json.JSONDecoder().decode('{"a": 1} x')
Returns
json.decoder.JSONDecodeError: Extra data: line 1 column 10 (char 9)
4. strict=False: raw tab in a string
import json json.JSONDecoder(strict=False).decode('["a\tb"]')
Returns
['a\tb']
5. strict=True (the default) refuses it
import json json.JSONDecoder().decode('["a\tb"]')
Returns
json.decoder.JSONDecodeError: Invalid control character at: line 1 column 4 (char 3)
6. object_pairs_hook sees every pair
import json json.JSONDecoder(object_pairs_hook=list).decode('{"a": 1, "a": 2}')
Returns
[('a', 1), ('a', 2)]
7. Keep floats as text
import json json.JSONDecoder(parse_float=str).decode('[1.10, 2]')
Returns
['1.10', 2]
8. bytes work in loads, not here
import json json.JSONDecoder().decode(b'[1]')
Returns
TypeError: cannot use a string pattern on a bytes-like object

Pitfalls

1. Leading whitespace in raw_decode
raw_decode starts parsing exactly at idx. Skip the whitespace yourself (decode and loads do it for you).
raw_decode(text)
import json
text = '  {"a": 1}'
json.JSONDecoder().raw_decode(text)
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
skip it first
import json
text = '  {"a": 1}'
idx = len(text) - len(text.lstrip(' \t\n\r'))
json.JSONDecoder().raw_decode(text, idx)
({'a': 1}, 10)
2. json.loads on concatenated documents
loads (and decode) require exactly one value. Several values in a row are "Extra data" — walk them with raw_decode.
json.loads
import json
json.loads('{"a": 1}{"b": 2}')
json.decoder.JSONDecodeError: Extra data: line 1 column 9 (char 8)
raw_decode loop
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
[{'a': 1}, {'b': 2}]
3. Expecting strict=False to accept sloppy JSON
strict only concerns control characters inside strings. Single quotes, trailing commas and comments are still errors; a Python literal needs ast.literal_eval.
strict=False
import json
json.JSONDecoder(strict=False).decode("{'a': 1}")
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
ast.literal_eval
import ast
ast.literal_eval("{'a': 1}")
{'a': 1}

When to use

Use it
  • 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
Reach for something else
  • 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

CPython impl
Lib/json/decoder.py with the C scanner from Modules/_json.c; decode(s) skips whitespace, calls raw_decode, then raises Extra data if anything but whitespace is left
Whitespace
Only space, tab, \n and \r count as JSON whitespace — decode skips those, raw_decode skips nothing
vs json.loads
loads also accepts bytes (detecting UTF-8/16/32) and rejects a leading BOM with its own message; decode() needs str and reports a BOM as "Expecting value"
Hooks
object_pairs_hook takes priority over object_hook when both are given

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).