我用单个 HTML 文件构建了一个 JSON diff 工具(无需构建步骤)
Source: Dev.to
概览
我经常需要并排比较 JSON 负载(API 响应 vs 预期、前后对比、生产 vs 暂存)。每次我都会把它粘贴到随便的在线工具里,盯着看,然后几分钟后又重复这个过程。
为了解决这个问题,我构建了 fknjsn.com —— 一个本地优先的 JSON 对比工具,整个工具只在一个 HTML 文件中,无需构建步骤。
- 通过 CDN 引入 Vue 3(全局构建)
- 通过 CDN 引入
vue-json-pretty用于树形渲染 - 使用
localStorage实现持久化
就这些 —— 没有 webpack、Vite、node_modules,也没有 package.json。只需要一个 index.html,甚至可以直接邮件发送给别人。
设置
整个应用只需两个 <script> 标签和对 Vue 全局导出的解构:
const { createApp, ref, computed, watch, onMounted, nextTick } = Vue;
// ... the rest is just Vue
全局构建虽然不时髦,但能用。没有打包工具,没有 import map —— 只用像 2014 年那样的 <script> 标签,却仍然拥有响应式框架。
任意位置粘贴的用户体验
应用全局监听粘贴事件,但在 <input> 或 <textarea> 获得焦点时会忽略。这让你可以在页面任意位置粘贴 JSON,内容会自动落到选中的行上。
window.addEventListener('paste', (e) => {
const tag = document.activeElement?.tagName?.toLowerCase()
if (tag === 'input' || tag === 'textarea') return
try {
const json = JSON.parse(e.clipboardData.getData('text'))
addJsonToSelectedRow(json)
} catch {
// not valid JSON, ignore
}
})
递归搜索
每个 JSON 块都有自己的搜索输入框。过滤器会遍历整棵树,并在有任意子节点匹配时保留父节点。概念上的实现如下:
function filterJson(obj, search) {
if (!search) return obj
const lower = search.toLowerCase()
if (Array.isArray(obj)) {
const filtered = obj
.map(item => filterJson(item, search))
.filter(item => item !== undefined)
return filtered.length ? filtered : undefined
}
if (obj && typeof obj === 'object') {
const result = {}
for (const [key, value] of Object.entries(obj)) {
if (key.toLowerCase().includes(lower)) {
result[key] = value
} else {
const filtered = filterJson(value, search)
if (filtered !== undefined) result[key] = filtered
}
}
return Object.keys(result).length ? result : undefined
}
// primitives
const str = String(obj).toLowerCase()
return str.includes(lower) ? obj : undefined
}
防抖持久化
状态会保存到 localStorage,但采用 500 ms 的防抖,以避免在快速变更时频繁写入存储:
watch(rows, () => {
clearTimeout(saveTimeout)
saveTimeout = setTimeout(() => {
localStorage.setItem('json-rows', JSON.stringify(state))
}, 500)
}, { deep: true })
扩展思路
如果工具需要处理更大的 JSON 负载或增加更多功能,我会考虑:
- 为大体积过滤添加 Web Worker
- 为树形视图使用 虚拟滚动
- 实现真正的 diff 视图,而不仅仅是并排比较
目前它正好满足我的需求,整个项目也能轻松装进我的脑袋里。
链接
- 在线演示: https://fknjsn.com
- 源码: 页面 view‑source(整个应用就在那儿)
名字的发音正如你想的那样。