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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28918232/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 09:45:24  来源:igfitidea点击:

How do I persist a ES6 Map in localstorage (or elsewhere)?

javascriptjsondictionaryecmascript-6

提问by Letharion

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

Unfortunatly JSON.stringify(a);simply returns '{}', which means a becomes an empty object when restored.

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

I found es6-mapifythat allows up/down-casting between a Map and a plain object, so that might be one solution, but I was hoping I would need to resort to an external dependency simply to persist my map.

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

回答by Bergi

Assuming that both your keys and your values are serialisable,

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

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

should work. For the reverse, use

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

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

回答by Oriol

Usually, serialization is only useful if this property holds

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

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

where a ≈ bcould be defined as serialize(a) === serialize(b).

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

This is satisfied when serializing an object to JSON:

这在将对象序列化为 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 :)

And this works because properties can only be strings, which can be losslessly serialized into strings.

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

However, ES6 maps allow arbitrary values as keys. This is problematic because, objects are uniquely identified by their reference, not their data. And when serializing objects, you lose the references.

然而,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 :(

So I would say in general it's not possibleto do it in an useful way.

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

And for those cases where it would work, most probably you can use a plain object instead of a map. This will also have these advantages:

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

  • It will be able to be stringified to JSON without losing key information.
  • It will work on older browsers.
  • It might be faster.
  • 它将能够被字符串化为 JSON 而不会丢失关键信息。
  • 它适用于较旧的浏览器。
  • 它可能会更快。

回答by Oded Breiner

Clean as a whistle:

像哨子一样清洁:

JSON.stringify([...myMap])

回答by Jonathan Gawrych

Building off of Oriol'sanswer, we can do a little better. We can still use object references for keys as long as the there is primitive root or entrance into the map, and each object key can be transitively found from that root key.

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

Modifying Oriol's example to use Douglas Crockford's JSON.decycle and JSON.retrocyclewe can create a map that handles this case:

修改 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 and retrocycle make it possible to encode cyclical structures and dags in JSON. This is useful if we want to build relations between objects without creating additional properties on those objects themselves, or want to interchangeably relate primitives to objects and visa-versa, by using an ES6 Map.

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

The one pitfall is that we cannotuse the original key object for the new map (map3.get(key);would return undefined). However, holding the original key reference, but a newly parsed JSON map seems like a very unlikely case to ever have.

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

回答by Hashbrown

If you implement your own toJSON()function for any classobjects you have then just regular old JSON.stringify()will just work!

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

Maps with Arrays for keys? Maps with other Mapas values? A Mapinside a regular Object? Maybe even your own custom class; easy.

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

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

That's it! prototype manipulation is required here. You could go around adding toJSON()manually to all your non-standard stuff, but really you're just avoiding the power of JS

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

DEMO

演示

test = {
    regular : 'object',
    map     : new Map([
        [['array', 'key'], 7],
        ['stringKey'     , new Map([
            ['innerMap'    , 'supported'],
            ['anotherValue', 8]
        ])]
    ])
};
console.log(JSON.stringify(test));

outputs:

输出:

{"regular":"object","map":[[["array","key"],7],["stringKey",[["innerMap","supported"],["anotherValue",8]]]]}


Deserialising all the way back to real Maps isn't as automatic, though. Using the above resultant string, I'll remake the maps to pull out a value:

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

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

outputs

输出

"supported"

That's a bit messy, but with a little magic sauceyou can make deserialisation automagic too.

这有点混乱,但是使用一点魔法酱,您也可以使反序列化自动化

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

Now the JSON is

现在 JSON 是

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

And deserialising and use is dead simple with our Map.fromJSON

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

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

outputs (and no new Map()s used)

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

"supported"

DEMO

演示

回答by Redu

The accepted answer will fail when you have multi dimentional Maps. One should always keep in mind that, a Map object can take another Map object as a key or value.

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

So a better and safer way of handling this job could be as follows;

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

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

Once you have this tool then you can always do like;

一旦你有了这个工具,你就可以随时做喜欢的事情;

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

回答by hobbit_be

One thing that is being left outis that Map is an ORDEREDstructure - i.e. when iterating the first item entered would be the first listed.

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

This is NOTlike a Javascript Object. I required this type of structure (so i used Map) and then to find out that JSON.stringify doesn't work is painful (but understandable).

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

I ended up making a 'value_to_json' function, which means parsing EVERYTHING - using JSON.stringify only for the most basic 'types'.

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

Unfortunately subclassing MAP with a .toJSON() doesn't work as it excepts a value not a JSON_string. Also it is considered legacy.

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

My use case would be exceptional though.

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

related:

有关的:

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

回答by Pawel

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

I wrote here the explanation about replacer and reviver functions here https://stackoverflow.com/a/56150320/696535

我在这里写了关于replacer和reviver功能的解释https://stackoverflow.com/a/56150320/696535

This code will work for any other value like regular JSON.stringify so there's no assumption that the serialised object must be a Map. It can also be a Map deeply nested in an array or an object.

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