LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

使用 HTML + JavaScript 实现JSON格式化工具(附完整代码)

admin
2025年12月22日 19:3 本文热度 1057
在日常的开发工作中,我们经常需要处理和调试JSON数据。一个美观且功能丰富的JSON格式化工具不仅能提高开发效率,还能帮助我们更好地理解复杂的数据结构。本文将介绍如何使用 HTML、CSS 和 JavaScript 实现一个功能完整的JSON格式化工具。

效果演示

这款JSON格式化工具具有直观的用户界面,分为左右两个面板:左侧为原始JSON输入区,右侧为格式化后的输出区。用户可以在左侧输入任意JSON字符串,点击"格式化"按钮后,右侧将展示经过美化和着色的JSON结构。该工具还提供了一些自定义选项,包括引号显示、折叠控制、Unicode转义以及缩进调整等功能,极大地提升了JSON数据的可读性和可用性。

页面结构

页面主要包括配置面板和主操作区域两部分。

配置面板

位于页面顶部的配置面板允许用户自定义格式化选项。配置选项包括:

  • 引号:控制是否显示JSON键名的双引号

  • 显示控制:启用或禁用可折叠功能

  • 转义Unicode:自动将Unicode字符转换为\uXXXX格式

  • 缩进量:设置每级嵌套的缩进空格数(1-6)

<div class="config-panel">  <div class="config-options-list">    <div class="config-option-item">      <input type="checkbox" id="toggleQuoteKeys" checked>      <label for="toggleQuoteKeys">引号</label>    </div>    <div class="config-option-item">      <input type="checkbox" id="toggleCollapsibleView" checked>      <label for="toggleCollapsibleView">显示控制</label>    </div>    <div class="config-option-item">      <input type="checkbox" id="toggleUnicodeEscape">      <label for="toggleUnicodeEscape">转义Unicode</label>    </div>    <div class="config-option-item">      <label for="selectIndentationSize">缩进量:</label>      <select id="selectIndentationSize">        <option value="1">1</option>        <!-- 其他option -->      </select>    </div>  </div></div>

主操作区域

主操作区域采用两列布局,分别用于输入原始JSON和显示格式化结果。左侧面板包含一个文本域用于输入JSON数据和一个“格式化”按钮;右侧面板展示了格式化结果,并提供了"展开"和"复制"功能按钮。
<div class="main-layout">  <div class="panel-container">    <div class="panel-header-section">      <h2 class="panel-title-text">输入 JSON</h2>      <div class="action-buttons">        <button class="btn-primary" id="btnFormatJson">格式化</button>      </div>    </div>    <textarea id="textareaRawJson" placeholder="在此输入 JSON 数据..."></textarea>  </div>  <div class="panel-container">    <div class="panel-header-section">      <h2 class="panel-title-text">格式化输出</h2>      <div class="action-buttons">        <button class="btn-secondary" id="btnExpandAll">展开</button>        <button class="btn-copy" id="btnCopyFormatted">复制</button>      </div>    </div>    <pre id="preFormattedOutput"></pre>  </div></div>

核心功能实现

JSON 格式化核心逻辑

performJsonFormatting 函数是整个工具的核心,负责解析和格式化JSON数据。该函数首先尝试解析输入的JSON字符串,如果解析失败则尝试进一步处理,最终调用 formatJsonValue 进行递归格式化。
function performJsonFormatting(json) {  try {    let obj;    if (typeof json === 'object') {      obj = json;    } else {      if (json === "") {        document.getElementById('preFormattedOutput').innerHTML = "";        isJsonFormatted = false;        return;      }
      try {        obj = JSON.parse(json);      } catch (parseError) {        try {          const tempStr = `"${json}"`;          const unescapedStr = JSON.parse(tempStr);          obj = JSON.parse(unescapedStr);        } catch (secondError) {          throw parseError;        }      }    }    document.getElementById('preFormattedOutput').innerHTML = formatJsonValue(obj, 0falsefalsefalse);    isJsonFormatted = true;    attachCollapseEvents();  } catch (e) {    alert("JSON数据格式不正确:\n" + e.message);    document.getElementById('preFormattedOutput').innerHTML = "";    isJsonFormatted = false;  }}

递归格式化函数

