Подтвердить что ты не робот

Как сохранить карту ES6 в localstorage (или в другом месте)?

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, который позволяет выполнять перетаскивание вверх/вниз между Картой и простым объектом, так что это может быть одно решение, но Я надеялся, что мне придется прибегнуть к внешней зависимости, просто чтобы сохранить мою карту.

4b9b3361

Ответ 1

Предполагая, что ваши ключи и ваши значения будут сериализованы,

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

должен работать. Для обратного использования используйте

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

Ответ 2

Обычно сериализация применима только в том случае, если это свойство содержит

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 без потери ключевой информации.
  • Он будет работать в старых браузерах.
  • Это может быть быстрее.

Ответ 3

Создав Ориол ответ, мы можем сделать немного лучше. Мы все еще можем использовать ссылки на объекты для ключей, если на карте есть примитивный корень или вход, и каждый ключ объекта может быть транзитно найден из этого корневого ключа.

Изменив пример 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 кажется очень маловероятным случаем.

Ответ 4

Чисто как свисток:

JSON.stringify([...myMap])

Ответ 5

Принимаемый ответ не удастся, если у вас многомерные карты. Следует всегда иметь в виду, что объект Map может взять другой объект Map в качестве ключа или значения.

Таким образом, лучший и безопасный способ решения этой задачи может быть следующим:

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

Как только у вас есть этот инструмент, вы всегда можете сделать:

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

Ответ 6

Если вы реализуете свою собственную toJSON() для любых toJSON() вас объектов class, тогда просто обычный старый JSON.stringify() будет просто работать!

Map с Array для ключей? Map с другими Map качестве значений? Map внутри обычного Object? Может быть, даже ваш собственный класс; легко.

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

Это! Здесь требуется манипулирование прототипом.Вы можете добавить toJSON() вручную ко всем вашим нестандартным 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"

Это немного грязно, но с небольшим количеством волшебного соуса вы также можете сделать десериализацию автоматической.

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

Одна вещь, которая остается за ее пределами, заключается в том, что 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));

Ответ 8

// 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. Это также может быть карта, глубоко вложенная в массив или объект.