JavaScript 中 JSON 的处理方法

一、JSON 基础概念

1. JSON 与 JavaScript 对象的区别

javascript 复制代码
// JavaScript 对象
const jsObj = {
  name: "John",
  age: 30,
  isActive: true,
  sayHello: function() { return "Hello"; },  // 函数
  date: new Date(),                          // Date 对象
  undefined: undefined,                      // undefined
  [Symbol("id")]: 123                        // Symbol
};

// JSON 字符串(合法的 JSON 格式)
const jsonStr = `{
  "name": "John",
  "age": 30,
  "isActive": true,
  "hobbies": ["reading", "coding"],
  "address": {
    "city": "New York",
    "zip": "10001"
  }
}`;

2. JSON 数据格式特点

  • 仅支持的数据类型
    • 字符串(必须双引号)
    • 数字
    • 布尔值
    • 数组
    • 对象
    • null
  • 不支持的类型
    • 函数
    • Date 对象
    • undefined
    • Symbol
    • 循环引用

二、核心 API 方法

1. JSON.parse() - 字符串转对象

javascript 复制代码
// 基本使用
const jsonStr = '{"name":"John","age":30}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // "John"

// 第二个参数:reviver 函数(转换函数)
const jsonStr2 = '{"name":"John","age":30,"dob":"1990-01-01"}';
const obj2 = JSON.parse(jsonStr2, (key, value) => {
  if (key === 'dob') {
    return new Date(value);  // 将字符串转为 Date 对象
  }
  if (key === 'age' && value < 0) {
    return 0;  // 验证并修正数据
  }
  return value;
});

// 解析包含特殊字符
const jsonStr3 = '{"message":"Hello\\nWorld"}';
JSON.parse(jsonStr3); // message: "Hello\nWorld"

2. JSON.stringify() - 对象转字符串

javascript 复制代码
const obj = {
  name: "John",
  age: 30,
  isActive: true,
  hobbies: ["reading", "coding"],
  address: {
    city: "New York"
  }
};

// 基本使用
const jsonStr = JSON.stringify(obj);
// {"name":"John","age":30,"isActive":true,"hobbies":["reading","coding"],"address":{"city":"New York"}}

// 第二个参数:replacer(过滤器)
const jsonStr2 = JSON.stringify(obj, ['name', 'age']);  // 只保留指定属性
// {"name":"John","age":30}

// 第二个参数:replacer 函数
const jsonStr3 = JSON.stringify(obj, (key, value) => {
  if (typeof value === 'string') {
    return value.toUpperCase();  // 字符串转大写
  }
  if (key === 'age') {
    return undefined;  // 排除 age 属性
  }
  return value;
});

// 第三个参数:格式化(空格数或字符串)
const jsonStr4 = JSON.stringify(obj, null, 2);  // 2 空格缩进
const jsonStr5 = JSON.stringify(obj, null, '\t');  // 制表符缩进

// 处理 toJSON 方法
const objWithToJSON = {
  name: "John",
  date: new Date(),
  toJSON() {
    return {
      name: this.name,
      timestamp: this.date.getTime()
    };
  }
};
JSON.stringify(objWithToJSON); // {"name":"John","timestamp":...}

三、数据处理技巧

1. 深度克隆对象

javascript 复制代码
// 使用 JSON 进行深拷贝(有限制)
const original = {
  name: "John",
  hobbies: ["reading", "coding"],
  address: { city: "NYC" }
};

const cloned = JSON.parse(JSON.stringify(original));

// 注意:会丢失函数、Date对象等
const problematic = {
  date: new Date(),
  func: () => console.log("hi"),
  undefined: undefined,
  infinity: Infinity,
  nan: NaN
};
JSON.parse(JSON.stringify(problematic));
// {"date":"2024-01-01T00:00:00.000Z","infinity":null,"nan":null}

2. 数据验证与清洗

javascript 复制代码
// 验证 JSON 字符串是否有效
function isValidJSON(str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    return false;
  }
}

// 安全解析
function safeJSONParse(str, defaultValue = {}) {
  try {
    return JSON.parse(str);
  } catch (error) {
    console.warn('JSON 解析失败:', error.message);
    return defaultValue;
  }
}

// 数据清洗
function cleanJSONData(obj) {
  return JSON.parse(JSON.stringify(obj, (key, value) => {
    // 移除空字符串
    if (value === "") return undefined;
    // 移除 null
    if (value === null) return undefined;
    // 处理特殊数字
    if (typeof value === 'number' && !isFinite(value)) {
      return null;
    }
    return value;
  }));
}

3. 自定义序列化

javascript 复制代码
// 处理特殊类型
class User {
  constructor(name, birthDate) {
    this.name = name;
    this.birthDate = birthDate;
  }
  
  toJSON() {
    return {
      name: this.name,
      birthDate: this.birthDate.toISOString().split('T')[0],
      age: new Date().getFullYear() - this.birthDate.getFullYear()
    };
  }
  
  static fromJSON(json) {
    return new User(json.name, new Date(json.birthDate));
  }
}

