Streaming JSON parser that yields partial valid trees as tokens arrive. For LLM tool calls, structured outputs, and partial recovery.
Project description
partial-json-stream
A streaming JSON parser that yields partial valid trees as tokens arrive.
Python port of @mukundakatta/streamparse.
Built for LLM tool-call payloads, structured-output streams, and any place
json.loads waits too long.
from partial_json_stream import JsonStreamParser
p = JsonStreamParser()
p.push('{"name":"Cl')
p.snapshot().value # {'name': 'Cl'}
p.push('aude","tools":[1,2')
p.snapshot().value # {'name': 'Claude', 'tools': [1, 2]}
p.push(']}')
p.end()
p.snapshot().complete # True
Every snapshot is valid JSON. Synthetic closure of in-progress strings,
numbers, and containers means you can render it directly, persist it, or
round-trip it through json.loads(json.dumps(...)).
Why
Every Python LLM project ships its own broken version of this. They wait for
the full payload (slow), or hand-roll json.loads in a try/except on a growing
buffer (O(n²) and useless until the very last byte), or use ad-hoc regex.
partial-json-stream is the version you should reuse.
Install
pip install partial-json-stream
Zero runtime dependencies. Python 3.9+.
API
JsonStreamParser(lenient=True, max_depth=256, max_string_length=None)
Lenient mode (default) tolerates the LLM-isms you actually see in production:
- trailing commas:
{"a": 1,} - single-quoted strings:
{'a': 'hi'} - unquoted keys:
{a: 1, b: 2} ```jsoncode fences// lineand/* block */comments- prose before/after the JSON:
Sure! Here it is: {...}
Set lenient=False for strict RFC 8259 mode.
parser.push(chunk: str) -> None
Feed in more bytes.
parser.end() -> None
Signal end of input. Lenient mode silently closes any open containers.
parser.snapshot() -> Snapshot
Take a snapshot. Always returns valid JSON.
@dataclass
class Snapshot:
value: Any
complete: bool
path: list # cursor location, e.g. ['tools', 2, 'input', 'path']
bytes_in: int
confidence: float # 0..1
parser.on(event, fn)
Subscribe to events. Returns an unsubscribe function.
| event | callback signature | when |
|---|---|---|
field |
(path, value) |
every leaf commit |
container |
(path, value) |
every {} or [] close |
partial |
(snapshot) |
every push() |
complete |
(value) |
once, when top-level value closes |
error |
(err) |
on syntax error (suppresses raise) |
parse_partial(text: str, lenient=True) -> Any
One-shot helper for a possibly-truncated blob.
from partial_json_stream import parse_partial
truncated = '{"type":"tool_use","name":"edit","input":{"path":"a/b.ts","cont'
parse_partial(truncated)
# {'type': 'tool_use', 'name': 'edit', 'input': {'path': 'a/b.ts', 'cont': None}}
Real-world usage
Recover a dropped tool call
from partial_json_stream import parse_partial
# The connection dropped before the model finished writing its tool call.
partial = '{"type":"tool_use","name":"edit_file","input":{"path":"a.ts","patches":[{"line":10,"op":"insert","content":"print('
value = parse_partial(partial)
# value['input']['patches'][0] is fully usable.
Stream Anthropic tool calls
from partial_json_stream import JsonStreamParser
parser = JsonStreamParser()
@parser.on("partial", lambda snap: ui.update(snap.value))
for chunk in client.messages.stream(...).text_stream:
parser.push(chunk)
parser.end()
License
MIT, by Mukunda Katta.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file partial_json_stream-1.0.0.tar.gz.
File metadata
- Download URL: partial_json_stream-1.0.0.tar.gz
- Upload date:
- Size: 13.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
332455e4bacb3b2affd24d7af04178450ed8ee87a3d279b781313ddfc7f2e740
|
|
| MD5 |
edcd839d7416f54ff824f3d8dd5d4e57
|
|
| BLAKE2b-256 |
5958cc793d3106d3ed1b6b48d9607cc69973cf0dde59d1b88f98f532011cdae2
|
File details
Details for the file partial_json_stream-1.0.0-py3-none-any.whl.
File metadata
- Download URL: partial_json_stream-1.0.0-py3-none-any.whl
- Upload date:
- Size: 9.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07f36b47d9bcb436f4d31d5a039733222bedccae2ead3f74ed9b4c74ffca1714
|
|
| MD5 |
3765ecbc8ff35c538006447c789cde05
|
|
| BLAKE2b-256 |
8610ca97941654c8459b0c12b2c8d7b8d12304a4a57de143567552e03ba89a52
|