【前端数据解析】JSON.stringify()的威力

本文探讨如何使用JSON.stringify()及其替换技巧,高效地在不改变数据值的情况下,调整对象属性名,避免冗余代码。通过实例展示序列化替换的方法,以及JSON.stringify在处理特殊值和深拷贝中的行为和限制。

JSON.stringify()的威力

首先我们在开发的过程当中遇到这样一个处理数据的需求

const todayILearn = {
  _id: 1,
  content: '今天学习 JSON.stringify(),我很开心!',
  created_at: 'Mon Jun 25 2020 14:03:55 GMT+0800 (中国标准时间)',
  updated_at: 'Mon Jun 25 2020 16:03:55 GMT+0800 (中国标准时间)'
}

我们需要将上面这个对象处理成下面这个对象

const todayILearn = {
  id: 1,
  content: '今天学习 JSON.stringify(),我很开心!',
  createdAt: 'Mon Jun 25 2020 14:03:55 GMT+0800 (中国标准时间)',
  updatedAt: 'Mon Jun 25 2020 16:03:55 GMT+0800 (中国标准时间)'
}

也就是在不改变属性的值的前提下,将对象属性修改一下。 把_id改成 id,把 updated_at 改成 updatedAt,把 created_at 改成 createdAt

方案一:一次遍历+多声明一个变量

// 多一个变量存储
const todayILearnTemp = {};
for (const [key, value] of Object.entries(todayILearn)) {
  if (key === "_id") todayILearnTemp["id"] = value;
  else if (key === "created_at") todayILearnTemp["createdAt"] = value;
  else if (key === "updatedAt") todayILearnTemp["updatedAt"] = value;
  else todayILearnTemp[key] = value;
}
console.log(todayILearnTemp);
// 结果:
 { id: 1,
  content: '今天学习 JSON.stringify(),我很开心!',
  createdAt: 'Mon Jun 25 2020 14:03:55 GMT+0800 (中国标准时间)',
  updated_at: 'Mon Jun 25 2020 16:03:55 GMT+0800 (中国标准时间)' 
 }

方案一完全没有问题可以实现。但是多声明了一个变量又加上一层循环并且还有很多的 if else 语句,怎么都显得不太优雅。

方案二:暴力 delete 属性和增加属性

// 极致的暴力美学
todayILearn.id = todayILearn._id;
todayILearn.createdAt = todayILearn.created_at;
todayILearn.updatedAt = todayILearn.updated_at;
delete todayILearn._id;
delete todayILearn.created_at;
delete todayILearn.updated_at;
console.log(todayILearn);
{ 
  content: '今天学习 JSON.stringify(),我很开心!',
  id: 1,
  createdAt: 'Mon Jun 25 2020 14:03:55 GMT+0800 (中国标准时间)',
  updatedAt: 'Mon Jun 25 2020 16:03:55 GMT+0800 (中国标准时间)' 
}

直接 delete 暴力解决太粗鲁了,而且有一个缺点,属性的顺序可能会发生改变。

方案三:序列化+ replace 美学典范

const mapObj = {
  _id: "id",
  created_at: "createdAt",
  updated_at: "updatedAt"
};
JSON.parse(
  JSON.stringify(todayILearn).replace(
    /_id|created_at|updated_at/gi,
    matched => mapObj[matched])
    )
 { 
  id: 1,
  content: '今天学习 JSON.stringify(),我很开心!',
  createdAt: 'Mon Jun 25 2020 14:03:55 GMT+0800 (中国标准时间)',
  updatedAt: 'Mon Jun 25 2020 16:03:55 GMT+0800 (中国标准时间)' 
 }

瞬间感觉非常优雅和舒服,有木有!

补充:

JSON.parse(JSON.stringify(obj))我们一般用来深拷贝,其过程说白了 就是利用JSON.stringify() 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象;

