需求:支持用户在线编辑、保存 JSON,对修改的内容做限制/校验,使其符合 JSON 数据格式
方案一:vue-json-pretty
vue-json-pretty
是 JSON 展示 + 轻量编辑组件,没有原生 schema 校验能力。如果要锁字段,必须自己套 schema 校验库(如ajv) 或者手动比对修改前后的字段结构。
问题:
1)需要套 ajv 或者手动比对进行校验,比较麻烦
2)比起其他方案,没有格式化、撤销、重做等功能
方案二:jsoneditor
jsoneditor 用纯 JavaScript 编写 ,可完成可视化编辑、格式化、校验、压缩、转换等操作,它同时提供树形、代码、纯文本等多种视图,支持 JSON Schema 实时校验 与自定义主题/插件,可轻松嵌入Vue、Reac 等主流框架。
安装:pnpm add jsoneditor
限制用户只能添加/删除数据而不修改层级结构和字段名的需求,可以通过原生 jsoneditor 配置权限 +动态生成验证规则的方式来实现。核心思路是:根据后端返回的初始数据自动生成 "结构模板",然后限制用户只能在数组中添加符合模板结构的元素,不能修改现有字段名和层级。
demo 代码:
xml
<template>
<div class="json-editor-box">
<!-- 数据回来再渲染,保证第一次 new 就带 schema -->
<div v-if="schema" ref="editorRef" style="height: 500px"></div>
<el-button class="save-btn" type="primary" @click="saveJson">保存</el-button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import JSONEditor from 'jsoneditor'
import 'jsoneditor/dist/jsoneditor.min.css'
import { ElMessage } from 'element-plus'
import { getSituationStatusData } from '@/api/screen/overview.js'
const editorRef = ref(null)
const json = ref({})
const schema = ref(null)
let editor = null
/* 只锁字段名 & 类型 Schema */
const genSchema = (obj) => {
if (Array.isArray(obj)) {
return obj.length
? { type: 'array', items: genSchema(obj[0]), additionalItems: false }
: { type: 'array', items: {} }
}
if (obj !== null && typeof obj === 'object') {
const s = {
type: 'object',
properties: {},
additionalProperties: false,
required: Object.keys(obj)
}
for (const [k, v] of Object.entries(obj)) s.properties[k] = genSchema(v)
return s
}
return { type: typeof obj }
}
/* 异步加载 → 第一次就带有效 schema */
const loadData = async () => {
const res = await getSituationStatusData({ timeMode: 'day', orgId: '' })
schema.value = genSchema(res) // 先给 schema
json.value = res // 再给数据
}
/* 挂载编辑器(schema 已有效) */
const initEditor = () => {
editor = new JSONEditor(editorRef.value, {
mode: 'code',
enableSort: false,
enableTransform: false,
schema: schema.value,
schemaRefs: {},
}, json.value)
// 双向绑定
editor.aceEditor.on('change', () => {
try { json.value = editor.get() } catch (e) {}
})
}
/* 保存再验一次兜底 */
const saveJson = () => {
editor.validate().then(errors => {
if (errors.length) {
ElMessage.error('字段名或层级结构被改动,请恢复后再保存!')
return
}
console.log('校验通过', json.value)
ElMessage.success('数据保存成功')
})
}
onMounted(async () => {
await loadData() // 先拿数据
initEditor() // 再挂载编辑器(带有效 schema)
})
</script>
<style scoped>
.save-btn {
margin-top: 12px;
}
:deep(.jsoneditor-poweredBy) {
display: none;
}
</style>
效果:

vue-json-editor
vue-json-editor 是基于原生 jsoneditor 封装的 Vue 组件库,适用于 Vue2。
它的核心是将原生 jsoneditor 的功能包装成 Vue 组件的形式,支持通过 Vue2 语法调用,本质上还是依赖原生 jsoneditor 的核心能力。
安装依赖:
csharp
# 安装组件本身
pnpm add vue-json-editor
# 安装依赖的原生 jsoneditor 库
pnpm add jsoneditor
问题:
1)code 模式下输入中文存在重复
vue3-json-editor
vue3-json-editor 是专门为 Vue3 设计的封装库,内部依赖原生 jsoneditor 库,使用方式符合 Vue3 语法,支持通过 props 传递配置(如编辑模式、Schema 校验规则等),支持 v-model 双向绑定数据,无需手动管理编辑器实例的生命周期。
安装依赖:
csharp
# 安装组件本身
pnpm add vue3-json-editor
# 安装依赖的原生 jsoneditor 库
pnpm add jsoneditor
问题:
1)code 模式下输入中文存在重复
json-editor-vue3
json-editor-vue3 也是专门为 Vue3 设计的封装库,内部依赖原生 jsoneditor 库
安装依赖:
csharp
# 安装组件本身
pnpm add json-editor-vue3
# 安装依赖的原生 jsoneditor 库
pnpm add jsoneditor
对于 jsoneditor 的 demo,使用v-model
绑定数据,通过:options
传递配置项,通过ref="jsonRef"
并访问jsonRef.value.editor
获取原生实例,同时保留「结构锁」逻辑
demo 代码:
xml
<template>
<div class="json-editor-box">
<!-- schema 有了再渲染 -->
<json-editor-vue3
v-if="jsonSchema"
ref="jsonRef"
v-model="jsonData"
:options="editorOptions"
language="zh"
style="height: 400px"
/>
<el-button class="save-btn" type="primary" @click="saveJson">保存</el-button>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import JsonEditorVue3 from 'json-editor-vue3'
import { ElMessage } from 'element-plus'
import { getSituationStatusData } from '@/api/screen/overview.js'
const jsonData = ref({})
const jsonRef = ref(null)
const jsonSchema = ref(null)
const editorOptions = computed(() => ({
mode: 'code',
enableSort: false,
enableTransform: false,
schema: jsonSchema.value,
schemaRefs: {}
}))
// 只锁字段名 & 类型 Schema
const getSchema = (obj) => {
if (Array.isArray(obj)) {
// 数组 → 返回 items 对象(不要返回数组)
return obj.length
? { type: 'array', items: getSchema(obj[0]), additionalItems: false }
: { type: 'array', items: {} }
}
if (obj !== null && typeof obj === 'object') {
const s = {
type: 'object', properties: {},
additionalProperties: false, // 禁止添加新字段
required: Object.keys(obj) // 所有字段都为必填,禁止删除
}
for (const [k, v] of Object.entries(obj)) {
s.properties[k] = getSchema(v)
}
return s
}
// 基本类型
return { type: typeof obj }
}
const loadJsonData = async () => {
const res = await getSituationStatusData({ timeMode: 'day', orgId: '' })
jsonSchema.value = getSchema(res) // 更新 schema
jsonData.value = res
}
// 保存前再校验一次
const saveJson = async () => {
try {
const errors = await jsonRef.value.editor.validate()
// 校验不通过:提示错误
if (errors?.length) {
ElMessage.error('字段名或层级结构被改动,请恢复后再保存!')
return
}
// 校验通过:执行保存逻辑
console.log('校验通过', jsonData.value)
ElMessage.success('保存成功')
} catch (err) {
console.error(err)
}
}
onMounted(() => {
loadJsonData()
})
</script>
<style scoped>
:deep(.jsoneditor-modes) {
display: none;
}
:deep(.jsoneditor-poweredBy) {
display: none;
}
.save-btn {
margin-top: 12px;
}
</style>
效果:

