PlantUML as a structured JSON renderer — turn config / API responses / any JSON into a tree

puml.online

PlantUML’s underrated gem: render JSON / YAML as a structured tree diagram. One directive, zero boilerplate. Faster than hand-drawing Visio, cleaner than Graphviz dot, more truthful than Mermaid flowchart.

The one-liner

1
2
3
4
5
6
7
8
9
10
11
@startjson
{
"name": "puml.online",
"version": "1.2.0",
"stack": {
"frontend": "Hexo + redefine",
"backend": "EdgeOne Pages",
"diagrams": ["PlantUML", "Mermaid"]
}
}
@endjson

Wrap a JSON block in @startjson / @endjson and PlantUML draws it as a tree. The YAML twin is @startyaml / @endyaml. Both ship in PlantUML’s official stdlib — no plugin, no extension.

What problem does this solve

Drawing “nested structure diagrams” is one of the most common documentation chores:

  • Config files — package.json / tsconfig.json / nx.json / docker-compose.yml field hierarchies
  • API responses — what the backend actually returned, especially when the frontend field doesn’t match
  • State snapshots — dump the entire Redux store / Vuex state / Pinia store
  • Domain examples — a real JSON instance conveys schema better than an ER diagram
  • Test fixtures — visualize a mock payload to confirm nesting is correct

The old workflow was (a) manually re-type fields into Visio, (b) write a jq + dot script, or (c) screenshot console.log. @startjson kills (a) and (c) outright, and dramatically simplifies (b) — your script only has to assemble the JSON string, rendering is PlantUML’s job.

Full syntax: all JSON value types supported

PlantUML recognizes and renders all 6 JSON value types differently:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@startjson
{
"string": "text value",
"number": 42,
"boolean": true,
"null_value": null,
"array": [
"first",
"second",
{"nested_in_array": "objects can sit inside arrays too"}
],
"object": {
"depth_2": {
"depth_3": "any depth works"
}
}
}
@endjson

Visual treatment:

  • String → light background + quotes
  • Number → blue
  • Boolean → purple, true / false distinguished
  • null → grey, italic
  • Array → expands as <array> node with [0], [1], … children
  • Object → expands as <object> node with field-name children

Highlight & styling: make the important stuff pop

Single key highlight

1
2
3
4
5
6
7
8
@startjson
#highlight "version"
{
"name": "puml.online",
"version": "1.2.0",
"deprecated": false
}
@endjson

The version node turns orange.

Multiple keys

1
2
3
4
5
6
7
8
9
@startjson
#highlight "version"
#highlight "deprecated"
{
"name": "puml.online",
"version": "1.2.0",
"deprecated": false
}
@endjson

Highlight everything

1
2
3
4
5
6
7
@startjson
#highlight
{
"all": "everything turns pink",
"you": "might not want this"
}
@endjson

Conditional highlight (only when value matches)

1
2
3
4
5
6
7
@startjson
#highlight "deprecated" : false
{
"deprecated": false,
"active": false
}
@endjson

Only the deprecated node is highlighted — active: false is not. Perfect for “find all fields where enabled=false”.

Real-world example: visualizing package.json

Drop a React project’s package.json straight in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@startjson
{
"name": "my-react-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.0",
"zustand": "^4.5.4"
},
"devDependencies": {
"vite": "^5.4.0",
"typescript": "^5.5.0",
"@types/react": "^18.3.0",
"vitest": "^2.0.0"
}
}
@endjson

Paste this into a README — it’s an order of magnitude more readable than raw JSON. New team members immediately see “what’s runtime, what’s dev, what are the build commands”.

Real-world example: API response reconciliation

The actual response from your backend, dumped for debugging:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@startjson
#highlight "status"
#highlight "user.email_verified" : false
{
"status": 200,
"data": {
"user": {
"id": 12345,
"email": "alice@example.com",
"email_verified": false,
"role": "admin",
"created_at": "2025-06-12T08:33:21Z"
},
"session": {
"token": "eyJhbGciOi...",
"expires_at": "2025-07-12T08:33:21Z"
}
},
"meta": {
"request_id": "req_abc123",
"server": "api-3"
}
}
@endjson

status and the unverified email light up — at a glance: “call succeeded but the account isn’t verified”. Paste this into an issue comment; way more efficient than prose.

When JSON is huge / ugly

Pasting a 1000-line JSON straight in gets cramped. Three tricks:

1. Default truncation handles long strings