const user = new User("Alice", new Date(1990, 0, 1));
const json = JSON.stringify(user);
const restoredUser = User.fromJSON(JSON.parse(json));

四、性能优化

1. 大数据量处理

javascript 复制代码
// 流式处理大数据
async function processLargeJSON(jsonString) {
  // 使用 JSON.parse 的 reviver 进行流式处理
  return JSON.parse(jsonString, (key, value) => {
    // 在解析时即时处理数据
    if (key === 'timestamp' && typeof value === 'string') {
      return new Date(value);
    }
    return value;
  });
}

// 分块处理
function* chunkJSONParse(jsonString, chunkSize = 1024 * 1024) {
  const obj = JSON.parse(jsonString);
  const keys = Object.keys(obj);
  
  for (let i = 0; i < keys.length; i += chunkSize) {
    const chunk = {};
    for (let j = i; j < Math.min(i + chunkSize, keys.length); j++) {
      const key = keys[j];
      chunk[key] = obj[key];
    }
    yield chunk;
  }
}

2. 缓存与复用

javascript 复制代码
// JSON 解析缓存
const jsonCache = new Map();

function parseWithCache(jsonString) {
  if (jsonCache.has(jsonString)) {
    return jsonCache.get(jsonString);
  }
  
  const result = JSON.parse(jsonString);
  jsonCache.set(jsonString, result);
  return result;
}

// 序列化缓存
const stringifyCache = new WeakMap();

function stringifyWithCache(obj) {
  if (stringifyCache.has(obj)) {
    return stringifyCache.get(obj);
  }
  
  const result = JSON.stringify(obj);
  stringifyCache.set(obj, result);
  return result;
}

五、高级应用场景

1. 配置文件处理

javascript 复制代码
// 配置文件解析
class ConfigManager {
  constructor(configPath) {
    this.configPath = configPath;
    this.config = this.loadConfig();
  }
  
  loadConfig() {
    try {
      const configStr = fs.readFileSync(this.configPath, 'utf-8');
      return JSON.parse(configStr, (key, value) => {
        // 处理环境变量替换
        if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {
          const envVar = value.slice(2, -1);
          return process.env[envVar] || value;
        }
        return value;
      });
    } catch (error) {
      throw new Error(`配置文件解析失败: ${error.message}`);
    }
  }
  
  saveConfig(config) {
    const configStr = JSON.stringify(config, null, 2);
    fs.writeFileSync(this.configPath, configStr);
  }
}

2. API 数据处理

javascript 复制代码
// 统一的 API 响应处理
class APIResponseHandler {
  static async handleResponse(response) {
    const text = await response.text();
    
    try {
      const data = JSON.parse(text, (key, value) => {
        // 转换日期字符串
        if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}/.test(value)) {
          const date = new Date(value);
          return isNaN(date.getTime()) ? value : date;
        }
        // 转换数字字符串
        if (typeof value === 'string' && /^-?\d+(\.\d+)?$/.test(value)) {
          return Number(value);
        }
        return value;
      });
      
      return {
        success: true,
        data,
        status: response.status
      };
    } catch (error) {
      return {
        success: false,
        error: 'JSON 解析失败',
        raw: text,
        status: response.status
      };
    }
  }
}

3. JSON Schema 验证

javascript 复制代码
// 使用 JSON Schema 验证数据
const userSchema = {
  type: "object",
  required: ["name", "email"],
  properties: {
    name: { type: "string", minLength: 2 },
    email: { type: "string", format: "email" },
    age: { type: "integer", minimum: 0, maximum: 150 },
    hobbies: {
      type: "array",
      items: { type: "string" }
    }
  }
};

function validateJSONAgainstSchema(jsonStr, schema) {
  const obj = JSON.parse(jsonStr);
  
  // 验证必需字段
  if (schema.required) {
    for (const field of schema.required) {
      if (!(field in obj)) {
        throw new Error(`缺少必需字段: ${field}`);
      }
    }
  }
  
  // 验证类型
  for (const [key, value] of Object.entries(obj)) {
    if (schema.properties && schema.properties[key]) {
      const propSchema = schema.properties[key];
      validateType(value, propSchema, key);
    }
  }
  
  return obj;
}

六、安全注意事项

1. 防止 JSON 注入攻击

javascript 复制代码
// 不安全的方式
const userInput = '{"name": "John", "__proto__": {"admin": true}}';
const obj = JSON.parse(userInput); // 可能污染原型链

// 安全的解析方式
function safeJSONParse(str) {
  const obj = JSON.parse(str);
  // 检查并过滤危险属性
  const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
  dangerousKeys.forEach(key => {
    if (key in obj) {
      delete obj[key];
    }
  });
  return obj;
}

// 使用 Object.create(null)
const safeObj = Object.create(null);
Object.assign(safeObj, JSON.parse(userInput));

2. 处理循环引用

javascript 复制代码
// 检测循环引用
function stringifyWithCircularCheck(obj) {
  const seen = new WeakSet();
  
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return '[Circular Reference]';
      }
      seen.add(value);
    }
    return value;
  });
}

