Javascript 如何在本地存储(或其他地方)中持久化ES6映射?

Javascript 如何在本地存储(或其他地方)中持久化ES6映射?,javascript,json,dictionary,ecmascript-6,Javascript,Json,Dictionary,Ecmascript 6,不幸的是JSON.stringify(a)只返回“{}”,这意味着在还原时a将变成空对象 我发现它允许在映射和普通对象之间进行上/下转换,因此这可能是一种解决方案,但我希望我需要借助外部依赖来保持映射。假设键和值都是可序列化的 var a = new Map([[ 'a', 1 ]]); a.get('a') // 1 var forStorageSomewhere = JSON.stringify(a); // Store, in my case, in localStorage. //

不幸的是
JSON.stringify(a)
只返回“{}”,这意味着在还原时a将变成空对象


我发现它允许在映射和普通对象之间进行上/下转换,因此这可能是一种解决方案,但我希望我需要借助外部依赖来保持映射。

假设键和值都是可序列化的

var a = new Map([[ 'a', 1 ]]);
a.get('a') // 1

var forStorageSomewhere = JSON.stringify(a);
// Store, in my case, in localStorage.

// Later:
var a = JSON.parse(forStorageSomewhere);
a.get('a') // TypeError: undefined is not a function
应该有用。相反,使用

localStorage.myMap = JSON.stringify(Array.from(map.entries()));

通常,只有当此属性保持不变时,序列化才有用

map = new Map(JSON.parse(localStorage.myMap));
其中
a≈ b
可以定义为
序列化(a)==序列化(b)

将对象序列化为JSON时可以满足这一要求:

deserialize(serialize(data)).get(key) ≈ data.get(key)
这是因为属性只能是字符串,可以无损地序列化为字符串

但是,ES6映射允许将任意值作为键。这是有问题的,因为对象是通过其引用而不是数据唯一标识的。序列化对象时,会丢失引用

var obj1 = {foo: [1,2]},
    obj2 = JSON.parse(JSON.stringify(obj1));
obj1.foo; // [1,2]
obj2.foo; // [1,2] :)
JSON.stringify(obj1.foo) === JSON.stringify(obj2.foo); // true :)
所以我想说,一般来说,用一种有用的方式做这件事是不可能的

对于那些可以使用它的情况,您最有可能使用普通对象而不是贴图。这还具有以下优点:

  • 它将能够被字符串化为JSON,而不会丢失关键信息
  • 它将在较旧的浏览器上工作
  • 它可能更快
在我们的基础上,我们可以做得更好一些。我们仍然可以对键使用对象引用,只要存在原始根或映射的入口,并且每个对象键都可以从该根键传递找到

修改Oriol的示例以使用Douglas Crockford的,我们可以创建一个处理此情况的地图:

var key = {},
    map1 = new Map([ [1,2], [key,3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()])));
map1.get(1); // 2
map2.get(1); // 2 :)
map1.get(key); // 3
map2.get(key); // undefined :(
Decycle和retrocycle使得用JSON编码循环结构和DAG成为可能。如果我们希望在对象之间建立关系,而不在这些对象本身上创建附加属性,或者希望通过使用ES6映射将原语与对象互换关联,反之亦然,那么这非常有用


一个陷阱是我们不能将原始的key对象用于新映射(
map3.get(key);
将返回未定义)。但是,保留原始的键引用,但新解析的JSON映射似乎是不太可能出现的情况。

当您使用多维映射时,接受的答案将失败。应该始终记住,贴图对象可以将另一个贴图对象作为键或值

因此,处理这项工作的更好、更安全的方法如下:

var key = {},
    map1 = new Map([ [1, key], [key, 3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()]))),
    map3 = new Map(JSON.retrocycle(JSON.parse(JSON.stringify(JSON.decycle([...map1.entries()])))));
map1.get(1); // key
map2.get(1); // key
map3.get(1); // key
map1.get(map1.get(1)); // 3 :)
map2.get(map2.get(1)); // undefined :(
map3.get(map3.get(1)); // 3 :)
一旦你有了这个工具,你就可以一直这样做

function arrayifyMap(m){
  return m.constructor === Map ? [...m].map(([v,k]) => [arrayifyMap(v),arrayifyMap(k)])
                               : m;
}

一件被忽略的事情是,Map是一个有序的结构,也就是说,当迭代时,输入的第一个项目将是第一个列出的项目

这与Javascript对象不同。我需要这种类型的结构(所以我使用Map),然后发现JSON.stringify不起作用是痛苦的(但可以理解)

