From 85ca47b32c8a480e6e20aa39b43e65b59d3f53a2 Mon Sep 17 00:00:00 2001 From: Paul Knopf Date: Wed, 19 Aug 2026 07:07:29 -0400 Subject: [PATCH] feat: add JSON output format for diagram export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add .json as a supported export format that serializes the d2target.Diagram to pretty-printed JSON. This provides a structured, machine-readable representation of the rendered diagram including all shapes, connections, positions, and styling. The JSON output skips SVG rendering entirely — it marshals the already-computed diagram after layout and export, making it the fastest output format. Changes: - d2cli/export.go: Register JSON extension (.json) in supported extensions, stdout format map, and supported stdout formats - d2cli/main.go: Add JSON handling in _render() that marshals diagram via json.MarshalIndent and writes to output - d2cli/help.go: Add file.json to usage and description text - d2cli/export_test.go: Add test cases for file and stdout JSON output Usage: d2 input.d2 output.json d2 --format json input.d2 - --- d2cli/export.go | 6 ++++-- d2cli/export_test.go | 17 +++++++++++++++++ d2cli/help.go | 4 ++-- d2cli/main.go | 14 +++++++++++++- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/d2cli/export.go b/d2cli/export.go index c39657dc53..51e98372e4 100644 --- a/d2cli/export.go +++ b/d2cli/export.go @@ -14,8 +14,9 @@ const PPTX exportExtension = ".pptx" const PDF exportExtension = ".pdf" const SVG exportExtension = ".svg" const TXT exportExtension = ".txt" +const JSON exportExtension = ".json" -var SUPPORTED_EXTENSIONS = []exportExtension{SVG, PNG, PDF, PPTX, GIF, TXT} +var SUPPORTED_EXTENSIONS = []exportExtension{SVG, PNG, PDF, PPTX, GIF, TXT, JSON} var STDOUT_FORMAT_MAP = map[string]exportExtension{ "png": PNG, @@ -25,9 +26,10 @@ var STDOUT_FORMAT_MAP = map[string]exportExtension{ "pdf": PDF, "pptx": PPTX, "gif": GIF, + "json": JSON, } -var SUPPORTED_STDOUT_FORMATS = []string{"png", "svg", "ascii", "txt", "pdf", "pptx", "gif"} +var SUPPORTED_STDOUT_FORMATS = []string{"png", "svg", "ascii", "txt", "pdf", "pptx", "gif", "json"} func getOutputFormat(stdoutFormatFlag *string, outputPath string) (exportExtension, error) { if stdoutFormatFlag != nil && *stdoutFormatFlag != "" { diff --git a/d2cli/export_test.go b/d2cli/export_test.go index 6022c65d07..c4ff7202f4 100644 --- a/d2cli/export_test.go +++ b/d2cli/export_test.go @@ -83,6 +83,23 @@ func TestOutputFormat(t *testing.T) { requiresAnimationInterval: true, requiresPngRender: true, }, + { + stdoutFormatFlag: "json", + outputPath: "-", + extension: JSON, + supportsDarkTheme: false, + supportsAnimation: false, + requiresAnimationInterval: false, + requiresPngRender: false, + }, + { + outputPath: "/out.json", + extension: JSON, + supportsDarkTheme: false, + supportsAnimation: false, + requiresAnimationInterval: false, + requiresPngRender: false, + }, } for _, tc := range testCases { diff --git a/d2cli/help.go b/d2cli/help.go index 6344018f7e..ec0bd9b1ce 100644 --- a/d2cli/help.go +++ b/d2cli/help.go @@ -19,13 +19,13 @@ import ( func help(ms *xmain.State) { fmt.Fprintf(ms.Stdout, `%[1]s %[2]s Usage: - %[1]s [--watch=false] [--theme=0] file.d2 [file.svg | file.png | file.pdf | file.pptx | file.gif | file.txt] + %[1]s [--watch=false] [--theme=0] file.d2 [file.svg | file.png | file.pdf | file.pptx | file.gif | file.txt | file.json] %[1]s layout [name] %[1]s fmt file.d2 ... %[1]s play [--theme=0] [--sketch] file.d2 %[1]s validate file.d2 -%[1]s compiles and renders file.d2 to file.svg | file.png | file.pdf | file.pptx | file.gif | file.txt +%[1]s compiles and renders file.d2 to file.svg | file.png | file.pdf | file.pptx | file.gif | file.txt | file.json It defaults to file.svg if an output path is not provided. Use - to have d2 read from stdin or write to stdout. diff --git a/d2cli/main.go b/d2cli/main.go index 8fe67de61f..02505ed529 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -105,7 +105,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } - stdoutFormatFlag := ms.Opts.String("", "stdout-format", "", "", "output format when writing to stdout (svg, png, ascii, txt, pdf, pptx, gif). Usage: d2 input.d2 --stdout-format png - > output.png") + stdoutFormatFlag := ms.Opts.String("", "stdout-format", "", "", "output format when writing to stdout (svg, png, ascii, txt, pdf, pptx, gif, json). Usage: d2 input.d2 --stdout-format png - > output.png") if err != nil { return err } @@ -940,6 +940,18 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts } return ascii, nil } + if outputFormat == JSON { + jsonBytes, err := json.MarshalIndent(diagram, "", " ") + if err != nil { + return nil, err + } + jsonBytes = append(jsonBytes, '\n') + err = Write(ms, outputPath, jsonBytes) + if err != nil { + return jsonBytes, err + } + return jsonBytes, nil + } toPNG := outputFormat == PNG var scale *float64