formatJsonValue 函数根据数据类型递归处理JSON结构。对于对象和数组类型,函数还会根据配置决定是否添加折叠控制元素。
function formatJsonValue(obj, indent, addComma, isArray, isPropertyContent) {  let html = "";  const comma = addComma ? `<span class="json-comma-symbol">,</span>` : "";  const type = typeof obj;
  if (Array.isArray(obj)) {    if (obj.length === 0) {      html += generateIndentedLine(indent, `<span class="json-array-bracket">[ ]</span>${comma}`, isPropertyContent);    } else {      const sectionId = collapsibleSectionId++;      const isCollapsible = formattingOptions.isCollapsible;
      if (isCollapsible) {        html += generateIndentedLine(indent, `<span class="brace-line"><span class="json-array-bracket">[</span><span class="toggle-wrapper"><span class="toggle-icon" data-target="content-${sectionId}">-</span></span></span>`, isPropertyContent);        html += `<span id="content-${sectionId}" class="collapsible-content">`;      } else {        html += generateIndentedLine(indent, `<span class="json-array-bracket">[</span>`, isPropertyContent);      }      for (let i = 0; i < obj.length; i++) {        html += formatJsonValue(obj[i], indent + 1, i < (obj.length - 1), truefalse);      }      if (isCollapsible) html += `</span>`;      html += generateIndentedLine(indent, isCollapsible ? `<span class="brace-line"><span class="json-array-bracket">]</span>${comma}</span>` : `<span class="json-array-bracket">]</span>${comma}`);    }  } else if (obj === null) {    html += formatPrimitiveValue("null""", comma, indent, isArray, "null");  } else if (type === 'object') {    const keys = Object.keys(obj);    if (keys.length === 0) {      html += generateIndentedLine(indent, `<span class="json-object-bracket">{ }</span>${comma}`, isPropertyContent);    } else {      const sectionId = collapsibleSectionId++;      const isCollapsible = formattingOptions.isCollapsible;
      if (isCollapsible) {        html += generateIndentedLine(indent, `<span class="brace-line"><span class="json-object-bracket">{</span><span class="toggle-wrapper"><span class="toggle-icon" data-target="content-${sectionId}">-</span></span></span>`, isPropertyContent);        html += `<span id="content-${sectionId}" class="collapsible-content">`;      } else {        html += generateIndentedLine(indent, `<span class="json-object-bracket">{</span>`, isPropertyContent);      }
      for (let i = 0; i < keys.length; i++) {        const key = keys[i];        const quote = formattingOptions.quoteKeys ? '"' : '';        const hasMore = i < keys.length - 1;        html += generateIndentedLine(indent + 1, `<span class="json-key-name">${quote}${key}${quote}</span>: ${formatJsonValue(obj[key], indent + 1, hasMore, false, true)}`);      }      if (isCollapsible) html += `</span>`;      html += generateIndentedLine(indent, isCollapsible ? `<span class="brace-line"><span class="json-object-bracket">}</span>${comma}</span>` : `<span class="json-object-bracket">}</span>${comma}`);    }  } else if (type === 'string') {    html += formatPrimitiveValue(obj, '"', comma, indent, isArray, "string");  } else if (type === 'number') {    html += formatPrimitiveValue(obj, "", comma, indent, isArray, "number");  } else if (type === 'boolean') {    html += formatPrimitiveValue(obj, "", comma, indent, isArray, "boolean");  }  return html;}

折叠功能实现

折叠功能通过动态添加和移除 CSS 类实现。
function attachCollapseEvents() {  if (!isJsonFormatted) return;  document.querySelectorAll('.toggle-icon').forEach(function(icon) {    icon.addEventListener('click'function(e) {      e.preventDefault();      const targetId = icon.getAttribute('data-target');      const content = document.getElementById(targetId);
      if (content.classList.contains('hidden')) {        content.classList.remove('hidden');        icon.textContent = '-';      } else {        content.classList.add('hidden');        icon.textContent = '+';      }    });  });}

扩展建议

  • 增强错误提示:提供更详细的错误位置信息和修复建议

  • 支持主题切换:提供多种语法高亮主题供用户选择

  • 增加数据统计:显示JSON结构的基本统计信息,如节点数量、深度等

  • 添加搜索功能:在大型JSON文档中快速定位特定键或值

完整代码