序列化的作用是存储(对象本身存储的只是一个地址映射,如果断电,对象将不复存在,因此需将对象的内容转换成字符串的形式再保存在磁盘上来传输。

1、如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;

var test = {
    name: 'a',
    date: [new Date(1536627600000), new Date(1540047600000)],
};

let b;
b = JSON.parse(JSON.stringify(test))

总结几点:

  • undefined、任意的函数以及 symbol 作为对象属性值时 JSON.stringify() 对跳过(忽略)它们进行序列化

  • undefined、任意的函数以及 symbol 作为数组元素值时,JSON.stringify() 将会将它们序列化为 null

  • undefined、任意的函数以及 symbolJSON.stringify() 作为单独的值进行序列化时,都会返回 undefined

  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。

  • const data = {
      a: "aaa",
      b: undefined,
      c: Symbol("dd"),
      fn: function() {
        return true;
      },
      d: "ddd"
    };
    JSON.stringify(data); // 输出:?
    // "{"a":"aaa","d":"ddd"}"
    
    JSON.stringify(["aaa", undefined, function aa() {
        return true
      }, Symbol('dd'),"eee"])  // 输出:?
    
    // "["aaa",null,null,null,"eee"]"
    

    之前已经了解到JSON.stringify() 序列化时会忽略一些特殊的值,所以不能保证序列化后的字符串还是以特定的顺序出现(数组除外)。

  • JSON.stringify() 将会正常序列化 Date 的值。

    JSON.stringify({ now: new Date() });
    // "{"now":"2019-12-08T07:42:11.973Z"}"
    
  • NaNInfinity 格式的数值及 null 都会被当做 null

    JSON.stringify(NaN)
    // "null"
    JSON.stringify(null)
    // "null"
    JSON.stringify(Infinity)
    // "null"
    
  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。

    JSON.stringify([new Boolean(false)new Number(1), new String("false")]);
    // "[false,1,"false"]"
    
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

    // 不可枚举的属性默认会被忽略:
    JSON.stringify( 
        Object.create(
            null, 
            { 
                x: { value: 'json', enumerable: false }, 
                y: { value: 'stringify', enumerable: true } 
            }
        )
    );
    // "{"y","stringify"}"
    
  • 之前说的实现深拷贝最简单粗暴的方式就是序列化:JSON.parse(JSON.stringify()),这个方式实现深拷贝会因为序列化的诸多特性导致诸多的坑点:比如现在我们要说的循环引用问题。

    // 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。 
    const obj = {
      name: "loopObj"
    };
    const loopObj = {
      obj
    };
    // 对象之间形成循环引用,形成闭环
    obj.loopObj = loopObj;
    function deepClone(obj) {
      return JSON.parse(JSON.stringify(obj));
    }
    deepClone(obj)
    /**
     VM44:9 Uncaught TypeError: Converting circular structure to JSON
        --> starting at object with constructor 'Object'
        |     property 'loopObj' -> object with constructor 'Object'
        --- property 'obj' closes the circle
        at JSON.stringify (<anonymous>)
        at deepClone (<anonymous>:9:26)
        at <anonymous>:11:13
     */
    

    对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

    这也就是为什么用序列化去实现深拷贝时,遇到循环引用的对象会抛出错误的原因。

### 前端 JavaScript 深拷贝对象使用 `JSON.parse` 和 `JSON.stringify` 方法的问题与解决方案 #### 问题描述 在前端开发中,有时需要创建对象的副本而不影响原始对象。对于复杂的数据结构,简单的赋值操作只会复制引用而不是实际的对象内容。为了实现真正的深拷贝,开发者经常采用 `JSON.parse(JSON.stringify(object))` 这种方式[^4]。 然而这种方法并非万能,在某些情况下可能会遇到意想不到的行为: - **循环引用**:当对象内部含有指向自身的属性时,调用 `stringify()` 会抛出错误。 - **函数丢失**:由于 JSON 只支持特定几种数据类型的表示法,像 Function 类型这样的特殊成员会被忽略掉[^2]。 - **原型链破坏**:新生成的对象不会继承原有实例的方法或特性,因为它们只包含了可枚举自有属性及其值。 - **Date 对象及其他内置类**:日期和其他一些原生构造器产生的实体可能无法被正确恢复为相同类型的新实例[^3]。 #### 解决方案 针对上述提到的各种情况,这里给出几个改进建议以及替代方案: ##### 处理循环引用 可以通过自定义 replacer 函数来检测并跳过那些形成环路的部分,从而避免无限递归导致程序崩溃。不过更推荐的做法是寻找专门设计用来处理这种情况的库,比如 lodash 或者 structuredClone API (现代浏览器环境)。 ##### 保留函数和构造器信息 如果确实有必要保持这些细节不变的话,则不得不放弃纯粹依赖于标准库的方式转而编写更加复杂的逻辑去遍历整个目标树形结构逐项克隆每一个节点的同时记录下必要的元数据以便后续重建完整的功能集。这显然增加了不少工作量同时也降低了代码通用性和易读性所以除非绝对必要否则应尽量简化需求范围内的要求仅限于基础数值/字符串等简单项目即可满足大部分应用场景下的预期效果[^5]。 ##### 维护原型关系 考虑到大多数时候我们并不关心副件是否完全一致而是仅仅希望得到一份独立存在的镜像版本那么这个问题实际上是可以接受一定程度上的折衷即允许最终产物失去部分原本具有的行为特征只要不影响主要业务流程运转就行。当然如果有严格的要求要维持原有的方法列表也可以考虑手动重新挂载所需的功能模块到新的容器上去。 ```javascript function deepCopyWithPrototypes(source) { const copy = Object.create(Object.getPrototypeOf(source)); for (let key in source) { if (source.hasOwnProperty(key)) { copy[key] = typeof source[key] === 'object' && source[key] !== null ? deepCopyWithPrototypes(source[key]) : source[key]; } } return copy; } ``` ##### 特殊类型的支持 对于 Date 等特殊情况可以在序列化之前先做预处理将其转化为易于识别的形式然后再通过相应的逆向转换步骤确保能够准确无误地还原回原来的形态。 ```javascript const obj = { date: new Date('2023-01-01') }; // 序列化前准备 obj.date = obj.date.toISOString(); const jsonString = JSON.stringify(obj); console.log(jsonString); // 输出:"{"date":"2023-01-01T00:00:00.000Z"}" // 反序列化后修复 const parsedObj = JSON.parse(jsonString, function(k, v){ if (k === "date") return new Date(v); return v; }); console.log(parsedObj.date instanceof Date); // true ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

简杰不简单

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值