PlantUML truncates long strings in the view at 30 chars + .... The full text is still in the SVG (select-and-copy gets it back), but the diagram stays readable.

2. Vertical arrays save horizontal space

1
2
3
4
5
6
7
8
9
@startjson
{
"bigArray": [
#highlight "0" : "first"
"a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k", "l", "m", "n", "o", "p"
]
}
@endjson

Arrays stack vertically by default. Horizontal compact mode gets unreadable fast on big JSON — don’t.

3. Extract variables for deep nesting

1
2
3
4
5
6
7
8
9
10
11
12
13
@startuml
!$cfg = %json("{" +
" \"name\": \"demo\"," +
" \"children\": [" +
" {\"id\": 1, \"name\": \"a\"}," +
" {\"id\": 2, \"name\": \"b\"}" +
" ]" +
"}")

@startjson
%$cfg
@endjson
@enduml

Pre-process the JSON via %json() and reference it with %$var. Avoids double-escaping hell in .puml files. The !$var = ... syntax is PlantUML’s preprocessor assignment; %json() is a stdlib helper.

JSON, YAML, JSON5 — all supported

1
2
3
4
5
6
7
8
9
10
@startyaml
name: puml.online
stack:
frontend: Hexo + redefine
backend: EdgeOne Pages
features:
- live preview
- SVG export
- PNG export
@endyaml

@startyaml / @endyaml is JSON’s twin. YAML is more compact (no quotes, no braces) and reads cleaner when deeply nested. The output diagram is identical.

PlantUML also supports JSON5 (comments, trailing commas, single quotes) — just swap @startjson for @startjson5 / @endjson5. If your team uses .jsonc files (VSCode’s settings.json / tsconfig.json), paste them straight in.

Compared to other tools

Tool How you’d do the same thing Pain point
Graphviz / dot Hand-write digraph { a -> b } nodes and edges Tiring past 50 fields
Mermaid flowchart Manually convert JSON to flowchart TD; A-->B; Must flatten nested structure, ugly
draw.io / Visio Drag boxes + lines Not version-controllable, no diff
PlantUML @startjson Paste JSON, done Still readable at 100+ fields

Mermaid has no native JSON directive — you’d hand-roll a flowchart or use mindmap / sankey as approximations, neither of which is as clean.

Edge cases & gotchas

1. Escaped characters in JSON

If your JSON contains \" or \\, you do NOT need to escape them again when wrapping in @startjson — PlantUML parses it as a literal string. But if you’re using a preprocessor variable (!$var = "..."), you must double-escape.

2. Comments

PlantUML does not support comments inside @startjson / @startyaml blocks. Use ' comment outside the block.

3. Large files

Past ~500 fields, rendering slows noticeably (dot’s limit). Past that, consider (a) only diagram the subtree that matters, or (b) use jq to strip uninteresting fields before pasting.

4. Duplicate keys

JSON spec forbids duplicate keys, but PlantUML’s parser accepts them and renders only the last. Don’t let this slip past code review.

Practical workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. Pull API response
curl -s https://api.example.com/v1/me \
-H "Authorization: Bearer YOUR_TOKEN" \
-o response.json

# 2. Wrap in PlantUML
{
echo '@startjson'
cat response.json
echo '@endjson'
} > diagram.puml

# 3. Render (if you have plantuml CLI)
plantuml -tsvg diagram.puml

Or paste straight into the puml.online editor — what you see is what you ship.

When NOT to use it

  • Database schema evolution — ER diagram (@startuml + entity) is clearer than a JSON instance
  • Class hierarchies — class diagram
  • Call relations / flows — sequence or activity diagram
  • The JSON structure itself is the topic@startjson is perfect

Rule of thumb: “show the shape of a real data sample” → JSON diagram; “show the relationships between types” → UML diagram.

Recap

@startjson is one of PlantUML’s most under-used features:

  • One directive turns any JSON / YAML into a readable tree
  • All 6 JSON value types auto-styled differently
  • #highlight makes important keys pop; conditional highlights for “find all X=false fields”
  • Real uses: config file structure, API responses, state snapshots, test fixtures
  • 5-10× faster than Graphviz / Mermaid hand-rolled flowcharts

Add it to your documentation toolkit. Next time someone asks “what’s the difference between dependencies and devDependencies” — draw them a diagram instead of explaining for 10 minutes.

  • Title: PlantUML as a structured JSON renderer — turn config / API responses / any JSON into a tree
  • Author: puml.online
  • Created at : 2026-08-04 14:00:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-json-diagram-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.