git地址:https://gitee.com/ironpro/hjdemo/blob/master/json-formater/index.html
<!DOCTYPE html><html lang="zh-CN"><head>  <meta charset="utf-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>JSON格式化工具</title>  <style>      * {          margin0;          padding0;          box-sizing: border-box;      }      body {          background-color#f0f2f5;          padding15px;          color#333;      }      .container {          max-width1200px;          margin0 auto;      }      h1 {          text-align: center;          color#333;          margin-bottom20px;          font-size24px;          font-weight500;      }      .config-panel {          background#ffffff;          border1px solid #e1e4e8;          border-radius6px;          padding15px;          margin-bottom15px;          box-shadow0 1px 3px rgba(0000.05);      }      .config-options-list {          display: flex;          gap15px;          flex-wrap: wrap;      }      .config-option-item {          display: flex;          align-items: center;          gap6px;      }      input[type="checkbox"] {          width16px;          height16px;          cursor: pointer;      }      label {          cursor: pointer;          user-select: none;          font-size14px;      }      .main-layout {          display: grid;          grid-template-columns1fr 1fr;          gap15px;          margin-bottom15px;      }      .panel-container {          background#ffffff;          border1px solid #e1e4e8;          border-radius6px;          padding15px;          display: flex;          flex-direction: column;          height100%;          box-shadow0 1px 3px rgba(0000.05);      }      .panel-header-section {          display: flex;          justify-content: space-between;          align-items: center;          margin-bottom14px;          padding-bottom8px;          border-bottom1px solid #f0f0f0;      }      .panel-title-text {          font-size16px;          font-weight500;          color#333;      }      .action-buttons {          display: flex;          gap8px;      }      button {          padding6px 14px;          border1px solid #d9d9d9;          border-radius4px;          background#ffffff;          color#333;          font-size13px;          cursor: pointer;          transition: all 0.2s ease;      }      .btn-primary {          border-color#1890ff;          background#1890ff;          color: white;      }      .btn-primary:hover {          background#40a9ff;          border-color#40a9ff;      }      .btn-secondary {          border-color#52c41a;          background#52c41a;          color: white;      }      .btn-secondary:hover {          background#73d13d;          border-color#73d13d;      }      .btn-copy {          border-color#fa8c16;          background#fa8c16;          color: white;      }      .btn-copy:hover {          background#faad14;          border-color#faad14;      }      textarea {          width100%;          flex1;          padding10px;          border1px solid #d9d9d9;          border-radius4px;          font-family'Consolas''Monaco''Courier New', monospace;          font-size13px;          resize: vertical;          height500px;      }      #preFormattedOutput {          background-color#fafafa;          padding10px;          border1px solid #d9d9d9;          border-radius4px;          height500px;          white-space: pre-wrap;          word-wrap: break-word;          word-break: break-all;          max-width100%;          font-family'Consolas''Monaco''Courier New', monospace;          font-size13px;          overflow-y: auto;      }      .json-object-bracket {          color#006d6d;font-weight: bold;      }      .json-array-bracket {          color#005cc5;font-weight: bold;      }      .json-key-name {          color#d73a49;font-weight: bold;      }      .json-string-value {color#032f62;}      .json-number-value {color#9900cc;}      .json-boolean-value {color#005cc5;}      .json-null-value {color#005cc5;}      .json-comma-symbol {          color#333;          font-weight: bold;      }      .toggle-wrapper {          display: inline-block;          vertical-align: top;          margin-left5px;      }      .toggle-icon {          cursor: pointer;          user-select: none;          display: inline-block;          width15px;          color#1890ff;          font-weight: bold;          border1px solid #1890ff;          border-radius3px;          text-align: center;          font-size14px;          line-height13px;      }      .collapsible-content {          display: block;      }      .hidden {          display: none;      }      .brace-line {          line-height1.5;          white-space: nowrap;      }      select {          padding4px 8px;          border1px solid #d9d9d9;          border-radius4px;          background-color#fff;          font-size13px;          outline: none;          cursor: pointer;      }      select:focus {          border-color#1890ff;      }      .toast-notification {          position: fixed;          top15px;          right15px;          background-color#52c41a;          color: white;          padding8px 16px;          border-radius4px;          z-index1000;          display: none;          font-size13px;          box-shadow0 2px 8px rgba(0000.15);      }  </style></head><body><div class="container">  <h1>JSON格式化工具</h1>
  <div class="config-panel">    <div class="config-options-list">      <div class="config-option-item">        <input type="checkbox" id="toggleQuoteKeys" checked>        <label for="toggleQuoteKeys">引号</label>      </div>      <div class="config-option-item">        <input type="checkbox" id="toggleCollapsibleView" checked>        <label for="toggleCollapsibleView">显示控制</label>      </div>      <div class="config-option-item">        <input type="checkbox" id="toggleUnicodeEscape">        <label for="toggleUnicodeEscape">转义Unicode</label>      </div>      <div class="config-option-item">        <label for="selectIndentationSize">缩进量:</label>        <select id="selectIndentationSize">          <option value="1">1</option>          <option value="2" selected>2</option>          <option value="3">3</option>          <option value="4">4</option>          <option value="5">5</option>          <option value="6">6</option>        </select>      </div>    </div>  </div>
  <div class="main-layout">    <div class="panel-container">      <div class="panel-header-section">        <h2 class="panel-title-text">输入 JSON</h2>        <div class="action-buttons">          <button class="btn-primary" id="btnFormatJson">格式化</button>        </div>      </div>      <textarea id="textareaRawJson" placeholder="在此输入 JSON 数据..."></textarea>    </div>    <div class="panel-container">      <div class="panel-header-section">        <h2 class="panel-title-text">格式化输出</h2>        <div class="action-buttons">          <button class="btn-secondary" id="btnExpandAll">展开</button>          <button class="btn-copy" id="btnCopyFormatted">复制</button>        </div>      </div>      <pre id="preFormattedOutput"></pre>    </div>  </div>
  <div id="toastNotification" class="toast-notification">已复制到剪贴板</div></div><script>  let isJsonFormatted = false;  let collapsibleSectionId = 0;  let indentationString = '  ';  let formattingOptions = {    tabSize2,    quoteKeystrue,    isCollapsibletrue,    escapeUnicodetrue  };
  function generateIndentedLine(indent, data, isPropertyContent) {    const tabs = !isPropertyContent ? indentationString.repeat(indent) : "";    return tabs + (data && data.length > 0 && data.charAt(data.length - 1) !== "\n" ? data + "\n" : data);  }
  function formatPrimitiveValue(literal, quote, comma, indent, isArray, style) {    if (typeof literal === 'string' && formattingOptions.escapeUnicode) {      literal = literal.replace(/[\u007F-\uFFFF]/gfunction(chr) {        return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4);      });    }    if (typeof literal === 'string') {      literal = literal.replace(/</g'&lt;').replace(/>/g'&gt;');    }    const str = `<span class="json-${style}-value">${quote}${literal}${quote}</span>${comma}`;    return isArray ? generateIndentedLine(indent, str) : str;  }
  function formatJsonValue(obj, indent, addComma, isArray, isPropertyContent) {    let html = "";    const comma = addComma ? `<span class="json-comma-symbol">,</span>` : "";    const type = typeof obj;
    if (Array.isArray(obj)) {      if (obj.length === 0) {        html += generateIndentedLine(indent, `<span class="json-array-bracket">[ ]</span>${comma}`, isPropertyContent);      } else {        const sectionId = collapsibleSectionId++;        const isCollapsible = formattingOptions.isCollapsible;
        if (isCollapsible) {          html += generateIndentedLine(indent, `<span class="brace-line"><span class="json-array-bracket">[</span><span class="toggle-wrapper"><span class="toggle-icon" data-target="content-${sectionId}">-</span></span></span>`, isPropertyContent);          html += `<span id="content-${sectionId}" class="collapsible-content">`;        } else {          html += generateIndentedLine(indent, `<span class="json-array-bracket">[</span>`, isPropertyContent);        }        for (let i = 0; i < obj.length; i++) {          html += formatJsonValue(obj[i], indent + 1, i < (obj.length - 1), truefalse);        }        if (isCollapsible) html += `</span>`;        html += generateIndentedLine(indent, isCollapsible ? `<span class="brace-line"><span class="json-array-bracket">]</span>${comma}</span>` : `<span class="json-array-bracket">]</span>${comma}`);      }    } else if (obj === null) {      html += formatPrimitiveValue("null""", comma, indent, isArray, "null");    } else if (type === 'object') {      const keys = Object.keys(obj);      if (keys.length === 0) {        html += generateIndentedLine(indent, `<span class="json-object-bracket">{ }</span>${comma}`, isPropertyContent);      } else {        const sectionId = collapsibleSectionId++;        const isCollapsible = formattingOptions.isCollapsible;
        if (isCollapsible) {          html += generateIndentedLine(indent, `<span class="brace-line"><span class="json-object-bracket">{</span><span class="toggle-wrapper"><span class="toggle-icon" data-target="content-${sectionId}">-</span></span></span>`, isPropertyContent);          html += `<span id="content-${sectionId}" class="collapsible-content">`;        } else {          html += generateIndentedLine(indent, `<span class="json-object-bracket">{</span>`, isPropertyContent);        }
        for (let i = 0; i < keys.length; i++) {          const key = keys[i];          const quote = formattingOptions.quoteKeys ? '"' : '';          const hasMore = i < keys.length - 1;          html += generateIndentedLine(indent + 1`<span class="json-key-name">${quote}${key}${quote}</span>: ${formatJsonValue(obj[key], indent + 1, hasMore, falsetrue)}`);        }        if (isCollapsible) html += `</span>`;        html += generateIndentedLine(indent, isCollapsible ? `<span class="brace-line"><span class="json-object-bracket">}</span>${comma}</span>` : `<span class="json-object-bracket">}</span>${comma}`);      }    } else if (type === 'string') {      html += formatPrimitiveValue(obj, '"', comma, indent, isArray, "string");    } else if (type === 'number') {      html += formatPrimitiveValue(obj, "", comma, indent, isArray, "number");    } else if (type === 'boolean') {      html += formatPrimitiveValue(obj, "", comma, indent, isArray, "boolean");    }    return html;  }
  function performJsonFormatting(json) {    try {      let obj;      if (typeof json === 'object') {        obj = json;      } else {        if (json === "") {          document.getElementById('preFormattedOutput').innerHTML = "";          isJsonFormatted = false;          return;        }
        try {          obj = JSON.parse(json);        } catch (parseError) {          try {            const tempStr = `"${json}"`;            const unescapedStr = JSON.parse(tempStr);            obj = JSON.parse(unescapedStr);          } catch (secondError) {            throw parseError;          }        }      }      document.getElementById('preFormattedOutput').innerHTML = formatJsonValue(obj, 0falsefalsefalse);      isJsonFormatted = true;      attachCollapseEvents();    } catch (e) {      alert("JSON数据格式不正确:\n" + e.message);      document.getElementById('preFormattedOutput').innerHTML = "";      isJsonFormatted = false;    }  }
  function attachCollapseEvents() {    if (!isJsonFormatted) return;    document.querySelectorAll('.toggle-icon').forEach(function(icon) {      icon.addEventListener('click'function(e) {        e.preventDefault();        const targetId = icon.getAttribute('data-target');        const content = document.getElementById(targetId);
        if (content.classList.contains('hidden')) {          content.classList.remove('hidden');          icon.textContent = '-';        } else {          content.classList.add('hidden');          icon.textContent = '+';        }      });    });  }
  function expandAllSections() {    if (!isJsonFormatted) return;    document.querySelectorAll('.collapsible-content').forEach(function(c) {      c.classList.remove('hidden');    });    document.querySelectorAll('.toggle-icon').forEach(function(i) {      i.textContent = '-';    });  }
  document.addEventListener('DOMContentLoaded'function() {    function collectFormattingOptions() {      return {        tabSizeparseInt(document.getElementById('selectIndentationSize').value),        quoteKeysdocument.getElementById('toggleQuoteKeys').checked,        isCollapsibledocument.getElementById('toggleCollapsibleView').checked,        escapeUnicodedocument.getElementById('toggleUnicodeEscape').checked      };    }
    function refreshFormattingDisplay() {      const jsonText = document.getElementById('textareaRawJson').value;      if (jsonText.trim() !== "" && isJsonFormatted) {        formattingOptions = collectFormattingOptions();        indentationString = ' '.repeat(formattingOptions.tabSize);        performJsonFormatting(jsonText);      }    }
    document.getElementById('btnFormatJson').addEventListener('click'function() {      const jsonText = document.getElementById('textareaRawJson').value;      formattingOptions = collectFormattingOptions();      indentationString = ' '.repeat(formattingOptions.tabSize);      performJsonFormatting(jsonText);    });
    ['toggleQuoteKeys''toggleCollapsibleView''toggleUnicodeEscape'].forEach(function(id) {      document.getElementById(id).addEventListener('change', refreshFormattingDisplay);    });    document.getElementById('selectIndentationSize').addEventListener('change', refreshFormattingDisplay);
    document.getElementById('btnExpandAll').addEventListener('click'function(e) {      e.preventDefault();      expandAllSections();    });
    document.getElementById('btnCopyFormatted').addEventListener('click'function() {      const outputElement = document.getElementById('preFormattedOutput');      const tempElement = document.createElement('div');      tempElement.innerHTML = outputElement.innerHTML;      tempElement.querySelectorAll('.toggle-icon, .toggle-wrapper').forEach(function(el) {        el.remove();      });      const textToCopy = tempElement.innerText;      if (!textToCopy.trim()) {        alert('没有可复制的内容');        return;      }      navigator.clipboard.writeText(textToCopy).then(function() {        const notification = document.getElementById('toastNotification');        notification.style.display = 'block';        setTimeout(function() {          notification.style.display = 'none';        }, 2000);      }).catch(function(err) {        console.error('复制失败:', err);        alert('复制失败,请手动复制');      });    });    document.getElementById('btnFormatJson').click();  });</script></body></html>


阅读原文:https://mp.weixin.qq.com/s/xL7wP78FWMQVKuTrD1T7oA


该文章在 2025/12/22 19:03:25 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved