n4nAI

LangChain output parsers: Pydantic vs JSON vs XML

Compare LangChain output parsers — Pydantic, JSON, and XML — across validation, ergonomics, streaming, and failure modes to pick the right one for your LLM pipeline.

n4n Team6 min read1,257 words

Audio narration

Coming soon — every post will get a voice note here.

LangChain output parsers sit between your model and your application logic, turning raw completions into structured data your code can trust. The three main contenders — Pydantic, JSON, and XML — each make different trade-offs around validation strictness, streaming support, error recovery, and developer ergonomics. This comparison walks through concrete dimensions so you can choose without guessing.

How each parser works

PydanticOutputParser

Wraps a Pydantic model and injects formatting instructions into the prompt. On the return path it calls model.parse() which runs model_validate_json() under the hood. You get full Pydantic validation: type coercion, custom validators, computed fields, and detailed ValidationError objects with field-level context.

from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field, field_validator

class Extraction(BaseModel):
    entities: list[str] = Field(min_length=1)
    confidence: float = Field(ge=0.0, le=1.0)

    @field_validator("entities")
    @classmethod
    def dedupe(cls, v):
        return list(dict.fromkeys(v))

parser = PydanticOutputParser(pydantic_object=Extraction)
prompt = f"Extract entities.\n{parser.get_format_instructions()}"

The parser also exposes parser.parse_result() for handling Generation objects directly from LLMResult.

JsonOutputParser

A lighter wrapper that asks the model for valid JSON and runs json.loads() on the response. It accepts an optional pydantic_object for validation, but the default path skips schema enforcement entirely. Useful when you want structured output without committing to a Pydantic model in your dependency graph.

from langchain.output_parsers import JsonOutputParser

parser = JsonOutputParser()  # no schema enforcement
# or
parser = JsonOutputParser(pydantic_object=MyModel)  # validates after parse

XMLOutputParser

Requests XML with a specified root tag and optional field tags. Parses with xml.etree.ElementTree and returns a dict. No built-in validation — you validate downstream or not at all. Historically favored for models that struggle with JSON escaping but handle angle brackets cleanly.

from langchain.output_parsers import XMLOutputParser

parser = XMLOutputParser(tags=["entities", "confidence"])
# Prompt gets: "Output XML with <entities> and <confidence> tags"

Validation and type safety

Pydantic wins decisively here. You define the contract once in the model and get enforcement at parse time, including nested models, enums, constrained types, and custom validators. A failed parse raises OutputParserException wrapping a ValidationError with error.loc, error.msg, and error.input — everything you need for automated retry or structured logging.

JSON parser with a Pydantic model attached gives you the same validation, but the prompt instructions are weaker. The parser injects a generic “output valid JSON” directive rather than the detailed field-by-field instructions PydanticOutputParser generates. Models occasionally omit required fields or mismatch types because the prompt guidance is thinner.

XML parser offers no validation. You receive a dict of strings. Type coercion, presence checks, and business logic validation are entirely your responsibility.

Streaming support

PydanticOutputParser does not support incremental parsing. You must wait for the full completion, then validate. For long extractions this adds perceived latency.

JsonOutputParser can pair with JsonOutputParser.parse_stream() which yields partial dicts as the stream arrives. This works because JSON is prefix-parseable up to a point — but incomplete objects raise json.JSONDecodeError until the closing brace arrives. LangChain’s implementation buffers until it can emit a valid dict, so you still get discrete chunks rather than true token-level streaming.

XMLOutputParser has no streaming parser in LangChain core. You would need a SAX-style incremental parser (e.g., xml.sax) and custom logic to emit partial results — not provided out of the box.

If you need token-level streaming with structured output, consider function calling / tool use APIs instead of output parsers. The model emits structured arguments incrementally, and the provider handles the framing.

Error recovery and retry strategies

Pydantic’s ValidationError is a rich diagnostic object. You can extract the failed field, the raw value, and the expected type, then feed that back to the model in a retry prompt:

from langchain.output_parsers import OutputFixingParser
from langchain_openai import ChatOpenAI

fixing_parser = OutputFixingParser.from_llm(
    parser=parser,
    llm=ChatOpenAI(model="gpt-4o-mini")
)
result = fixing_parser.parse(bad_completion)

OutputFixingParser works with any base parser but shines with Pydantic because the error context is precise. With JSON or XML parsers, the error is usually “invalid syntax” — less actionable for the fixing model.

JSON parser failures are typically malformed JSON (trailing commas, unescaped newlines, truncated output). The fixing parser can often repair these, but semantic errors (wrong keys, type mismatches) only surface if you attached a Pydantic model.

XML parser failures tend to be unclosed tags or mismatched nesting. The fixing parser handles these reasonably well since XML structure is explicit.

