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

时间:2015-03-07 18:08:26

标签: javascript ecmascript-6

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);只返回'{}',这意味着在恢复时它会成为一个空对象。

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

9 个答案:

答案 0 :(得分:69)

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

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

应该有效。反之,请使用

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

答案 1 :(得分:10)

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

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。
  • 它适用于旧浏览器。
  • 可能会更快。

答案 2 :(得分:5)

建立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);将返回undefined)。但是,持有原始密钥引用,但新解析的JSON映射似乎是一个非常不可能的案例。

答案 3 :(得分:5)

吹口哨:

JSON.stringify([...myMap])

答案 4 :(得分:2)

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

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

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

一旦你拥有了这个工具,你就可以随时做到;

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

答案 5 :(得分:0)

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

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

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

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

我的用例会很特殊。

相关:

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

答案 6 :(得分:0)

如果您为任何class对象实现自己的toJSON()函数,那么正常的旧JSON.stringify()就可以了!

Map个,其中Array个密钥?有其他Map作为值的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]]]]}

反序列化到真实Map的过程并不是自动的。使用上面的结果字符串,我将重新制作地图以提取值:

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

"supported"

DEMO

答案 7 :(得分:0)

// 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://stackoverflow.com/a/56150320/696535

上写了关于替换和恢复功能的说明。

此代码将对任何其他值(如常规JSON.stringify)起作用,因此无需假设序列化的对象必须是Map。它也可以是深深嵌套在数组或对象中的Map。

答案 8 :(得分:0)

js localStorage & Map 合二为一


(() => {
  // 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