我最终制作了一个“value-to-json”函数,这意味着解析所有内容- 仅对最基本的“类型”使用JSON.stringify

不幸的是,使用.toJSON()子类化MAP不起作用,因为它只包含一个值而不是JSON字符串。它也被认为是遗产

不过,我的用例将是例外

相关的:

函数值到json(值){
如果(值===null){
返回'null';
}
如果(值===未定义){
返回'null';
}
//闲暇时处理+/-INF-改为null。。
常量类型=值的类型;
//处理尽可能多,但不会有副作用。功能可能
//返回一些MAP/SET->TODO,但不太可能
if(['string'、'boolean'、'number'、'function'].includes(type)){
返回JSON.stringify(值)
}else if(Object.prototype.toString.call(value)='[Object Object]'){
让零件=[];
for(让输入值){
if(Object.prototype.hasOwnProperty.call(value,key)){
push(JSON.stringify(key)+':'+value_to_JSON(value[key]);
}
}
返回'{'+parts.join(',')+'}';
}
else if(映射的值实例){
让零件按顺序排列=[];
value.forEach((输入,键)=>{
如果(键的类型=='string'){
push(JSON.stringify(key)+':'+value到JSON(entry));
}否则{
log('不直接支持映射中的非字符串键');
}
//对于其他密钥类型,请添加自定义…“密钥”编码。。。
});
按顺序返回'{'+parts'.'.join(',')+'}';
}else if(值的类型[符号.迭代器]!=“未定义”){
//其他易趣,如SET(也按顺序)
让零件=[];
for(让输入值){
push(值到json(条目))
}
返回'['+部件.连接(',')+']';
}否则{
返回JSON.stringify(值)
}
}
设m=newmap();
m、 设置('first'、'first_value');
m、 设置('second','second_value');
设m2=newmap();
m2.set('nested','nested_value');
m、 集合(“子地图”,m2);
让映射数组中的映射=新映射();
将_映射到_数组中。set('key','value');
设set1=新集合([“1”,2,3.0,4]);
m2.set('array_here',[map_in_array,'Hello',true,0.1,null,未定义,Number.POSITIVE_无穷大{
“a”:4
}]);
m2.集合('a集合:',集合1);
常数测试={
“你好”:“好的”,
“地图”:m
};
log(从值到json(测试))像哨子一样干净:

localStorage.myMap = JSON.stringify(arrayifyMap(myMap))
如果您为任何
对象实现自己的函数,那么只需常规的
JSON.stringify()
就可以了

Map
s与
Array
s为键<代码>映射
s与其他
映射
作为值?常规
对象内的
映射
?甚至你自己的自定义类;简单

JSON.stringify([...myMap])
就这样! 这里需要原型操作。您可以手动向所有非标准内容添加
toJSON()
Map.prototype.toJSON = function() {
    return Array.from(this.entries());
};
test = {
    regular : 'object',
    map     : new Map([
        [['array', 'key'], 7],
        ['stringKey'     , new Map([
            ['innerMap'    , 'supported'],
            ['anotherValue', 8]
        ])]
    ])
};
console.log(JSON.stringify(test));
{"regular":"object","map":[[["array","key"],7],["stringKey",[["innerMap","supported"],["anotherValue",8]]]]}
test2 = JSON.parse(JSON.stringify(test));
console.log((new Map((new Map(test2.map)).get('stringKey'))).get('innerMap'));
"supported"
Map.prototype.toJSON = function() {
    return ['window.Map', Array.from(this.entries())];
};
Map.fromJSON = function(key, value) {
    return (value instanceof Array && value[0] == 'window.Map') ?
        new Map(value[1]) :
        value
    ;
};
{"regular":"object","test":["window.Map",[[["array","key"],7],["stringKey",["window.Map",[["innerMap","supported"],["anotherValue",8]]]]]]}
test2 = JSON.parse(JSON.stringify(test), Map.fromJSON);
console.log(test2.map.get('stringKey').get('innerMap'));
"supported"
// store
const mapObj = new Map([['a', 1]]);
localStorage.a = JSON.stringify(mapObj, replacer);

// retrieve
const newMapObj = JSON.parse(localStorage.a, reviver);

// required replacer and reviver functions
function replacer(key, value) {
  const originalObject = this[key];
  if(originalObject instanceof Map) {
    return {
      dataType: 'Map',
      value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
    };
  } else {
    return value;
  }
}
function reviver(key, value) {
  if(typeof value === 'object' && value !== null) {
    if (value.dataType === 'Map') {
      return new Map(value.value);
    }
  }
  return value;
}