JS保持JSON中读取顺序一致的方法

昨天临时写个小脚本,表单内容保存到JSON中,需要按照写入顺序读取。
JS无法直接做到写入和读取顺序一致。本人能力有限,只能想到3种方法,每种方法适用场景不同,要根据前后端需求选择。

一、JSON对象保存成JSON数组。

把键值对拆分开,每个键值对单独保存到每个数组项上,利用数组保证顺序的一致性。

{
    "name": "米米奇",
    "age": 24,
    "city": "仓群峡谷"
}

保存成如下格式:

[
    {"name": "米米奇"},
    {"age": 24},
    {"city": "仓群峡谷"}
]

但这个方法遍历起来很麻烦,因为js遍历时JSON对象时,必须要对key循环。另外查找也很麻烦,不能直接用in操作符,需要遍历数组,再逐项对比key才可以。

arr.forEach(obj=>{
    for(let key in obj){
        if(key=="name"){
            ……
        }
    }
});

优点:

  1. 可以保证插入顺序。
  2. 反序列化和序列化后顺序不变。
  3. 反序列化后文件体积增加不大。

缺点:

  1. 遍历查找困难。

二、对JSON数组进行改进。

为了解决第一个方法中遍历和查找困难问题,修改一下数据结构:

[
    {key: "name", value: "米米奇"},
    {key: "age":  value: 24},
    {key: "city": value:  "仓群峡谷"}
]

明确定义键值对的名称,这样每个数组项遍历和查找时,就可以用key直接定位,不需要嵌套循环了。

arr.forEach(obj=>{
    if(obj.key=="name"){
        ……
    }
});

代码相对与第一个确实简单多了。

优点:

  1. 可以保证插入顺序。
  2. 反序列化和序列化后顺序不变。
  3. 遍历查找相对简单。

缺点:

  1. 反序列化后文件体积变大,尤其值主要为数字时。
  2. 需要约定好key-value项的名称。

三、其他方法。

  1. 用额外数组记录key的顺序,暴露给外部自定义的方法(getter、setter、forEach、delete等),但是这样代码要改动好多,原生方法都不能用了,还要维护调用接口,数据一致性难以确保。不推荐自己造轮子。
  2. 如果无反序列化要求,可以利用ES6 Proxy代理,记录JSON对象的插入、修改操作,内部数组记录key的顺序,遍历时直接返回内部保存的key数组。直接上代码:

    /**
     * 用于代理实现json遍历顺序和插入时一致
     * 用法:var proxy = JSONProxy();
     * 然后可以直接用原生方法操作,for(key in obj){}遍历时key就是添加顺序
     * 注意:重新赋值时,会把key移到末尾,如果不需要可以修改代码
     */
    function JSONProxy(obj){
     return new Proxy(obj||{}, {
         // 用于保存健的插入顺序
         keys:[],
         //设置属性时,检测并保存key,这里其实有两种算法,看需求
         set: function(target, key, value, receiver){
             let index=this.keys.indexOf(key);
             if(index>=0){
                 this.keys.splice(index,1); // 删除已有健
             }
             this.keys[this.keys.length]=key; // 添加到末尾
             return target[key]=value;
         },
         // 拦截属性删除操作
         deleteProperty:function(target, key) {
             let index=this.keys.indexOf(key);
             if(index>=0){
                 this.keys.splice(index,1);
             }
             return delete target[key];
         },
         // 遍历获取键值时的操作,for...in循环,返回一个数组。
         ownKeys:function(target){
             return this.keys;
         }
     });
    }

    因为使用了代理,修改一下变量定义外,现有代码再不需要改动。代码:

    let wuta={};
    let person=JSONProxy(wuta);
    
    // 赋值
    person.name="小虾米"
    person.country="萌国";
    person.city="葫芦岛";
    
    // 循环遍历
    for(let key in person){
     …… // key顺序'name', 'country','city'
    }
    
    // 获取所有key数组。
    let keys=Object.keys(person); // 输出 ['name', 'country', 'city']
    // 或者
    let keys=Object.getOwnPropertyNames(person); 
    // 上面无法获取内部属性了,JSON也不需要吧。可以从被代理对象获取,但无法排序。

优点:

  1. 可以保证插入顺序。
  2. 与原生对象一样的操作方法,现有代码无需改动。

缺点:

  1. 不能确保反序列化和序列化后顺序。
  2. 代理对性能会有影响。

结论:

1、持久化或者反序列化有要求时,可以优先采用方法二;
2、只是临时操作,可以考虑Proxy代理模式,操作最方便。

THE END

标签: Javascript

本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。