调试嵌套API时,你数过括号吗?我见过一个Stripe的webhook,展开后80多个节点。第三次调试时,我放弃了——直接写了个工具:左边贴JSON,右边看交互图。这就是jsonbloom.com,纯浏览器运行,无需注册。
这篇文章讲架构选择。出乎意料的是,大部分决策比预想的小得多。
为什么不用react-flow或d3-hierarchy?
两个都是好库。我试过,才决定自己写。
问题在于,它们的设计远比JSON查看器需要的复杂。react-flow单独压缩后约150kb,支持节点拖拽、边编辑、小地图、自定义句柄——JSON查看器全都不需要。d3-hierarchy只给布局数学,渲染器、折叠逻辑、交互层全要自己补。
JSON查看器真正需要的很窄:
1. 把对象和数组渲染成盒子
2. 画父节点到子节点的边
3. 折叠/展开子树
4. 平移和缩放画布
5. 行内编辑叶子值
就这些。没有拖拽重排,没有节点合并,没有自定义节点类型。清单写下来,自定义渲染器的理由自然成立:约5kb代码,只做这五件事;对比150kb代码,做很多间接的事。
架构
Astro(静态外壳,SSG)
└── React孤岛(client:only)
├── 左侧CodeMirror编辑器
└── 右侧自定义SVG图
整个落地页——hero、功能卡片、FAQ——都是Astro组件,构建时渲染。只有工作区(编辑器+图)是React孤岛,用client:only挂载,因为那里没有有用的SSR。这带来:
• 营销页约30kb JS(那里零React)
• 慢连接也能快速首屏
• 交互部分与用户读hero时并行加载
用过Astro的,这是 obvious play。没用过的:这是"落地页+交互工具"站点的最大性能收益,且 adoption 成本极低。
布局
JSON是树。朴素做法是组织图布局:根在上,子在下。小payload有效,真实数据立刻崩——50键对象变成50个横排盒子。
我确定的方案:
• 对象和数组是左到右树的盒子
• 原始值钉在父盒内键旁,不作为独立节点
• 每层一列;兄弟垂直堆叠
这把数百节点压缩到数十。典型Stripe事件渲染成约8–15个盒子,而非80个。
布局一次遍历:
function layout(node, depth = 0, y = 0) {
node.x = depth * COLUMN_WIDTH;
node.y = y;
let childY = y;
for (const child of node.children) {
const h = layout(child, depth + 1, childY);
childY += h + GAP;
node.height =
(原文截断,此处保留未补全)
渲染
SVG,不是Canvas。原因:文本渲染。JSON查看器里,键名和值就是内容。SVG的元素用浏览器文本引擎,自动处理字体、RTL、换行。Canvas要自己造这些。
节点是元素,子元素:背景、键名、值的或嵌套。边是用简单二次贝塞尔。没有WebGL,没有复杂着色器。
交互层直接绑SVG事件:click处理折叠,wheel处理缩放,mousedown/mousemove处理平移。约200行,没抽象到看不清。
状态管理
React的useState够用了。整个图状态:一个嵌套节点树,每个节点有collapsed布尔值。展开/折叠只是翻转该布尔值,重跑布局遍历。50ms内完成,数据量下够快,没上虚拟化。
编辑器与图的同步:CodeMirror的onChange把JSON解析成树,丢进React状态。解析错误时,图区显示红错提示,不崩溃。
性能
最大JSON测试:10MB的GitHub API事件流。首次渲染约800ms,之后交互流畅。瓶颈在解析和初始布局,不是渲染。SVG DOM节点数:约1200个,现代浏览器轻松处理。
优化就一条:折叠默认开启深度>3的节点。大payload首次加载不炸屏,用户按需展开。
部署
Cloudflare Pages。Astro构建输出静态文件,边缘网络全球分发。自定义域名,HTTPS,零配置。免费额度够个人项目用到很大。
没有后端。用户数据不进任何服务器,贴敏感JSON的人可以放心。
意外收获
写自定义渲染器前,我以为要造小型图形引擎。实际只是:递归函数算位置,SVG元素画出来,事件处理器改状态。复杂度与用现成库差不多,体积差30倍。
这个模式我后来复用了:先列需求清单,再评估库。清单短且具体时,自己写常比适配通用库快。
jsonbloom.com现在是我的默认工具。Stripe webhook调试时间从数分钟降到数十秒。代码在GitHub,MIT协议——如果你需要改,fork就行。
热门跟贴