Vue 项目 JSON 在线编辑、校验如何选?

需求:支持用户在线编辑、保存 JSON,对修改的内容做限制/校验,使其符合 JSON 数据格式

方案一:vue-json-pretty

vue-json-prettyJSON 展示 + 轻量编辑组件,没有原生 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 校验,大文件处理更流畅。

相关推荐
派大星_分星2 小时前
nuxt fetch $fetch useFetch 等使用方式区别
前端
快手技术2 小时前
兼顾效率和性能!快手低代码平台在大型活动中的技术实践!
前端
tuuuuuun3 小时前
Electron 缓存数据共享同步
vue.js·electron
WebInfra3 小时前
📱开源 AI 工具驱动 iOS 自动化 、接入全新 Qwen 模型 - Midscene v0.29 发布
前端·ios·测试
乖女子@@@3 小时前
React-props的children属性
前端·javascript·react.js
OEC小胖胖3 小时前
组件化思维(下):表单与交互组件,倾听用户的心声
前端·微信小程序·小程序·微信开放平台
八月十八3 小时前
React常用Hooks及使用示例大全
前端·javascript·react.js
狼爷3 小时前
前端项目从 Windows 到 Linux:构建失败的陷阱
前端·node.js·angular.js
1024小神3 小时前
vitepress多语言实现第一次跟随浏览器,第二次不跟随
前端