Add JSON render output format
Problem
The render pipeline outputs visual formats (SVG, HTML, PNG, PDF, terminal) but has no structured-data output. AI agents calling via MCP get expensive HTML blobs they can't reason over. Tests that want to assert on resolved chart decisions (auto type, auto fields) must parse SVG. There's no way to get "what did the engine decide?" as data.
Context
dataface/core/render/renderer.py— top-levelrender()with format dispatch (svg/html/png/pdf/terminal)dataface/core/render/chart/rendering.py—_render_chart_item_inner()does: execute query → resolve chart → render SVG. The resolution step (resolve_chart()) is where auto chart type, auto fields, enrichment all happen.dataface/core/render/chart/pipeline.py—resolve_chart()returnsResolvedChartdataclassdataface/core/render/chart/models.py—ResolvedChart(dataclass),ChartIntent,EnrichmentPatchdataface/core/compile/compiled_types.py—CompiledFace(Pydantic),CompiledChart,LayoutItemdataface/core/render/faces.py—render_face_svg()walks the layout tree- Chart-level JSON already exists (
render_chart(format="json")) but produces Vega-Lite specs, not resolved semantics - Related: issue-369 (text/describe format for AI agents) — this is the foundation step
Possible Solutions
-
Recommended — Light wrapper: walk layout, serialize resolved objects + data. Add
format="json"torender(). Walk the layout tree likerender_face_svgdoes, but instead of building SVG, execute queries, resolve charts viaresolve_chart(), and collect into dicts. Usedataclasses.asdict()for ResolvedChart,model_dump()for CompiledFace/LayoutItem. Attach executed query data. Returnjson.dumps(). No new schema file — the output shape is defined by the existing models. Evolves naturally as models evolve. -
Define a separate JSON schema / Pydantic model for the output. More explicit contract, but adds a parallel type hierarchy that must be kept in sync with the compile/render models. Premature at this stage.
Plan
Files to modify:
- dataface/core/render/renderer.py — add elif format == "json" branch calling _to_json()
- dataface/core/render/json_format.py (new) — render_face_json() that walks layout tree, executes queries, resolves charts, serializes
Tests:
- tests/core/test_json_format.py (new) — test JSON output contains resolved chart type, executed data, layout structure
Steps:
1. Write failing test: render a simple face with an auto-type chart, assert JSON output contains resolved chart_type and data
2. Implement render_face_json() — walk layout items, for each chart: execute query, resolve, serialize. For nested faces: recurse.
3. Wire into render() as format="json"
4. Run just ci
Implementation Progress
2026-03-17: Implementation complete
Files created:
- dataface/core/render/json_format.py — render_face_json() walks layout tree, executes queries via executor.execute_chart(), resolves charts via resolve_chart(), serializes with dataclasses.asdict() (skipping source_chart), returns JSON string.
- tests/core/test_json_format.py — 7 tests covering: basic output, resolved fields, executed data, auto type resolution, nested faces, multiple charts, KPI charts.
Files modified:
- dataface/core/render/renderer.py — Added early return for format="json" before SVG rendering (no SVG overhead).
- dataface/cli/commands/render.py — JSON output prints to stdout (like terminal format).
- dataface/cli/main.py — Updated --format help text to include "json".
Key decisions:
- JSON format short-circuits before SVG rendering in render() — no unnecessary SVG overhead.
- Uses dataclasses.asdict() for ResolvedChart with source_chart popped (avoids circular serialization of the full CompiledChart).
- Uses model_dump() for Pydantic Variable objects.
- Custom _json_default handles DotDict (→ dict) and set (→ sorted list).
- Output shape: {id, title, variables?, items: [{type: "chart", chart: {...}, data: [...]}, {type: "face", face: {id, title, items: [...]}}]}
QA Exploration
- [x] QA exploration completed (or N/A for non-UI tasks)
Review Feedback
- [ ] Review cleared