// 自定义循环引用处理
class CircularSafeJSON {
  static stringify(obj) {
    const cache = [];
    const result = JSON.stringify(obj, (key, value) => {
      if (typeof value === 'object' && value !== null) {
        if (cache.includes(value)) {
          return `[Circular:${cache.indexOf(value)}]`;
        }
        cache.push(value);
      }
      return value;
    });
    return result;
  }
}

七、实用工具函数

1. 常用工具函数集

javascript 复制代码
class JSONUtils {
  // 美化输出
  static prettyPrint(obj, indent = 2) {
    return JSON.stringify(obj, null, indent);
  }
  
  // 压缩 JSON
  static compress(obj) {
    return JSON.stringify(obj);
  }
  
  // 深度比较两个 JSON 对象
  static deepEqual(obj1, obj2) {
    return JSON.stringify(obj1) === JSON.stringify(obj2);
  }
  
  // 获取 JSON 差异
  static diff(obj1, obj2) {
    const keys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
    const differences = {};
    
    for (const key of keys) {
      const val1 = obj1[key];
      const val2 = obj2[key];
      
      if (JSON.stringify(val1) !== JSON.stringify(val2)) {
        differences[key] = { from: val1, to: val2 };
      }
    }
    
    return differences;
  }
  
  // 合并 JSON 对象
  static merge(target, ...sources) {
    sources.forEach(source => {
      const parsed = typeof source === 'string' ? 
        JSON.parse(source) : source;
      Object.assign(target, parsed);
    });
    return target;
  }
}

2. JSON Path 查询

javascript 复制代码
// 简单的 JSON Path 查询
class JSONPath {
  static query(obj, path) {
    const parts = path.split('.');
    let current = obj;
    
    for (const part of parts) {
      if (current === null || current === undefined) {
        return undefined;
      }
      
      // 处理数组索引
      if (part.includes('[') && part.includes(']')) {
        const [key, indexStr] = part.split('[');
        const index = parseInt(indexStr.replace(']', ''), 10);
        current = current[key][index];
      } else {
        current = current[part];
      }
    }
    
    return current;
  }
  
  static set(obj, path, value) {
    const parts = path.split('.');
    let current = obj;
    
    for (let i = 0; i < parts.length - 1; i++) {
      const part = parts[i];
      if (!current[part] || typeof current[part] !== 'object') {
        current[part] = {};
      }
      current = current[part];
    }
    
    current[parts[parts.length - 1]] = value;
    return obj;
  }
}

八、现代 JavaScript 特性

1. 使用 Proxy 监控 JSON 操作

javascript 复制代码
function createObservableJSON(obj) {
  const history = [];
  
  return new Proxy(obj, {
    set(target, property, value) {
      const oldValue = target[property];
      target[property] = value;
      
      history.push({
        timestamp: new Date(),
        property,
        oldValue: JSON.stringify(oldValue),
        newValue: JSON.stringify(value)
      });
      
      return true;
    },
    
    getHistory() {
      return history;
    }
  });
}

2. 使用 Decorators(需要 Babel)

javascript 复制代码
// JSON 序列化装饰器
function jsonSerializable(target) {
  target.prototype.toJSON = function() {
    return Object.getOwnPropertyNames(this).reduce((obj, key) => {
      const value = this[key];
      // 排除函数
      if (typeof value !== 'function') {
        obj[key] = value;
      }
      return obj;
    }, {});
  };
  
  return target;
}

@jsonSerializable
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
  
  greet() {
    return `Hello, ${this.name}`;
  }
}

最佳实践总结

  1. 始终验证 JSON 字符串 :使用 try-catch 包装 JSON.parse()
  2. 合理使用 reviver/replacer:处理特殊数据类型
  3. 注意性能:大数据量时考虑分块处理
  4. 确保安全性:过滤危险属性,防止原型污染
  5. 保持向后兼容:处理新增/删除字段的情况
  6. 使用 Schema 验证:确保数据结构符合预期
  7. 考虑使用第三方库 :复杂需求可使用 ajvjson5 等库
相关推荐
烛阴2 小时前
C# 正则表达式(3):分组与捕获——从子串提取到命名分组
前端·正则表达式·c#
eason_fan3 小时前
从一则内存快照看iframe泄漏:活跃与Detached状态的回收差异
前端·性能优化
狗头大军之江苏分军3 小时前
年底科技大考:2025 中国前端工程师的 AI 辅助工具实战盘点
java·前端·后端
编程修仙4 小时前
第三篇 Vue路由
前端·javascript·vue.js
Lxinccode4 小时前
python(67) : json解析异常json.decoder.JSONDecodeError
json·json解析·json解析报错·jsondecodeerror
比老马还六4 小时前
Bipes项目二次开发/硬件编程-设备连接(七)
前端·javascript
掘金一周4 小时前
前端一行代码生成数千页PDF,dompdf.js新增分页功能| 掘金一周 12.25
前端·javascript·后端
张就是我1065924 小时前
漏洞复现指南:利用 phpinfo() 绕过 HttpOnly Cookie 保护
前端
Kagol4 小时前
🎉TinyVue v3.27.0 正式发布:增加 Space 新组件,ColorPicker 组件支持线性渐变
前端·vue.js·typescript