如何在本地存储(或其他地方)中保留 ES6 地图?

IT技术 javascript json dictionary ecmascript-6
2021-01-18 00:15:54
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

不幸的是JSON.stringify(a);,它只是简单地返回了 '{}',这意味着 a 在恢复时变成了一个空对象。

我发现es6-mapify允许在 Map 和普通对象之间进行向上/向下转换,因此这可能是一种解决方案,但我希望我需要诉诸外部依赖来仅保留我的地图。

6个回答

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

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

应该管用。对于相反的情况,请使用

map = new Map(JSON.parse(localStorage.myMap));
刚刚在 Chrome 中尝试过这个,.entries()没有必要;这似乎做了同样的事情:localStorage.myMap = JSON.stringify(Array.from(map));
2021-03-29 00:15:54
因为我确实用一个数组初始化了 Map,你会认为我自己会考虑这个。:) 谢谢。
2021-03-31 00:15:54
@bmaupin 是的[Symbol.iterator]Array.from使用的默认值.entriesMap 上的相同,但我想将其拼写出来,以便很明显我们在这里使用迭代器。
2021-04-14 00:15:54

像哨子一样清洁:

JSON.stringify([...myMap])
这简直太棒了。我希望我能不止一次投票。对于那些阅读,您可以通过简单地反序列化它:let deserialized = new Map(JSON.parse(serialisedMap));
2021-03-16 00:15:54
如果在typescript中执行此操作,则需要将其添加"downlevelIteration": true,compilerOptionstsconfig.json 中,否则会引发错误。
2021-04-12 00:15:54

通常,序列化仅在此属性成立时才有用

deserialize(serialize(data)).get(key) ≈ data.get(key)

wherea ≈ b可以定义为serialize(a) === serialize(b).

这在将对象序列化为 JSON 时满足:

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 :)

这是有效的,因为属性只能是字符串,可以无损地序列化为字符串。

然而,ES6 映射允许任意值作为键。这是有问题的,因为对象是由它们的引用而不是它们的数据唯一标识的。并且在序列化对象时,您会丢失引用。

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 :(

所以我会说一般来说不可能以有用的方式做到这一点。

对于那些可以工作的情况,很可能您可以使用普通对象而不是 map这还将具有以下优点:

  • 它将能够被字符串化为 JSON 而不会丢失关键信息。
  • 它适用于较旧的浏览器。
  • 它可能会更快。
很有意思。+1 我目前不再从事这个项目,但我会在未来牢记这一点。虽然已经有一段时间了,所以我不完全记得我为什么选择一张地图,但我相信它与它完全适合我的用例有关,我认为这也会使它成为一个快速的选择。您能否详细说明“可能更快”这一点?:)
2021-03-21 00:15:54
@Letharion 我记得读到某个版本的 Firefox 会在地图中的所有键都是字符串时检测到,然后可以对其进行更多优化(如对象)。然后我想如果没有这种优化,地图在浏览器上会变慢。但这取决于实现,我还没有做过任何测试来衡量性能。
2021-03-27 00:15:54

基于Oriol 的 回答,我们可以做得更好。只要存在原始根或进入映射的入口,我们仍然可以对键使用对象引用,并且可以从该根键中传递地找到每个对象键。

修改 Oriol 的示例以使用 Douglas Crockford 的JSON.decycle 和 JSON.retrocycle我们可以创建处理这种情况的映射:

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 :)

Decycle 和retrocycle 使在JSON 中编码循环结构和dag 成为可能。如果我们想要在对象之间建立关系而不在这些对象本身上创建附加属性,或者想要通过使用 ES6 Map 将基元与对象互换地关联,反之亦然,这将非常有用。

一个陷阱是我们不能为新地图使用原始键对象(map3.get(key);将返回 undefined)。然而,持有原始密钥引用,但新解析的 JSON 映射似乎是一种非常不可能的情况。

我会建议,如果更多 Stack Overflow 答案具有这个答案的形式和质量——而且它们的实用性程度要低调完整——我们应该拥有相当出色的 JavaScript 资源。
2021-03-26 00:15:54

如果你为你拥有toJSON()的任何class对象实现你自己的函数,那么就可以正常JSON.stringify()工作!

Maps 与Arrays 键?Maps 与其他Map作为值?一个Map里面一个普通的Object甚至可能是您自己的自定义类;简单。

Map.prototype.toJSON = function() {
    return Array.from(this.entries());
};

而已! 这里需要原型操作。您可以toJSON()手动添加所有非标准内容,但实际上您只是在避免使用 JS 的功能

演示

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]]]]}

不过,反序列化一直到真正的Maps 并不是自动的。使用上述结果字符串,我将重新制作地图以提取一个值:

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
    ;
};

现在 JSON 是

{"regular":"object","test":["window.Map",[[["array","key"],7],["stringKey",["window.Map",[["innerMap","supported"],["anotherValue",8]]]]]]}

我们的反序列化和使用非常简单 Map.fromJSON

test2 = JSON.parse(JSON.stringify(test), Map.fromJSON);
console.log(test2.map.get('stringKey').get('innerMap'));

输出(并且没有new Map()使用)

"supported"

演示