后台系统里经常会遇到这样的需求:展示一段接口返回的 JSON,允许修改几个字段,再把结果提交给后端。
直接放一个 textarea 当然能做。但数据嵌套多一点,找字段、检查数组层级、处理引号和逗号,都不太方便。换成完整的代码编辑器,又未必符合一个小页面的接入成本。
这篇文章介绍的 jsoneditor,走的是比较轻量的路线:一个脚本文件,提供结构化编辑、思维导图和文本三种视图。既可以放进普通 HTML 页面,也有 Vue 3 和 React 18 的适配文件。
我看了它的使用文档和页面实际引用的源码。下面不只介绍功能,也把配置方式和接入时需要注意的地方整理出来。
先说明范围:这不是一篇大数据量性能测评,也没有经过生产环境的长期验证。涉及行为差异的地方,以本文查看到的源码为准。
一、先看它适合做什么
这个工具比较适合以下场景:
- 后台配置页里,编辑一段中小规模的 JSON;
- 调试页面中,查看和修改接口数据;
- 静态文档里,嵌入可折叠的 JSON 示例;
- 内部工具中,同时提供结构查看和原始文本修改。
它的核心文件不依赖前端框架,样式也由脚本注入,不需要另外引入一份编辑器 CSS。Vue 和 React 适配文件已经包含核心引擎,不过使用这些适配文件时,仍然需要对应的框架运行环境。"零依赖"说的是编辑器核心,不是说 Vue、React 页面可以不加载框架。
如果只想在现有页面里补一个 JSON 编辑区域,这种接入方式比较直接。
二、三种视图,各自解决不同的问题
1. 结构化视图:按字段编辑
editor 是默认视图。它把 JSON 展开成带层级的结构,支持:
- 编辑字段名和字段值;
- 新增、删除节点;
- 上移、下移节点;
- 折叠、展开对象和数组;
- 对容器范围做悬停高亮;
- 点击闭合括号收起对应容器。
插入操作会区分对象和数组:对象里新增的是键值对,数组里新增的是元素。
这里比较实用的是值类型识别。行内编辑提交后,不同输入会被转换为不同类型:
| 输入内容 | 识别结果 |
|---|---|
123、-3.14、1e5 |
数字 |
true、false |
布尔值 |
null |
空值 |
{"name":"demo"} |
对象 |
[1,2,3] |
数组 |
"123" |
字符串 |
| 普通文字 | 字符串 |
这些规则来自工具文档,布尔值和 null 区分大小写。
因此,编辑编号、邮编这类字段时要留意:如果希望保留字符串类型,最好明确输入 "123",不要只输入 123。
2. 思维导图:看层级关系
mindmap 会把 JSON 展示成树状关系图,支持拖动画布和滚轮缩放,文档给出的缩放范围是 30%~300%。对象、数组和不同类型的值有不同样式。
对于嵌套较多的配置,这个视图适合先看结构:
- 哪些字段属于同一层;
- 一个对象下面有哪些分支;
- 数组放在什么位置。
不过,它不是独立的自由编辑式脑图工具。当前实现中,双击节点会切回结构化编辑视图;容器节点上的 + 用于新增子节点,并转到结构化视图继续编辑。不要把它理解为可以拖拽节点、任意重排关系的流程图编辑器。
3. 文本视图:整段粘贴和修改
text 视图用于编辑 JSON 原始文本。修改后,界面会出现 Apply to structure 按钮,点击后才会解析文本并更新结构模型。解析失败时,按钮会提示 Invalid JSON。
这里要记住:改了文本,不代表已经改了编辑器的数据。
当前源码中,get()、get_text() 和顶部复制按钮读取的都是结构模型。文本没有应用之前,取到的仍然是旧数据;切换视图后再次进入文本页,也会从模型重新生成文本。
如果把它接进表单,建议在编辑器旁边明确提示:
在 Text 视图修改后,请先点击 Apply to structure,再提交。
这是个小细节,但比提交后发现内容没有保存要好处理得多。
三、原生 HTML 页面怎么接入
1. 引入核心脚本
普通 HTML 页面使用 json-editor.js 即可。可以从项目首页下载后放到自己的静态资源目录,也可以通过 npm 安装。
下面用本地脚本路径演示:
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JSON 配置编辑</title>
</head>
<body>
<div id="json-editor"></div>
<button id="submit" type="button">读取配置</button>
<pre id="result"></pre>
<script src="./json-editor.js"></script>
<script>
const result = document.querySelector('#result');
const editor = JED.create({
el: '#json-editor',
title: '接口配置',
theme: 'light',
height: '480px',
max_height: 'none',
font_size: '14px',
defaultView: 'editor',
readonly: false,
value: {
endpoint: '/api/users',
method: 'GET',
timeout: 5000,
enabled: true,
headers: {
'Content-Type': 'application/json'
},
tags: ['internal', 'debug']
},
onChange(json) {
console.log('已应用的当前数据:', json);
}
});
document.querySelector('#submit').addEventListener('click', () => {
const errors = editor.validate();
if (errors.length > 0) {
result.textContent = errors
.map(item => `${item.path}: ${item.msg}`)
.join('\n');
return;
}
result.textContent = editor.get_text();
});
</script>
</body>
</html>
这个例子只读取并展示结果,没有向服务器发送数据。实际保存时,可以在校验通过后,把 editor.get() 返回的数据交给自己的请求逻辑。
2. 不写初始化代码,也可以声明式使用
如果只是静态展示,工具支持扫描 <json> 标签和带有 tag="json" 的元素:
html
<json data-title="接口返回" data-theme="light">
{"code": 0, "data": {"name": "demo"}}
</json>
<div tag="json" data-title="配置预览" data-readonly>
{"enabled": true, "timeout": 3000}
</div>
<script src="./json-editor.js"></script>
页面初始化时会自动扫描。动态插入新的声明式元素后,可以调用:
js
JED.scan();
自动创建的实例保存在元素的 __jed 属性上:
js
const element = document.querySelector('json');
const data = element.__jed.get();
自动扫描和实例存储方式都可以在源码中找到。
需要注意,声明式内容会先经过浏览器的 HTML 解析。展示包含 <、& 等字符的数据时,要处理好转义;动态接口数据更建议通过 value 传入,不要直接拼接进 innerHTML。
四、初始化配置完整说明
下面是 JED.create(options) 的主要配置。文档和当前代码有少量差异,表格按实际源码做了说明。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
el |
CSS 选择器或 DOM 元素 | 必填 | 编辑器挂载容器 |
value |
对象、数组或 JSON 字符串 | {} |
初始内容,字符串会被当作 JSON 文本解析 |
title |
string |
当前源码为 JSON Editor |
顶部标题;文档写的是"JSON 编辑器" |
theme |
'light' / 'dark' |
浅色 | 编辑器主题 |
height |
CSS 长度字符串 | 不设置 | 固定高度,如 '480px' |
max_height |
CSS 长度或合法关键字 | '50vh' |
最大高度,超出后内部滚动 |
font_size |
CSS 字号字符串 | 不设置 | 根容器字号;源码基础字号为 14px |
defaultView |
'editor' / 'mindmap' / 'text' |
'editor' |
初始视图 |
readonly |
boolean |
false |
限制界面编辑操作 |
onChange |
function(json) |
无 | 模型编辑后的回调 |
onError |
function(errors) |
无 | 保留的校验错误回调,触发条件见后文 |
onSave |
function(json, valid) |
无 | 历史保存回调,不建议作为新的保存入口 |
有三点值得单独说。
第一,固定高度仍然会受最大高度限制。
源码会同时设置 height 和 maxHeight。如果只写:
js
height: '600px'
默认的 max_height: '50vh' 仍然存在。希望严格使用固定高度时,可以显式设置:
js
height: '600px',
max_height: 'none'
第二,font_size 不是所有文字的统一缩放开关。
它作用于根容器。源码中的标题、按钮、文本视图等部分元素另有固定字号,因此不能保证所有区域都会跟着一起变化。
第三,readonly 是界面只读,不是权限控制。
它不能替代后端鉴权、数据校验,也不意味着业务代码不能通过实例 API 更新内容。
声明式属性对应关系
声明式标签可用的属性如下:
| HTML 属性 | 对应配置 |
|---|---|
data-title |
title |
data-theme |
theme |
data-height |
height |
data-max-height |
max_height |
data-font-size |
font_size |
data-readonly |
属性存在即启用只读 |
data-value |
初始 JSON 文本;未提供时读取元素文本 |
尤其注意下面这种写法:
html
<div tag="json" data-readonly="false">{"a":1}</div>
它依然会被当成只读,因为代码检查的是属性是否存在,而不是属性值是不是 "false"。需要取消只读时,应移除该属性。
五、实例 API:读取、替换和切换视图
初始化后,可以通过实例操作数据:
js
// 读取当前模型对应的 JavaScript 数据
const data = editor.get();
// 读取格式化 JSON 文本,使用两个空格缩进
const text = editor.get_text();
// 替换全部内容
editor.set({
name: 'new-config',
enabled: false
});
// 也可以传入 JSON 文本
editor.set('{"name":"new-config","enabled":false}');
// 校验,返回错误数组;空数组表示通过当前内置检查
const errors = editor.validate();
// 切换视图
editor.view('mindmap');
editor.view('text');
editor.view('editor');
// 当前源码还提供这两个方法
const currentView = editor.get_view();
editor.set_theme('dark');
// 卸载
editor.destroy();
这些方法均存在于当前核心源码中。其中,set() 解析字符串失败时会抛错;成功替换内容后,不会主动触发 onChange。validate() 只返回错误数组,不会自动展示错误面板或调用 onError。
因此,业务代码主动调用 set() 时,如果还要同步其他状态,需要自己处理,不能完全依赖编辑回调。
六、校验这部分,不能只照着文档理解
使用文档提到,结构编辑会自动校验并展示错误。但当前页面引用的源码里,mark_dirty() 主要负责标记修改和调用 onChange;展示错误面板、触发 onError 的逻辑在 do_save() 中,代码中没有看到普通编辑操作对它的调用。文档也注明,保存按钮已经移除。
所以,接入时不要认为"配置了 onError,所有错误就一定会自动通知"。
更明确的处理方式是:
js
function readValidatedValue(editor) {
const errors = editor.validate();
if (errors.length > 0) {
return {
ok: false,
errors
};
}
return {
ok: true,
value: editor.get()
};
}
内置校验主要检查空键、重复键、超过 100 层的嵌套,以及缺失的字符串值,并不提供 JSON Schema 业务约束。
例如:
timeout必须大于零;method只能取指定枚举;endpoint不能为空;
这些仍然需要业务层校验。
还要区分"工具规则"和"JSON 语法":空字符串键名在标准 JSON 中是合法的,但这个工具会将其报告为错误。文本里的重复键经过 JSON.parse 后,也可能已经被后面的值覆盖,不能把这里的重复键检查理解为原始 JSON 文本审计。
七、Vue 3 怎么接
Vue 版本使用 json-vue.js,它已经包含核心引擎,不要再同时加载 json-editor.js。组件通过 app.use(JED.vue) 注册,使用 v-model 同步数据。
下面是浏览器直接引入的例子:
html
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="./json-vue.js"></script>
<div id="app">
<json-editor
v-model="config"
title="业务配置"
theme="light"
height="480px"
max_height="none"
font_size="14px"
default_view="editor"
:readonly="false"
></json-editor>
</div>
<script>
const app = Vue.createApp({
data() {
return {
config: {
enabled: true,
timeout: 3000,
modules: ['user', 'order']
}
};
}
});
app.use(JED.vue);
app.mount('#app');
</script>
这里有两个命名细节:
- 原生配置使用
defaultView; - Vue 组件使用
default_view。
max_height、font_size 也保留下划线,不要自行改成其他 prop 名称。组件还接收 title、theme、height 和 readonly。
当前适配层深度监听 modelValue,同时处理标题和主题变化,但没有为 readonly、尺寸和初始视图等所有配置建立监听。不能默认每个 prop 在运行时修改后都立即生效。其组件内部使用字符串模板,采用 Vue runtime-only 构建时,还需要处理运行时模板编译问题。
如果项目有动态权限切换,或者使用标准 Vue 单文件组件工程,建议先核对适配方式,再决定是否自己封装核心引擎。
八、React 18 怎么接
React 版本使用 json-react.js,同样不需要额外加载核心文件。
html
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="./json-react.js"></script>
<div id="root"></div>
<script>
const e = React.createElement;
function App() {
const [data, setData] = React.useState({
enabled: true,
timeout: 3000
});
return e(JED.react.component, {
value: data,
onChange: setData,
title: '业务配置',
theme: 'light',
height: '480px',
max_height: 'none',
font_size: '14px',
default_view: 'editor',
readonly: false
});
}
ReactDOM
.createRoot(document.getElementById('root'))
.render(e(App));
</script>
数据通过 value 传入,通过 onChange 回传。当前适配层使用全局 React,并针对外部数据变化进行序列化比较后更新编辑器。
和 Vue 一样,不要把所有配置都当成完整的动态属性接口。尤其是运行时切换只读、调整尺寸,以及回调闭包更新等需求,建议结合项目测试,必要时自己包装 JED.create()。
npm 安装需要注意什么
项目提供的安装方式是:
bash
npm install @juicy696/jsoneditor
文档给出了三个入口:
js
// 按使用方式三选一
import '@juicy696/jsoneditor';
import '@juicy696/jsoneditor/vue';
import '@juicy696/jsoneditor/react';
这三行是不同方案,不是让它们同时执行。
当前实现主要通过浏览器全局 window.JED 暴露能力,不应直接假设它提供:
js
import JsonEditor from '@juicy696/jsoneditor';
这样的默认组件导出。核心代码初始化时也会访问 document,React 适配层则直接使用全局 React。在 Vite、SSR 等环境中,应核对客户端加载时机和框架适配条件,而不是只复制一条 import 就认为接入完成。
对工程化项目来说,直接包装核心引擎也是一种选择:在组件挂载后创建实例,卸载时调用 destroy(),把数据同步和配置更新掌握在自己手里。
九、主题和样式怎么调整
工具内置浅色、深色主题,颜色通过 .jed-root 上的 CSS 变量定义。可以覆盖这些变量,不必修改编辑器源码。
例如,仅修改某个编辑器:
css
#json-editor.jed-root {
--jed-accent: #1677ff;
--jed-key: #0050b3;
--jed-str: #237804;
--jed-num: #ad4e00;
--jed-bool: #722ed1;
--jed-brace: #cf1322;
--jed-hover: #f0f5ff;
}
变量分别用于强调色、键名、字符串、数字、布尔值、对象括号和悬停背景。源码还定义了背景、文字、边框、标题栏背景等变量。
用具体容器 ID 限定覆盖范围,可以避免页面上多个编辑器互相影响。
十、使用前还需要知道的几个边界
1. 不要把它当作大文件编辑器
文档给出的定位是几千节点以内较舒适,更多节点会带来 DOM 压力。这属于项目说明,不是本文的实测结论。
数据较大时,建议拿真实 JSON 测一下初始化、编辑、切换导图和外部更新的耗时。不要仅根据文件体积小,就推断它能轻松处理大规模数据。
2. 宽松解析不等于完整 JSON5 支持
源码会先使用 JSON.parse,失败后再尝试通过字符串替换,兼容部分单引号、尾逗号和无引号键名。
这不是完整的 JSON5 解析器。正式配置仍建议使用标准 JSON,尤其不要依赖复杂字符串在宽松模式下得到正确处理。
3. 排序操作不代表所有对象键都能原样往返
编辑器提供上移、下移操作,内部节点也有顺序。但导出时会重新构造 JavaScript 对象,再进行序列化。
如果对象使用了 "1"、"2" 这样的整数形式键名,JavaScript 的属性枚举规则可能影响最终顺序。业务上需要稳定顺序的数据,优先考虑用数组表达。
4. 本地编辑不等于整个页面没有网络行为
核心源码没有上传 JSON 的请求逻辑;文档也说明,本地保存脚本后可离线使用。
但如果页面还加载 CDN、统计脚本,或者自己的 onChange 接入了接口,那是另一回事。处理敏感配置时,建议自托管脚本,并检查页面其他依赖。
5. 开源也要保留许可信息
项目源码标注使用 Apache License 2.0,并要求再分发和修改时保留相关版权、许可证及 NOTICE 信息。复制源码进自己的项目时,应一并核对仓库的许可文件。
最后
我觉得这个工具值得关注的地方,不是功能有多全,而是它把几个常见需求放进了一个比较容易嵌入页面的组件里:按字段修改、看结构关系、直接改文本。
如果需求是给内部页面补一个轻量 JSON 编辑区域,可以先从原生脚本方式试起。真正接入表单时,重点检查三件事:
- 文本视图的修改是否已经应用;
- 提交前有没有显式校验;
- 当前项目的 Vue、React 运行环境是否适配。
这些地方确认之后,再决定要不要围绕它做二次封装。小工具不必解决所有问题,但使用者需要知道它解决到了哪一步。
项目地址