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

Posted

技术标签:

【中文标题】如何在本地存储(或其他地方)中保存 ES6 地图?【英文标题】:How do I persist a ES6 Map in localstorage (or elsewhere)? 【发布时间】:2015-05-09 04:56:31 【问题描述】:
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 和普通对象之间进行向上/向下转换,因此这可能是一种解决方案,但我希望我需要借助外部依赖项来简单地保留我的地图。

【问题讨论】:

这里是如何做到这一点,而不是假设知道你在序列化什么***.com/a/57730562/696535 【参考方案1】:

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

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

应该可以。相反,使用

map = new Map(JSON.parse(localStorage.myMap));

【讨论】:

因为我确实用数组初始化了 Map,你会认为我自己会考虑这个。 :) 谢谢。 刚刚在 Chrome 中尝试过,.entries() 不是必需的;这似乎做同样的事情:localStorage.myMap = JSON.stringify(Array.from(map)); @bmaupin 是的,Array.from 使用的默认 [Symbol.iterator] 与 Map 上的 .entries 相同,但我想把它拼出来,这样很明显我们在这里使用了迭代器.【参考方案2】:

像口哨一样干净:

JSON.stringify([...myMap])

【讨论】:

这简直太棒了。我希望我可以多次投票。对于那些阅读,您可以通过简单地反序列化它:let deserialized = new Map(JSON.parse(serialisedMap)); 如果在 typescript 中执行此操作,则需要在 tsconfig.json 中将 "downlevelIteration": true, 添加到 compilerOptions,否则会引发错误。【参考方案3】:

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

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

其中a ≈ 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 :(

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

对于那些可以使用的情况,您很可能可以使用普通对象而不是地图。这也将具有以下优点:

它将能够被字符串化为 JSON 而不会丢失关键信息。 它适用于旧版浏览器。 可能会更快。

【讨论】:

非常有趣。 +1我目前不再从事这个项目,但我会记住这一点以备不时之需。虽然已经有一段时间了,所以我不完全记得我为什么选择一张地图,但我相信这与它完全适合我的用例有关,我想这也会使它成为一个快速的选择。您能否详细说明“可能更快”位? :) @Letharion 我记得读过某个版本的 Firefox 会在映射中的所有键都是字符串时检测到,然后可以对其进行更多优化(如对象)。然后我猜如果没有这种优化,地图在浏览器上会变慢。但这取决于实现,我还没有进行任何测试来衡量性能。【参考方案4】:

在Oriol's answer 的基础上,我们可以做得更好。我们仍然可以使用对象引用作为键,只要有原始根或进入映射,并且每个对象键都可以从该根键中传递找到。

修改 Oriol 的示例以使用 Douglas Crockford 的 JSON.decycle and 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); 将返回未定义)。然而,持有原始密钥引用,但新解析的 JSON 映射似乎不太可能发生。

【讨论】:

我建议,如果更多 Stack Overflow 的答案具有这种形式和质量——而且在实用程度上也保持低调完整——我们应该拥有相当出色的 javascript 资源。跨度> 【参考方案5】:

如果您为任何class 对象实现自己的toJSON() 函数,那么只需普通的旧JSON.stringify() 即可工作!

Maps 和 Arrays 的键? Maps 与其他 Map 作为值? Map 在常规 Object 内?甚至可能是您自己的自定义类;容易。

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

就是这样! 这里需要原型操作。您可以手动将 toJSON() 添加到所有非标准内容中,但实际上您只是在避免 JS 的强大功能

DEMO

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"

这有点乱,但是用一点magic sauce 你也可以使反序列化自动化

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()s)

"supported"

DEMO

【讨论】:

【参考方案6】:

当您拥有多维地图时,接受的答案将失败。应始终牢记,一个 Map 对象可以将另一个 Map 对象作为键或值。

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

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

一旦你有了这个工具,你就可以一直喜欢;

localStorage.myMap = JSON.stringify(arrayifyMap(myMap))

【讨论】:

【参考方案7】:
// 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;

我在https://***.com/a/56150320/696535这里写了关于replacer和reviver函数的解释

此代码适用于任何其他值,例如常规 JSON.stringify,因此不假设序列化对象必须是 Map。也可以是深度嵌套在数组或对象中的 Map。

【讨论】:

【参考方案8】:

遗漏的一件事是 Map 是一个 ORDERED 结构 - 即在迭代时输入的第一个项目将是第一个列出的。

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

我最终创建了一个“value_to_json”函数,这意味着解析所有内容 - 仅将 JSON.stringify 用于最基本的“类型”。

不幸的是,使用 .toJSON() 子类化 MAP 不起作用,因为它除了一个不是 JSON_string 的值。它也被认为是遗产。

不过,我的用例会很特别。

相关:

https://github.com/DavidBruant/Map-Set.prototype.toJSON/issues/16 JSON left out Infinity and NaN; JSON status in ECMAScript? How to stringify objects containing ES5 Sets and Maps? JSON stringify a Set

function value_to_json(value) 
  if (value === null) 
    return 'null';
  
  if (value === undefined) 
    return 'null';
  
  //DEAL WITH +/- INF at your leisure - null instead..

  const type = typeof value;
  //handle as much as possible taht have no side effects. function could
  //return some MAP / SET -> TODO, but not likely
  if (['string', 'boolean', 'number', 'function'].includes(type)) 
    return JSON.stringify(value)
   else if (Object.prototype.toString.call(value) === '[object Object]') 
    let parts = [];
    for (let key in value) 
      if (Object.prototype.hasOwnProperty.call(value, key)) 
        parts.push(JSON.stringify(key) + ': ' + value_to_json(value[key]));
      
    
    return '' + parts.join(',') + '';
  
  else if (value instanceof Map) 
    let parts_in_order = [];
    value.forEach((entry, key) => 
      if (typeof key === 'string') 
        parts_in_order.push(JSON.stringify(key) + ':' + value_to_json(entry));
       else 
        console.log('Non String KEYS in MAP not directly supported');
      
      //FOR OTHER KEY TYPES ADD CUSTOM... 'Key' encoding...
    );
    return '' + parts_in_order.join(',') + '';
   else if (typeof value[Symbol.iterator] !== "undefined") 
    //Other iterables like SET (also in ORDER)
    let parts = [];
    for (let entry of value) 
      parts.push(value_to_json(entry))
    
    return '[' + parts.join(',') + ']';
   else 
    return JSON.stringify(value)
  



let m = new Map();
m.set('first', 'first_value');
m.set('second', 'second_value');
let m2 = new Map();
m2.set('nested', 'nested_value');
m.set('sub_map', m2);
let map_in_array = new Map();
map_in_array.set('key', 'value');
let set1 = new Set(["1", 2, 3.0, 4]);

m2.set('array_here', [map_in_array, "Hello", true, 0.1, null, undefined, Number.POSITIVE_INFINITY, 
  "a": 4
]);
m2.set('a set: ', set1);
const test = 
  "hello": "ok",
  "map": m
;

console.log(value_to_json(test));

【讨论】:

【参考方案9】:

js localStorage & Map All In One


(() => 
  // bug
  const map = new Map();
  map.set(1, id: 1, name: 'eric');
  // Map(1) 1 => …
  localStorage.setItem('app', map);
  // undefined
  localStorage.getItem('app');
  // "[object Map]" ❌
)();





(() => 
  // ok
  const map = new Map();
  map.set(1, id: 1, name: 'eric');
  // Map(1) 1 => …
  localStorage.setItem('app', JSON.stringify([...map]));
  // undefined
  const newMap = new Map(JSON.parse(localStorage.getItem('app')));
  // Map(1) 1 => … ✅
)();

截图

参考

https://www.cnblogs.com/xgqfrms/p/14431425.html

【讨论】:

【参考方案10】:

请务必记住,如果您尝试在大型地图集合上设置项目,它将抛出 Quota Exceeded Error。我尝试将包含 168590 个条目的地图持久保存到本地存储并收到此错误。 :(

【讨论】:

以上是关于如何在本地存储(或其他地方)中保存 ES6 地图?的主要内容,如果未能解决你的问题,请参考以下文章

如何保存用户位置并使用图钉(或其他标记)将其显示在地图上?

如何在 iOS 中本地保存 PDF 或其他文件

如何备份 Azure 自定义托管映像

是否可以从一个字符串本地保存数据,然后在不将其移动到其他地方的情况下进行破坏?

是否可以将音乐文件存储在 iPhone 本地存储中?

如何在我的 Xamarin Forms 应用程序上本地保存一些用户数据?