JSON数据处理实战指南:格式化、验证与转换
JSON(JavaScript Object Notation)已成为Web开发中最流行的数据交换格式。无论是调用API、配置文件还是数据存储,JSON无处不在。本文将深入探讨JSON数据处理的各个方面,帮助你成为JSON处理专家。
目录
JSON基础回顾
什么是JSON?
JSON是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。
{ "name": "张三", "age": 30, "skills": ["JavaScript", "Python", "Go"], "address": { "city": "北京", "district": "朝阳区" }, "isActive": true, "salary": null }
JSON vs JavaScript对象
虽然语法相似,但有重要区别:
| 特性 | JSON | JavaScript对象 |
|---|---|---|
| 键名 | 必须用双引号 | 可以不用引号 |
| 字符串 | 只能用双引号 | 单双引号都可以 |
| 值类型 | 6种(字符串、数字、布尔、null、数组、对象) | 任意类型(函数、Date等) |
| 注释 | 不支持 | 支持 |
| 末尾逗号 | 不允许 | 允许 |
// ❌ 无效的JSON { name: 'John', // 键名没有引号 'age': 30, // 单引号 hobbies: ['读书',], // 末尾逗号 // 这是注释 // 不允许注释 birthday: new Date() // 不支持Date对象 } // ✅ 有效的JSON { "name": "John", "age": 30, "hobbies": ["读书"], "birthday": "2024-01-01T00:00:00.000Z" }
JSON格式化与美化
为什么需要格式化?
API返回的JSON通常是压缩的(minified),难以阅读:
{ "name": "张三", "age": 30, "skills": ["JavaScript", "Python"], "address": { "city": "北京" } }
格式化后:
{ "name": "张三", "age": 30, "skills": ["JavaScript", "Python"], "address": { "city": "北京" } }
JavaScript原生格式化
// JSON.stringify() 第三个参数控制缩进 const data = { name: '张三', age: 30, skills: ['JavaScript', 'Python'], }; // 美化输出(缩进2个空格) console.log(JSON.stringify(data, null, 2)); // 美化输出(缩进4个空格) console.log(JSON.stringify(data, null, 4)); // 使用制表符缩进 console.log(JSON.stringify(data, null, '\t'));
自定义格式化
function formatJSON(obj, options = {}) { const { indent = 2, // 缩进空格数 maxLineLength = 80, // 最大行长度 sortKeys = false, // 是否排序键名 } = options; // 如果需要排序键名 if (sortKeys && typeof obj === 'object' && obj !== null) { obj = sortObjectKeys(obj); } return JSON.stringify(obj, null, indent); } // 递归排序对象键名 function sortObjectKeys(obj) { if (Array.isArray(obj)) { return obj.map(sortObjectKeys); } if (typeof obj === 'object' && obj !== null) { return Object.keys(obj) .sort() .reduce((sorted, key) => { sorted[key] = sortObjectKeys(obj[key]); return sorted; }, {}); } return obj; } // 使用 const data = { z: 1, a: 2, m: { y: 3, b: 4 } }; console.log(formatJSON(data, { sortKeys: true })); // { // "a": 2, // "m": { // "b": 4, // "y": 3 // }, // "z": 1 // }
压缩JSON(Minify)
function minifyJSON(jsonString) { try { // 先解析再压缩,确保格式正确 const obj = JSON.parse(jsonString); return JSON.stringify(obj); } catch (error) { throw new Error('Invalid JSON'); } } const formatted = `{ "name": "张三", "age": 30 }`; console.log(minifyJSON(formatted)); // {"name":"张三","age":30}
JSON验证与错误处理
常见JSON错误
// 1. 多余的逗号 const invalid1 = '{"name": "John",}'; // ❌ // 2. 单引号 const invalid2 = "{'name': 'John'}"; // ❌ // 3. 未转义的字符 const invalid3 = '{"message": "She said "Hi""}'; // ❌ // 4. 数字前导零 const invalid4 = '{"id": 001}'; // ❌ // 5. NaN或Infinity const invalid5 = '{"value": NaN}'; // ❌
安全的JSON解析
function safeJSONParse(jsonString, fallback = null) { try { return JSON.parse(jsonString); } catch (error) { console.error('JSON解析失败:', error.message); return fallback; } } // 使用 const result = safeJSONParse('invalid json', {}); console.log(result); // {} // 更详细的错误处理 function parseJSONWithError(jsonString) { try { return { success: true, data: JSON.parse(jsonString), error: null, }; } catch (error) { return { success: false, data: null, error: { message: error.message, position: extractErrorPosition(error.message), }, }; } } function extractErrorPosition(errorMessage) { // Chrome: "Unexpected token } in JSON at position 25" const match = errorMessage.match(/position (\d+)/); return match ? parseInt(match[1]) : null; } // 使用 const result = parseJSONWithError('{"name": "John",}'); if (!result.success) { console.log(`错误位置: ${result.error.position}`); console.log(`错误信息: ${result.error.message}`); }
JSON Schema验证
// 简化的JSON Schema验证器 function validateJSON(data, schema) { const errors = []; function validate(value, schemaNode, path = '') { // 检查类型 if (schemaNode.type) { const actualType = Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value; if (actualType !== schemaNode.type) { errors.push({ path, message: `Expected type ${schemaNode.type}, got ${actualType}`, }); return; } } // 检查必需字段 if (schemaNode.type === 'object' && schemaNode.required) { schemaNode.required.forEach((key) => { if (!(key in value)) { errors.push({ path: `${path}.${key}`, message: `Required field missing`, }); } }); } // 递归验证对象属性 if (schemaNode.type === 'object' && schemaNode.properties) { Object.keys(schemaNode.properties).forEach((key) => { if (key in value) { validate(value[key], schemaNode.properties[key], `${path}.${key}`); } }); } // 递归验证数组元素 if (schemaNode.type === 'array' && schemaNode.items) { value.forEach((item, index) => { validate(item, schemaNode.items, `${path}[${index}]`); }); } } validate(data, schema); return { valid: errors.length === 0, errors, }; } // 使用示例 const schema = { type: 'object', required: ['name', 'age'], properties: { name: { type: 'string' }, age: { type: 'number' }, email: { type: 'string' }, }, }; const data = { name: 'John', age: '30', // 错误:应该是数字 }; const result = validateJSON(data, schema); console.log(result); // { // valid: false, // errors: [ // { path: '.age', message: 'Expected type number, got string' } // ] // }
JSON数据转换
JSON ↔️ CSV
// JSON转CSV function jsonToCSV(jsonArray) { if (!Array.isArray(jsonArray) || jsonArray.length === 0) { return ''; } // 获取所有键名作为表头 const headers = Object.keys(jsonArray[0]); // 创建CSV行 const csvRows = [ headers.join(','), // 表头 ...jsonArray.map((obj) => headers .map((header) => { const value = obj[header]; // 处理包含逗号或引号的值 if (typeof value === 'string' && (value.includes(',') || value.includes('"'))) { return `"${value.replace(/"/g, '""')}"`; } return value; }) .join(',') ), ]; return csvRows.join('\n'); } // 使用 const users = [ { name: 'John', age: 30, city: 'New York' }, { name: 'Jane', age: 25, city: 'Los Angeles' }, ]; console.log(jsonToCSV(users)); // name,age,city // John,30,New York // Jane,25,Los Angeles // CSV转JSON function csvToJSON(csv) { const lines = csv.trim().split('\n'); const headers = lines[0].split(','); return lines.slice(1).map((line) => { const values = line.split(','); return headers.reduce((obj, header, index) => { obj[header.trim()] = values[index].trim(); return obj; }, {}); }); }
JSON ↔️ XML
// JSON转XML(简化版) function jsonToXML(obj, rootName = 'root') { function convert(obj, name) { if (Array.isArray(obj)) { return obj.map((item) => convert(item, name)).join(''); } if (typeof obj === 'object' && obj !== null) { const children = Object.entries(obj) .map(([key, value]) => convert(value, key)) .join(''); return `<${name}>${children}</${name}>`; } return `<${name}>${obj}</${name}>`; } return `<?xml version="1.0" encoding="UTF-8"?>\n${convert(obj, rootName)}`; } // 使用 const data = { user: { name: 'John', age: 30, skills: ['JavaScript', 'Python'], }, }; console.log(jsonToXML(data)); // <?xml version="1.0" encoding="UTF-8"?> // <root><user><name>John</name><age>30</age><skills>JavaScript</skills><skills>Python</skills></user></root>
深度合并JSON
function deepMerge(target, source) { const output = { ...target }; if (isObject(target) && isObject(source)) { Object.keys(source).forEach((key) => { if (isObject(source[key])) { if (!(key in target)) { output[key] = source[key]; } else { output[key] = deepMerge(target[key], source[key]); } } else { output[key] = source[key]; } }); } return output; } function isObject(item) { return item && typeof item === 'object' && !Array.isArray(item); } // 使用 const defaults = { theme: 'light', colors: { primary: 'blue', secondary: 'green', }, }; const userConfig = { colors: { primary: 'red', }, fontSize: 16, }; const config = deepMerge(defaults, userConfig); console.log(config); // { // theme: 'light', // colors: { // primary: 'red', // 覆盖 // secondary: 'green' // 保留 // }, // fontSize: 16 // 新增 // }
处理大型JSON文件
流式解析
// 使用Web Streams API处理大型JSON async function processLargeJSON(url) { const response = await fetch(url); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // 处理完整的JSON对象 let boundary = buffer.lastIndexOf('}\n'); if (boundary !== -1) { const chunk = buffer.substring(0, boundary + 1); buffer = buffer.substring(boundary + 1); // 处理chunk中的数据 chunk.split('\n').forEach((line) => { if (line.trim()) { try { const obj = JSON.parse(line); processItem(obj); } catch (e) { console.error('Parse error:', e); } } }); } } } function processItem(item) { console.log('Processing:', item); // 处理每个项目 }
分块读取
async function readJSONInChunks(file, chunkSize = 1024 * 1024) { const fileSize = file.size; let offset = 0; while (offset < fileSize) { const chunk = file.slice(offset, offset + chunkSize); const text = await chunk.text(); // 处理chunk processChunk(text); offset += chunkSize; } } // 使用FileReader逐行读取 function readJSONLineByLine(file, callback) { const reader = new FileReader(); let lineBuffer = ''; reader.onload = (e) => { const text = e.target.result; const lines = (lineBuffer + text).split('\n'); // 保留最后不完整的行 lineBuffer = lines.pop(); lines.forEach((line) => { if (line.trim()) { try { const obj = JSON.parse(line); callback(obj); } catch (err) { console.error('Parse error:', err); } } }); }; reader.readAsText(file); }
JSON安全性
防止原型污染
// ❌ 危险:可能污染原型 function unsafeMerge(target, source) { for (let key in source) { target[key] = source[key]; } return target; } // 攻击示例 const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}'); unsafeMerge({}, malicious); console.log({}.isAdmin); // true - 原型被污染! // ✅ 安全:检查键名 function safeMerge(target, source) { for (let key in source) { if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; // 跳过危险键名 } if (source.hasOwnProperty(key)) { target[key] = source[key]; } } return target; }
安全的JSON解析
// 使用JSON.parse的reviver参数过滤危险内容 function secureJSONParse(jsonString) { return JSON.parse(jsonString, (key, value) => { // 过滤危险键名 if (key === '__proto__' || key === 'constructor') { return undefined; } // 过滤可能的XSS if (typeof value === 'string') { // 移除潜在的脚本标签 value = value.replace(/<script[^>]*>.*?<\/script>/gi, ''); } return value; }); }
防止拒绝服务攻击(DoS)
// 限制JSON大小和深度 function parseJSONSafely(jsonString, options = {}) { const { maxSize = 10 * 1024 * 1024, // 最大10MB maxDepth = 20, // 最大深度20层 } = options; // 检查大小 if (jsonString.length > maxSize) { throw new Error('JSON too large'); } const obj = JSON.parse(jsonString); // 检查深度 function checkDepth(obj, currentDepth = 0) { if (currentDepth > maxDepth) { throw new Error('JSON too deep'); } if (typeof obj === 'object' && obj !== null) { Object.values(obj).forEach((value) => { checkDepth(value, currentDepth + 1); }); } } checkDepth(obj); return obj; }
实战案例
案例1:API响应数据处理
async function fetchAndProcessAPI(url) { try { const response = await fetch(url); // 检查响应状态 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 解析JSON const data = await response.json(); // 验证数据结构 if (!data.results || !Array.isArray(data.results)) { throw new Error('Invalid API response structure'); } // 转换数据格式 const processedData = data.results.map((item) => ({ id: item.id, title: item.title || 'Untitled', date: new Date(item.created_at).toLocaleDateString('zh-CN'), tags: item.tags || [], })); return processedData; } catch (error) { console.error('API处理失败:', error); throw error; } } // 使用 fetchAndProcessAPI('https://api.example.com/data') .then((data) => { console.log('处理后的数据:', data); }) .catch((error) => { console.error('错误:', error); });
案例2:配置文件管理
class ConfigManager { constructor(defaultConfig = {}) { this.config = defaultConfig; } // 加载配置 async load(url) { try { const response = await fetch(url); const config = await response.json(); // 合并配置 this.config = deepMerge(this.config, config); // 验证配置 this.validate(); return this.config; } catch (error) { console.error('配置加载失败:', error); throw error; } } // 验证配置 validate() { const required = ['apiUrl', 'timeout', 'retryCount']; required.forEach((key) => { if (!(key in this.config)) { throw new Error(`缺少必需配置项: ${key}`); } }); } // 获取配置 get(key, defaultValue = null) { return key .split('.') .reduce((obj, k) => (obj && obj[k] !== undefined ? obj[k] : defaultValue), this.config); } // 保存配置 save() { const json = JSON.stringify(this.config, null, 2); localStorage.setItem('app-config', json); } } // 使用 const config = new ConfigManager({ apiUrl: 'https://api.example.com', timeout: 5000, retryCount: 3, }); await config.load('/config.json'); console.log(config.get('apiUrl')); console.log(config.get('features.darkMode', false));
案例3:数据导入导出工具
class DataExporter { constructor(data) { this.data = data; } // 导出为JSON toJSON(pretty = true) { return JSON.stringify(this.data, null, pretty ? 2 : 0); } // 导出为CSV toCSV() { return jsonToCSV(this.data); } // 导出为Excel(简化版,实际使用需要库支持) toExcelData() { if (!Array.isArray(this.data)) { throw new Error('Data must be an array'); } const headers = Object.keys(this.data[0]); const rows = this.data.map((obj) => headers.map((h) => obj[h])); return [headers, ...rows]; } // 下载文件 download(filename, format = 'json') { let content, mimeType; switch (format) { case 'json': content = this.toJSON(); mimeType = 'application/json'; break; case 'csv': content = this.toCSV(); mimeType = 'text/csv'; break; default: throw new Error('Unsupported format'); } const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${filename}.${format}`; a.click(); URL.revokeObjectURL(url); } } // 使用 const data = [ { name: 'John', age: 30, city: 'New York' }, { name: 'Jane', age: 25, city: 'Los Angeles' }, ]; const exporter = new DataExporter(data); exporter.download('users', 'json'); exporter.download('users', 'csv');
总结
JSON数据处理是Web开发的核心技能。掌握以下要点:
关键要点
- 格式化:使用
JSON.stringify(obj, null, 2)美化输出 - 验证:始终使用
try-catch包裹JSON.parse() - 安全:防止原型污染和XSS攻击
- 性能:大文件使用流式处理
- 转换:熟练掌握JSON与其他格式的互转
最佳实践
- ✅ 使用TypeScript或JSON Schema验证数据结构
- ✅ 对外部JSON数据进行安全检查
- ✅ 限制JSON大小和嵌套深度
- ✅ 使用合适的工具处理大型文件
- ✅ 保持JSON结构简单清晰
相关工具
- 📝 在线JSON格式化工具 - 实时格式化和验证
- 🔄 CSV/JSON互转工具 - 快速转换数据格式
- 🔐 Base64编码工具 - 编码二进制数据
关键词: JSON, JSON格式化, JSON验证, 数据转换, API处理
更新时间: 2026-01-05
相关阅读
其他
JSON 格式化完全指南:从入门到精通
深入理解 JSON 格式化的原理、最佳实践和常见问题解决方案。包含真实案例和性能优化技巧。
2026-01-07
其他
TypeScript 类型体操:什么时候值,什么时候在炫技
务实的 TypeScript 高级类型指南 — mapped types、conditional types、template literal types 真正能给你什么,什么时候用,什么时候应该退回到朴素代码。
2026-05-21
其他
Kubernetes 资源 requests / limits 实战:不会把生产搞挂的设法
怎么在生产里实际设 Kubernetes CPU 与内存的 requests/limits — QoS 类、CPU 节流、OOM kill、那些害公司钱的差别,以及好使的模式。
2026-05-18