news 2026/4/17 19:57:16

100个小工具挑战 #002 | 做了个能直接编辑树形视图的 JSON 格式化工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
100个小工具挑战 #002 | 做了个能直接编辑树形视图的 JSON 格式化工具

起因

今年给自己定了个目标:做 100 个小工具页面

不是为了流量,就是想把平时开发中遇到的痛点一个个解决掉。这是第 2 个。

第 1 个是发票批量识别工具,这次做的是 JSON 格式化。


为什么要自己做,不用现成的?

用过很多在线 JSON 格式化工具,大部分都是这个模式:

左边粘贴 JSON → 右边展示树形结构

右边的树形视图只能看,不能改

想改某个嵌套字段的值?回左边找到那一行,手动改,再看右边有没有更新。

嵌套层级深一点,找个字段要翻半天。

所以这次做的核心差异点就一个:右侧树形视图可以直接编辑,点击键名可以重命名,点击值可以直接修改,还能添加和删除节点,修改后左侧文本编辑器实时同步。


整体架构

┌─────────────────────────────────────────┐ │ JSON 编辑器 │ ├──────────────────┬──────────────────────┤ │ 左侧:文本编辑器 │ 右侧:树形视图 │ │ - 原始文本输入 │ - 可折叠节点 │ │ - 实时语法验证 │ - 点击编辑键名/值 │ │ - 格式化/压缩 │ - 添加/删除节点 │ └──────────────────┴──────────────────────┘ ↕ 双向同步

分屏布局,左右两侧实时联动,任意一侧修改都会同步到另一侧。


一、实时语法验证

最基础的功能,监听 input 事件,用 JSON.parse 捕获异常:

jsonInput.addEventListener('input', function() { updateStatus(); validateJSON(); // 防抖更新树形视图,避免频繁重绘 clearTimeout(updateTreeTimer); updateTreeTimer = setTimeout(() => updateTreeView(), 800); }); function validateJSON() { const text = jsonInput.value.trim(); if (!text) { setStatus('info', '等待输入...'); return; } try { JSON.parse(text); setStatus('valid', 'JSON 格式正确'); } catch (e) { setStatus('invalid', 'JSON 格式错误: ' + e.message); } }

防抖延迟 800ms 再更新树形视图,避免用户每输入一个字符就触发一次完整的 DOM 重建。


二、格式化与压缩

本质上就是 JSON.stringify 的两种用法:

// 格式化(美化) function formatJSON() { const obj = JSON.parse(jsonInput.value); const indentSize = document.getElementById('indentSize').value; const indent = indentSize === 'tab' ? '\t' : parseInt(indentSize); jsonInput.value = JSON.stringify(obj, null, indent); } // 压缩(去除所有空白) function compressJSON() { const obj = JSON.parse(jsonInput.value); jsonInput.value = JSON.stringify(obj); }

支持 2 空格、4 空格、制表符三种缩进方式,通过 select 元素切换。


三、转义与反转义

在某些场景下(比如把 JSON 作为字符串存入数据库,或者嵌入 HTML 属性),需要对 JSON 进行转义:

// 转义:将 JSON 字符串本身作为字符串值处理 function escapeJSON() { const text = jsonInput.value.trim(); jsonInput.value = JSON.stringify(text); // 会自动转义特殊字符 } // 反转义:解析被转义的 JSON 字符串 function unescapeJSON() { const text = jsonInput.value.trim(); const unescaped = JSON.parse(text); if (typeof unescaped === 'string') { jsonInput.value = unescaped; } }

四、树形视图的核心实现

这是整个编辑器最复杂的部分。核心思路是递归渲染,根据数据类型分别处理:

function renderJSON(data, container, level = 0, key = null, path = []) { const line = document.createElement('div'); line.className = 'json-viewer-line'; line.style.marginLeft = (level * 20) + 'px'; if (data === null || typeof data !== 'object') { // 基础类型:直接渲染值,绑定点击编辑事件 renderPrimitive(data, line, key, path); } else if (Array.isArray(data)) { // 数组:渲染折叠按钮 + [ + 递归子项 + ] renderArray(data, line, container, level, key, path); } else { // 对象:渲染折叠按钮 + { + 递归属性 + } renderObject(data, line, container, level, key, path); } }

折叠/展开实现
每个可折叠节点生成一个唯一 ID,通过切换 hidden class 控制显示:

function toggleCollapse(id, toggle) { const element = document.getElementById(id); const closeElement = document.getElementById('close-' + id); const ellipsis = document.getElementById('ellipsis-' + id); if (element.classList.contains('hidden')) { element.classList.remove('hidden'); closeElement.style.display = 'block'; ellipsis.style.display = 'none'; toggle.innerHTML = '▼'; } else { element.classList.add('hidden'); closeElement.style.display = 'none'; ellipsis.style.display = 'inline'; // 折叠时显示 "3 items..." toggle.innerHTML = '▶'; } }

折叠时显示 3 items... 或 5 properties... 的摘要,让用户知道折叠了多少内容。


五、可视化编辑——双向同步的关键

树形视图不只是展示,还支持直接编辑。关键是维护一个 currentJsonObject 引用,所有编辑操作都直接修改这个对象,然后重新序列化到文本编辑器:

let currentJsonObject = null; // 当前 JSON 对象的引用 function updateJsonValue(path, newValue) { if (path.length === 0) { currentJsonObject = newValue; return; } // 通过 path 数组定位到目标节点的父级 let current = currentJsonObject; for (let i = 0; i < path.length - 1; i++) { current = current[path[i]]; } current[path[path.length - 1]] = newValue; }

path 是一个数组,记录从根节点到当前节点的路径,例如 ['address', 'city'] 表示 obj.address.city。

编辑值的交互
点击值时,将 span 替换为 input,失焦或按 Enter 保存:

function editValue(path, currentValue, valueType, element) { const input = document.createElement('input'); input.value = currentValue; element.textContent = ''; element.appendChild(input); input.focus(); const saveEdit = () => { const newValue = input.value.trim(); // 字符串类型允许清空为 "" if (newValue === '' && valueType === 'string') { updateJsonValue(path, ''); syncToTextEditor(); return; } // 数值类型清空时提示将变为 null if (newValue === '' && valueType === 'number') { if (confirm('值已清空,将把该字段设为 null,确认吗?')) { updateJsonValue(path, null); syncToTextEditor(); updateTreeView(); // 类型变了,需要重新渲染 } return; } // 按原类型解析新值 const parsed = parseByType(newValue, valueType); updateJsonValue(path, parsed); syncToTextEditor(); }; input.addEventListener('blur', saveEdit); input.addEventListener('keydown', e => { if (e.key === 'Enter') saveEdit(); if (e.key === 'Escape') cancelEdit(); }); }

重命名键名
对象的键名重命名需要特殊处理——JavaScript 对象没有直接重命名 key 的 API,需要删除旧 key 并添加新 key,但这会改变属性顺序:

// 重命名键:删除旧键,添加新键(会移到末尾) const value = parent[oldKey]; delete parent[oldKey]; parent[newKey] = value;

如果需要保持顺序,可以重建整个对象:

// 保持顺序的重命名 const newObj = {}; for (const k of Object.keys(parent)) { newObj[k === oldKey ? newKey : k] = parent[k]; } Object.assign(parent, newObj); // 删除多余的旧键(如果 newKey !== oldKey)

六、节点的添加与删除

function addNode(path, nodeType) { let target = getByPath(currentJsonObject, path); if (nodeType === 'array') { target.push('新值'); } else { // 自动生成不重复的键名 let newKey = 'newKey'; let counter = 1; while (target.hasOwnProperty(newKey)) { newKey = 'newKey' + counter++; } target[newKey] = '新值'; } syncToTextEditor(); updateTreeView(); } function deleteNode(path) { const parent = getByPath(currentJsonObject, path.slice(0, -1)); const lastKey = path[path.length - 1]; if (Array.isArray(parent)) { parent.splice(lastKey, 1); // 数组用 splice } else { delete parent[lastKey]; // 对象用 delete } syncToTextEditor(); updateTreeView(); }

七、性能优化


对于大型 JSON(几千个节点),频繁的 DOM 操作会很慢。几个优化点:

1. 防抖更新
clearTimeout(updateTreeTimer); updateTreeTimer = setTimeout(() => updateTreeView(), 800);
2. 默认折叠深层节点

超过一定深度的节点默认折叠,减少初始渲染的 DOM 数量:

function renderJSON(data, container, level = 0, ...) { // 超过 3 层默认折叠 if (level > 3 && isObject(data)) { renderCollapsed(data, container, level, key, path); return; } // ... }
3. 虚拟滚动(进阶)

对于超大 JSON,可以引入虚拟滚动,只渲染可视区域内的节点。不过对于日常开发场景,10MB 以内的 JSON 用防抖 + 默认折叠基本够用。


八、全屏模式

全屏模式通过 CSS position: fixed 实现,不依赖 Fullscreen API,兼容性更好:

function toggleFullscreen() { isFullscreen = !isFullscreen; if (isFullscreen) { editorContainer.classList.add('fullscreen-mode'); document.body.style.overflow = 'hidden'; } else { editorContainer.classList.remove('fullscreen-mode'); document.body.style.overflow = ''; } } // ESC 退出全屏 document.addEventListener('keydown', e => { if (e.key === 'Escape' && isFullscreen) toggleFullscreen(); });
.fullscreen-mode { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9998; border-radius: 0; }

总结

成品图片:

实现一个完整的 JSON 编辑器,核心难点在于:

  1. 树形视图的递归渲染:需要处理对象、数组、基础类型三种情况
  2. 双向同步:维护一个共享的 JSON 对象引用,任意一侧修改后重新序列化
  3. path 路径定位:用数组记录节点路径,实现精准的深层节点更新
  4. 类型感知编辑:不同类型的值有不同的编辑规则(字符串可以为空,数值不能输入字母等)

大家可以来体验一下:Json在线格式化工具

代码量大约 2000 行,纯原生 JS 实现,无框架依赖,可以直接作为独立页面部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 19:57:12

指纹浏览器与代理IP协同优化:网络层与设备层的深度适配实践

在 2026 年平台 AI 风控持续升级的背景下&#xff0c;多账号安全运营已不再是单一工具的独立作用&#xff0c;而是指纹浏览器与代理 IP 的协同作战。多数运营者在实际使用中&#xff0c;常陷入 “指纹隔离到位但 IP 配置不当”“IP 质量优质但环境参数冲突” 的困境&#xff0c…

作者头像 李华
网站建设 2026/4/17 19:56:27

打卡信奥刷题(3126)用C++实现信奥题 P7428 [THUPC 2017] 母亲节的礼物

P7428 [THUPC 2017] 母亲节的礼物 题目描述 小 B 喜欢图&#xff0c;尤其是边数不太多的无向简单图。 母亲节快到了&#xff0c;小 B 在纸上画了一张有 nnn 个节点、mmm 条边的无向简单图&#xff08;即&#xff0c;不存在重边、自环&#xff09;&#xff0c;保证每个点只和最多…

作者头像 李华
网站建设 2026/4/17 19:52:13

微服务4:Spring Cloud 微服务实战:如何实现跨服务数据组装?

在微服务架构中&#xff0c;数据孤岛是一个常见的问题。当我们进行业务开发时&#xff0c;经常会遇到这样的场景&#xff1a;一个业务实体&#xff08;如订单&#xff09;需要展示另一个业务实体&#xff08;如用户&#xff09;的信息。今天&#xff0c;我们就通过一个经典的“…

作者头像 李华
网站建设 2026/4/17 19:51:09

清迈自由行找导游?这些避坑经验帮你少花冤枉钱

在现代社会&#xff0c;我们每天都承受着巨大的压力&#xff0c;焦虑如影随形。旅行&#xff0c;便成了我们逃离现实、放松身心的最佳方式。我一直向往清迈这座充满异域风情的小城&#xff0c;那里有古朴的寺庙、热闹的夜市&#xff0c;还有热情友善的当地人。于是&#xff0c;…

作者头像 李华
网站建设 2026/4/17 19:50:36

拯救者工具箱终极指南:5个技巧让你的游戏本性能翻倍

拯救者工具箱终极指南&#xff1a;5个技巧让你的游戏本性能翻倍 【免费下载链接】LenovoLegionToolkit Lightweight Lenovo Vantage and Hotkeys replacement for Lenovo Legion laptops. 项目地址: https://gitcode.com/gh_mirrors/le/LenovoLegionToolkit Lenovo Legi…

作者头像 李华