Ответ 1
Предполагая, что ваши ключи и ваши значения будут сериализованы,
localStorage.myMap = JSON.stringify(Array.from(map.entries()));
должен работать. Для обратного использования используйте
map = new Map(JSON.parse(localStorage.myMap));
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);
просто возвращает '{}', что означает, что при восстановлении объект становится пустым.
Я нашел es6-mapify, который позволяет выполнять перетаскивание вверх/вниз между Картой и простым объектом, так что это может быть одно решение, но Я надеялся, что мне придется прибегнуть к внешней зависимости, просто чтобы сохранить мою карту.
Предполагая, что ваши ключи и ваши значения будут сериализованы,
localStorage.myMap = JSON.stringify(Array.from(map.entries()));
должен работать. Для обратного использования используйте
map = new Map(JSON.parse(localStorage.myMap));
Обычно сериализация применима только в том случае, если это свойство содержит
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 :(
Поэтому я бы сказал, что вообще невозможно сделать это полезным способом.
И для тех случаев, когда он будет работать, скорее всего, вы можете использовать простой объект вместо карты. Это также будет иметь следующие преимущества:
Создав Ориол ответ, мы можем сделать немного лучше. Мы все еще можем использовать ссылки на объекты для ключей, если на карте есть примитивный корень или вход, и каждый ключ объекта может быть транзитно найден из этого корневого ключа.
Изменив пример 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 позволяют кодировать циклические структуры и dags в JSON. Это полезно, если мы хотим строить отношения между объектами, не создавая дополнительных свойств для самих этих объектов, или хотим взаимозаменяемо связывать примитивы с объектами и наоборот, используя карту ES6.
Единственная ошибка заключается в том, что мы не можем использовать исходный ключевой объект для новой карты (map3.get(key);
вернет undefined). Однако, сохраняя исходную ключевую ссылку, но недавно проанализированная карта JSON кажется очень маловероятным случаем.
Чисто как свисток:
JSON.stringify([...myMap])
Принимаемый ответ не удастся, если у вас многомерные карты. Следует всегда иметь в виду, что объект Map может взять другой объект Map в качестве ключа или значения.
Таким образом, лучший и безопасный способ решения этой задачи может быть следующим:
function arrayifyMap(m){
return m.constructor === Map ? [...m].map(([v,k]) => [arrayifyMap(v),arrayifyMap(k)])
: m;
}
Как только у вас есть этот инструмент, вы всегда можете сделать:
localStorage.myMap = JSON.stringify(arrayifyMap(myMap))
Если вы реализуете свою собственную toJSON()
для любых toJSON()
вас объектов class
, тогда просто обычный старый JSON.stringify()
будет просто работать!
Map
с Array
для ключей? Map
с другими Map
качестве значений? Map
внутри обычного Object
? Может быть, даже ваш собственный класс; легко.
Map.prototype.toJSON = function() {
return Array.from(this.entries());
};
Это! Здесь требуется манипулирование прототипом.Вы можете добавить toJSON()
вручную ко всем вашим нестандартным 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]]]]}
Однако десериализация вплоть до реальной Map
не так автоматична. Используя приведенную выше результирующую строку, я переделываю карты, чтобы вытащить значение:
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"
Одна вещь, которая остается за ее пределами, заключается в том, что Map является структурой ORDERED, т.е. когда итерация первого введенного элемента будет первой.
Это НЕ как объект Javascript. Мне нужен этот тип структуры (поэтому я использовал Map), а затем, чтобы узнать, что JSON.stringify не работает, является болезненным (но понятным).
Я закончил создание функции value_to_json, что означает синтаксический анализ ВСЕ - используя JSON.stringify только для самых простых "типов".
К сожалению, подкласс MAP с .toJSON() не работает, поскольку он исключает значение не 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));
// 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;
}
Я написал здесь объяснение функций заменителя и ревериратора здесь fooobar.com/info/148274/...
Этот код будет работать для любого другого значения, такого как обычный JSON.stringify, поэтому нет предположения, что сериализованный объект должен быть Map. Это также может быть карта, глубоко вложенная в массив или объект.