方案三:svelte-jsoneditor
svelte-jsoneditor 用 Svelte 重写,替代了 jsoneditor,更现代更轻量。提供了 vanilla-jsoneditor 包,Vue/React/Angular 一行即可引入,无需 Svelte 环境。它提供树形、代码、表格三模式,可流畅编辑 512 MB 文件;内置 ajv,支持 JSON Schema + 自定义校验器。
项目地址:github.com/josdejong/s...
在 Svelte 项目中使用:
csharp
pnpm add svelte-jsoneditor
在纯 JavaScript 或其他框架中使用:
csharp
pnpm add vanilla-jsoneditor
demo 代码:
xml
<template>
<div class="json-editor-box">
<!-- 数据回来再渲染,避免第一次 new 时无 schema -->
<div
v-if="schema"
ref="editorRef"
class="json-editor-wrap"
:class="{ pseudoFullScreen: isFull }"
style="height: 500px"
></div>
<el-button class="json-editor-save-btn" type="primary" @click="saveJson">保存</el-button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { createJSONEditor } from 'vanilla-jsoneditor/standalone.js'
import { createAjvValidator } from 'vanilla-jsoneditor/standalone.js'
import { ElMessage } from 'element-plus'
import { getSituationStatusData } from '@/api/screen/overview.js'
const editorRef = ref(null)
const content = ref({
json: null,
text: undefined
})
const schema = ref(null)
const isFull = ref(false)
let editor = null
// 只锁字段名 & 类型 Schema
const getSchema = (obj) => {
if (Array.isArray(obj)) {
return obj.length
? { type: 'array', items: getSchema(obj[0]), additionalItems: false }
: { type: 'array', items: {} }
}
if (obj !== null && typeof obj === 'object') {
const s = {
type: 'object',
properties: {},
additionalProperties: false,
required: Object.keys(obj)
}
for (const [k, v] of Object.entries(obj)) s.properties[k] = getSchema(v)
return s
}
return { type: typeof obj }
}
// 异步加载 → 第一次就带有效 schema
const loadData = async () => {
const res = await getSituationStatusData({ timeMode: 'day', orgId: '' })
schema.value = getSchema(res)
content.value = { json: res }
}
// 新增全屏按钮
const addFullScreenButton = () => {
const header = editorRef.value.querySelector('.jse-menu') // 顶部菜单栏
if (!header) return
// 创建全屏按钮元素
const fsBtn = document.createElement('button')
fsBtn.className = 'jse-button jse-fullscreen iconfont icon-quanping1'
fsBtn.title = '进入全屏'
fsBtn.onclick = () => (isFull.value = !isFull.value)
header.appendChild(fsBtn)
}
// 挂载编辑器(schema 已有效)
const initEditor = () => {
// 创建 ajv 校验器
const validator = createAjvValidator({ schema: schema.value, schemaRefs: {} })
editor = createJSONEditor({
target: editorRef.value,
props: {
content: content.value,
mode: 'text',
validator, // ajv 实时校验器
mainMenuBar: true,
// 自定义菜单项
onRenderMenu: (items) => {
// 目前尚未支持国际化,各个按钮的 title 需手动设置成中文
const zhTitles = {
'jse-format': '格式化JSON:添加正确的缩进和换行符(Ctrl+I)',
'jse-compact': '紧凑JSON:移除所有空白和换行符(Ctrl+Shift+I)',
'jse-undo': '撤销(Ctrl+Z)',
'jse-redo': '重做(Ctrl+Shift+Z)'
}
// 只展示格式化、紧凑、撤销、重做这些按钮
return items
.filter(item => item.type === 'button' && ['jse-format', 'jse-compact', 'jse-undo', 'jse-redo'].includes(item.className))
.map(item => ({
...item,
title: zhTitles[item.className]
}))
},
onChange: (updatedContent) => {
content.value = updatedContent
}
}
})
// 在 createJSONEditor 之后再添加全屏按钮,否则找不到 header 元素
addFullScreenButton()
}
// 保存再校验一次
const saveJson = async () => {
const res = await editor.validate()
const errors = res?.validationErrors ?? [] // 取数组再判断
if (errors?.length) {
ElMessage.error('字段名或层级结构被改动,请恢复后再保存!')
return
}
const jsonData = JSON.parse(content.value.text)
console.log('校验通过', jsonData)
// 用户修改后调接口进行保存
ElMessage.success('数据保存成功')
}
watch(isFull, val => {
const btn = editorRef.value?.querySelector('.jse-button.jse-fullscreen')
if (!btn) return
btn.className = `jse-button jse-fullscreen iconfont ${val ? 'icon-tuichuquanping' : 'icon-quanping1'}`
btn.title = val ? '退出全屏' : '进入全屏'
})
onMounted(async () => {
await loadData() // 先拿数据
initEditor() // 再挂载编辑器(带有效 schema)
})
onUnmounted(() => {
if (editor) {
editor.destroy()
editor = null
}
})
</script>
<style scoped>
/* 伪全屏样式 */
.json-editor-wrap.pseudoFullScreen {
position: fixed;
inset: 0;
z-index: 1501;
background: #fff;
padding: 20px;
height: 100vh !important;
}
:deep(.jse-button.jse-fullscreen) {
margin-left: auto;
background: none;
border: none;
font-size: 20px;
color: #fff;
cursor: pointer;
}
.json-editor-save-btn {
margin-top: 12px;
}
</style>
效果:

问题:
1)目前尚未支持国际化,菜单项各个按钮的 title 需手动设置成中文
2)相比 jsoneditor,没有全屏按钮,需要自行添加
方案四:代码编辑器
如 Ace/CodeMirror/Monaco 这些代码编辑器,支持 JSON 语法高亮、智能提示、实时校验、格式化等,原生支持 JSON Schema 校验,但体积较大,适合对功能要求高但对体积不敏感的场景。
方案对比
vue-json-pretty | j'son'editor | svelte-jsoneditor | 代码编辑器 | |
---|---|---|---|---|
校验 | 无原生支持,需手动集成 ajv 等库 | 内置 JSON 语法校验 + 完整 JSON Schema 支持 | 内置 ajv,支持 JSON Schema + 自定义校验器 | 原生支持 schema 校验 |
格式化、撤销、重做 | 无 | 支持 | 支持 | 支持 |
体积 | 体积极小,轻量 | 体积较大 | 比 jsoneditor 轻 50%,体积较小 | 体积极大 |
大文件处理 | 加载10MB 以上JSON 文件时会出现明显卡顿 | 较好,支持虚拟滚动 | 树/文本模式下直接编辑,可流畅编辑 512 MB 文件 | 支持虚拟滚动 + 增量渲染 |
多语言 | 无 | 支持 | 尚未支持 | 支持 |
框架依赖 | 仅支持 Vue | 支持 Vue/React/ 原生(需对应封装) | 无框架依赖(原生 JS) | 无框架依赖(可集成到任意框架) |
总结:推荐使用 svelte-jsoneditor,比 jsoneditor 更轻量,内置 ajv,支持 schema 校验,大文件处理更流畅。