Prompt overhead and token cost

PydanticOutputParser.get_format_instructions() emits a detailed schema description — field names, types, constraints, descriptions, and examples. For a model with 10 fields this can be 300-500 tokens. Worth it for correctness; painful if you’re optimizing for context window or per-token cost.

JsonOutputParser injects a short generic instruction: “Return a JSON object.” ~20 tokens. You save prompt tokens but spend more on retries when the model drifts.

XMLOutputParser sits in between: “Output XML with tags X, Y, Z.” ~50 tokens. Tag names act as implicit schema, giving the model more signal than bare JSON without the verbosity of Pydantic’s full instructions.

Ecosystem integration

Pydantic is the de facto standard for Python data validation. Models integrate with FastAPI, SQLAlchemy, Pydantic Settings, and countless libraries. You can reuse the same model for API contracts, database rows, and LLM output — single source of truth.

JSON parser output is a plain dict. Universal, but you lose type hints and IDE support unless you manually annotate or convert to a model afterward.

XML parser output is a dict of strings. Niche. Useful when downstream systems consume XML (legacy enterprise, some document pipelines) or when the model genuinely produces better XML than JSON. Rare in modern stacks.

Failure modes in production

Scenario Pydantic JSON (no model) JSON + Pydantic XML
Missing required field Caught, precise error Silently returns partial dict Caught, precise error Silently returns partial dict
Type mismatch (string vs int) Coerces or errors with context Silently keeps string Coerces or errors with context Silently keeps string
Extra unexpected fields extra="forbid" rejects Accepts silently extra="forbid" rejects Accepts silently
Truncated output ValidationError with location JSONDecodeError ValidationError ParseError (line/col)
Unicode / escaping issues Handled by JSON parser JSONDecodeError Handled by JSON parser Rare (CDATA helps)
Nested object validation Full recursive validation No validation Full recursive validation No validation

When to use each

PydanticOutputParser — Default choice for production pipelines. You need guaranteed schema compliance, rich error context for retries, and integration with the broader Python type system. Accept the prompt token cost and lack of streaming. Ideal for extraction, classification, agent tool arguments, and any output that feeds typed downstream code.

JsonOutputParser (no model) — Prototyping, exploratory analysis, or when the schema changes per request and defining Pydantic models dynamically is awkward. Also fine for non-critical paths where “best effort” structured output is acceptable and you have a separate validation layer.

JsonOutputParser + Pydantic model — Middle ground. You want validation but prefer writing the schema once in Pydantic and letting the parser handle prompt instructions. Slightly weaker prompt guidance than PydanticOutputParser; marginally fewer prompt tokens. Valid choice if you’re already using JsonOutputParser elsewhere and want consistency.

XMLOutputParser — Two legitimate cases: (1) the model you’re using demonstrably produces better-structured output with XML than JSON (some smaller open models), or (2) you’re piping output directly into an XML-consuming system (XSLT pipelines, legacy document generators). Otherwise, avoid — the validation gap and lack of tooling make it a liability.

Practical pattern: parser as a contract, not a suggestion

Treat the output parser as the boundary contract between your prompt and your application. Version it alongside your prompt template. When the prompt changes, regenerate the parser’s format instructions and run your eval suite.

# parser_registry.py
from functools import lru_cache
from langchain.output_parsers import PydanticOutputParser
from myapp.schemas import ExtractionV1, ExtractionV2

@lru_cache
def get_parser(version: str) -> PydanticOutputParser:
    model = {"v1": ExtractionV1, "v2": ExtractionV2}[version]
    return PydanticOutputParser(pydantic_object=model)

# In your chain factory
def build_chain(version: str):
    parser = get_parser(version)
    prompt = PromptTemplate(
        template="Extract...\n{format_instructions}",
        partial_variables={"format_instructions": parser.get_format_instructions()}
    )
    return prompt | llm | parser

This gives you reproducible parses, testable parser versions, and a clear upgrade path when schemas evolve.

Verdict

  • Default to PydanticOutputParser for any production workload where correctness matters. The validation, error context, and ecosystem integration pay for themselves in reduced retry logic and fewer silent data bugs.
  • Use JsonOutputParser without a model only for throwaway scripts, rapid prototyping, or highly dynamic schemas where Pydantic model generation is more friction than it’s worth.
  • Use JsonOutputParser with a Pydantic model if you want a single parser class across your codebase and can tolerate slightly weaker prompt instructions.
  • Avoid XMLOutputParser unless you have a measured, documented reason — model behavior or downstream system — that justifies the validation gap.

The parser choice is a contract decision, not a formatting preference. Choose the one that lets you catch failures at the boundary, not in your business logic.

Tagslangchainoutput-parserpydanticcomparison

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All langchain structured output & parsers posts →