es6 - localstorage javascript ejemplos
¿Cómo persisto un mapa ES6 en el almacenamiento local(o en otro lugar)? (8)
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
Desafortunadamente
JSON.stringify(a);
simplemente devuelve ''{}'', lo que significa que se convierte en un objeto vacío cuando se restaura.
Encontré es6-mapify que permite la es6-mapify ascendente / descendente entre un mapa y un objeto plano, por lo que podría ser una solución, pero esperaba tener que recurrir a una dependencia externa simplemente para mantener mi mapa.
La respuesta aceptada fallará cuando tenga mapas multidimensionales. Siempre se debe tener en cuenta que un objeto Map puede tomar otro objeto Map como clave o valor.
Entonces, una forma mejor y más segura de manejar este trabajo podría ser la siguiente;
function arrayifyMap(m){
return m.constructor === Map ? [...m].map(([v,k]) => [arrayifyMap(v),arrayifyMap(k)])
: m;
}
Una vez que tenga esta herramienta, siempre puede hacer me gusta;
localStorage.myMap = JSON.stringify(arrayifyMap(myMap))
Limpio como un silbato:
JSON.stringify([...myMap])
Partiendo de answer de , podemos hacerlo un poco mejor. Todavía podemos usar referencias de objeto para claves siempre que haya una raíz o entrada primitiva en el mapa, y cada clave de objeto se pueda encontrar de forma transitiva a partir de esa clave raíz.
Modificando el ejemplo de Oriol para usar JSON.decycle y JSON.retrocycle de Douglas Crockford, podemos crear un mapa que maneje este caso:
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 y retrocycle hacen posible codificar estructuras cíclicas y dags en JSON. Esto es útil si queremos construir relaciones entre objetos sin crear propiedades adicionales en esos objetos, o si queremos relacionar indistintamente primitivas con objetos y viceversa, usando un Mapa ES6.
El único inconveniente es que no
podemos
usar el objeto clave original para el nuevo mapa (
map3.get(key);
volvería indefinido).
Sin embargo, mantener la referencia clave original, pero un mapa JSON recién analizado parece un caso muy poco probable.
Por lo general, la serialización solo es útil si esta propiedad es válida
deserialize(serialize(data)).get(key) ≈ data.get(key)
donde
a ≈ b
podría definirse como
serialize(a) === serialize(b)
.
Esto se satisface cuando se serializa un objeto a 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 :)
Y esto funciona porque las propiedades solo pueden ser cadenas, que se pueden serializar sin pérdidas en cadenas.
Sin embargo, los mapas ES6 permiten valores arbitrarios como claves. Esto es problemático porque los objetos se identifican de manera única por su referencia, no por sus datos. Y al serializar objetos, pierde las referencias.
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 :(
Por lo tanto, diría que, en general, no es posible hacerlo de una manera útil.
Y para los casos en que funcionaría, lo más probable es que pueda usar un objeto plano en lugar de un mapa . Esto también tendrá estas ventajas:
- Podrá ser encadenado a JSON sin perder información clave.
- Funcionará en navegadores antiguos.
- Puede ser más rápido
Si implementa su propia función
toJSON()
para cualquier objeto de
class
que tenga, ¡entonces
JSON.stringify()
normal funcionará!
Map
s con
Array
s para claves?
Map
s con otro
Map
como valores?
¿Un
Map
dentro de un
Object
normal?
Quizás incluso tu propia clase personalizada;
fácil.
Map.prototype.toJSON = function() {
return Array.from(this.entries());
};
¡Eso es!
La manipulación del prototipo se requiere aquí.
Podrías ir agregando a
toJSON()
manualmente a todas tus cosas no estándar, pero en realidad solo estás evitando el poder de JS
test = {
regular : ''object'',
map : new Map([
[[''array'', ''key''], 7],
[''stringKey'' , new Map([
[''innerMap'' , ''supported''],
[''anotherValue'', 8]
])]
])
};
console.log(JSON.stringify(test));
salidas:
{"regular":"object","map":[[["array","key"],7],["stringKey",[["innerMap","supported"],["anotherValue",8]]]]}
Sin embargo, la deserialización hasta los
Map
reales no es tan automática.
Usando la cadena resultante anterior, rehaceré los mapas para extraer un valor:
test2 = JSON.parse(JSON.stringify(test));
console.log((new Map((new Map(test2.map)).get(''stringKey''))).get(''innerMap''));
salidas
"supported"
Eso es un poco desordenado, pero con un poco de salsa mágica también puedes hacer que la deserialización sea automática .
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
;
};
Ahora el JSON es
{"regular":"object","test":["window.Map",[[["array","key"],7],["stringKey",["window.Map",[["innerMap","supported"],["anotherValue",8]]]]]]}
Y la deserialización y el uso son muy simples con nuestro
Map.fromJSON
test2 = JSON.parse(JSON.stringify(test), Map.fromJSON);
console.log(test2.map.get(''stringKey'').get(''innerMap''));
salidas (y no se utilizan
new Map()
)
"supported"
Suponiendo que tanto sus claves como sus valores sean serializables,
localStorage.myMap = JSON.stringify(Array.from(map.entries()));
Deberia trabajar. Para el reverso, use
map = new Map(JSON.parse(localStorage.myMap));
Una cosa que queda fuera es que Map es una estructura ORDENADA , es decir, cuando iterar el primer elemento ingresado sería el primero en la lista.
Esto NO es como un objeto Javascript. Requerí este tipo de estructura (así que usé Map) y luego descubrir que JSON.stringify no funciona es doloroso (pero comprensible).
Terminé haciendo una función ''value_to_json'', lo que significa analizar TODO - usando JSON.stringify solo para los ''tipos'' más básicos.
Desafortunadamente, la subclasificación de MAP con un .toJSON () no funciona, ya que exceptúa un valor que no es una cadena_JSON. También se considera legado.
Sin embargo, mi caso de uso sería excepcional.
relacionado:
- https://github.com/DavidBruant/Map-Set.prototype.toJSON/issues/16
- JSON omitió Infinity y NaN; ¿Estado JSON en ECMAScript?
- ¿Cómo encadenar objetos que contienen conjuntos y mapas de ES5?
- JSON stringify un conjunto
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; }
Escribí aquí la explicación sobre las funciones de reemplazo y revividor aquí https://.com/a/56150320/696535
Este código funcionará para cualquier otro valor como JSON.stringify regular, por lo que no se supone que el objeto serializado debe ser un Mapa. También puede ser un Mapa profundamente anidado en una matriz o un objeto.