JSONOutputParser (json_parser.py)

On this page

class output_parser.json_parser.JSONOutputParser(BaseOutputParser)

Overview

Extracts and parses a JSON object or array from raw LLM text, with optional validation against a Pydantic model. This is the standalone-toolkit counterpart to the orchestrator's internal _parse_json function in parser.py — the extraction algorithm is similar (markdown fence first, then raw_decode scanning) but this class additionally supports Pydantic casting and raises ParsingError with rich context instead of a bare ValueError.

Depends on base_parser.py (BaseOutputParser, ParsingError) and the standard library (json, re); optionally pydantic (imported lazily inside _validate_pydantic_model, not at module load time).

Constructor

Python
JSONOutputParser(pydantic_model: Optional[Type] = None)
NameRequiredDefaultNotes
pydantic_modelNoNoneA Pydantic BaseModel subclass. If provided, __init__ immediately calls _validate_pydantic_model which raises ImportError if pydantic isn't installed, or ValueError if pydantic_model isn't actually a BaseModel subclass.

Public Methods

parse

Python
def parse(self, text: str) -> Union[Dict[str, Any], list, Any]

Parameters: text: str — raw text containing JSON somewhere.

Returns: a dict or list (if no pydantic_model), or an instance of pydantic_model (if one was configured).

Raises:

  • ParsingError("Cannot parse empty text", ...) if text is empty/whitespace.
  • ParsingError("No valid JSON found in text. ...", ...) if _extract_json returns None.
  • ParsingError(f"Invalid JSON: {e.msg} at line {e.lineno}, col {e.colno}", ...) if the extracted candidate string fails json.loads — this wraps the underlying json.JSONDecodeError as cause.
  • ParsingError(f"Expected JSON object for Pydantic model, got {type}", ...) if pydantic_model is set but the extracted JSON is a list/scalar instead of an object.
  • ParsingError(f"JSON does not match schema for {name}: {e}", ...) if Pydantic construction (pydantic_model(**data)) raises.

Internal Workflow / Algorithm: _extract_json(text) tries two strategies in order:

  1. Regex-search for a ```json ... ``` or bare ``` ... ``` fence; if found, tries json.loads() on the inner content — if that succeeds, returns it immediately.
  2. Falls through to a json.JSONDecoder().raw_decode scan: for each of { and [, finds every occurrence in text and attempts raw_decode starting at that index, advancing to the next occurrence on failure, until one succeeds. This handles JSON embedded anywhere in surrounding prose, not just at the very start of the string.

If neither strategy finds anything, _extract_json returns None.

Complexity: O(n·m) worst case, where n is text length and m is the number of {/[ occurrences scanned before a valid one is found (each raw_decode attempt itself is O(k) in the length of the JSON candidate). In typical well-formed LLM output, this resolves on the first or second candidate.

Thread Safety: Safe to share one instance across threads for concurrent parse() calls — no mutable state beyond the read-only pydantic_model attribute.

Side Effects: None beyond raising exceptions on failure — no logging, no caching, no I/O.

get_format_instructions

Python
def get_format_instructions(self) -> str

If pydantic_model is set, returns instructions embedding self.pydantic_model.model_json_schema() as a fenced JSON block. Otherwise returns a generic "double quotes for keys/strings" instruction with a hardcoded example.

Private Helpers

  • _extract_json(text) -> Optional[str] — see Algorithm above.
  • _validate_pydantic_model(model) -> None — raises ImportError if pydantic missing, ValueError if model isn't a BaseModel subclass.

Examples

Python · 28 lines
# Basic
parser = JSONOutputParser()
result = parser.parse('Here is the data: {"name": "John", "age": 30}')
# {'name': 'John', 'age': 30}

# Intermediate: JSON array
result = parser.parse('[1, 2, 3]')
# [1, 2, 3]

# Advanced: Pydantic validation
from pydantic import BaseModel
class User(BaseModel):
    name: str
    age: int
parser = JSONOutputParser(pydantic_model=User)
user = parser.parse('{"name": "John", "age": 30}')
# User(name='John', age=30)

# Error handling
try:
    parser.parse("no json here at all")
except ParsingError as e:
    print(e.original_text)   # "no json here at all"

# Production usage: fenced code block from a chat-style LLM response
raw = '''Sure, here's the JSON:
```json
{"status": "ok", "items": [1, 2, 3]}

Let me know if you need anything else.''' result = JSONOutputParser().parse(raw)

code · 20 lines

## Best Practices

- Prefer this over the orchestrator's internal `_parse_json` when you specifically want Pydantic validation baked into the parse call itself, or when you want `ParsingError`'s rich `original_text`/`cause` context for debugging.
- Combine with `AutoFixingParser` (from `base_parser.py`) if you want automatic retry-with-fix behavior without pulling in the full `OutputParser` orchestrator.

## Common Mistakes

- Passing a non-`BaseModel` class as `pydantic_model` — this raises `ValueError` immediately at construction time, not at parse time, so the error surfaces early (which is generally desirable, but can be surprising if you're constructing the parser far from where you use it).
- Expecting `parse()` to extract the *last* or *largest* JSON block in text with multiple candidates — it returns the *first* one found by the scanning order described above (fence first, then leftmost `{`/`[` in the raw text).

## Related APIs

- [`PydanticOutputParser`](pydantic_parser.md) — wraps this class internally for the Pydantic-first use case.
- [`BaseOutputParser`, `ParsingError`](base_parser.md)
- [`OutputParser`](output_parser.md) — the orchestrator's own internal `_parse_json` function is a simpler, non-class variant of this same extraction algorithm.

## Version History

Not determinable from source.

Related pages