Cómo almacenar objetos en HTML5 localStorage/sessionStorage

13 minutos de lectura

avatar de usuario
cristobal johnson

Me gustaría almacenar un objeto JavaScript en HTML5 localStoragepero aparentemente mi objeto se está convirtiendo en una cadena.

Puedo almacenar y recuperar tipos y matrices de JavaScript primitivos usando localStorage, pero los objetos no parecen funcionar. ¿Deberían ellos?

Aquí está mi código:

var testObject = { 'one': 1, 'two': 2, 'three': 3 };
console.log('typeof testObject: ' + typeof testObject);
console.log('testObject properties:');
for (var prop in testObject) {
    console.log('  ' + prop + ': ' + testObject[prop]);
}

// Put the object into storage
localStorage.setItem('testObject', testObject);

// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');

console.log('typeof retrievedObject: ' + typeof retrievedObject);
console.log('Value of retrievedObject: ' + retrievedObject);

La salida de la consola es

typeof testObject: object
testObject properties:
  one: 1
  two: 2
  three: 3
typeof retrievedObject: string
Value of retrievedObject: [object Object]

me parece el setItem El método está convirtiendo la entrada en una cadena antes de almacenarla.

Veo este comportamiento en Safari, Chrome y Firefox, así que asumo que es mi malentendido del Almacenamiento web HTML5 especificación, no un error o limitación específica del navegador.

He tratado de darle sentido a la clon estructurado algoritmo descrito en 2 Infraestructura común. No entiendo completamente lo que dice, pero tal vez mi problema tenga que ver con que las propiedades de mi objeto no sean enumerables (???).

¿Hay una solución fácil?


Actualización: el W3C finalmente cambió de opinión sobre la especificación de clones estructurados y decidió cambiar la especificación para que coincida con las implementaciones. Ver 12111: la especificación para el método getItem (clave) del objeto de almacenamiento no coincide con el comportamiento de implementación. Entonces esta pregunta ya no es 100% válida, pero las respuestas aún pueden ser de interés.

  • Por cierto, su lectura del “algoritmo de clonación estructurada” es correcta, es solo que la especificación se cambió de valores de solo cadena a esto después de que se eliminaron las implementaciones. archivé un error bugzilla.mozilla.org/show_bug.cgi?id=538142 con mozilla para rastrear este problema.

    – Nickolay

    6 de enero de 2010 a las 13:30

  • Esto parece un trabajo para indexedDB …

    – marka software

    2 de agosto de 2013 a las 2:06

  • ¿Qué hay de almacenar una matriz de objetos en localStorage? Estoy enfrentando el mismo problema que se está convirtiendo en una cadena.

    – Jayant Parek

    9 de junio de 2017 a las 19:08

  • ¿Podría simplemente serializar la matriz? como almacenar con JSON stringify y luego analizar nuevamente al cargar?

    – brandito

    12 de marzo de 2018 a las 6:35


  • Puede usar localDataStorage para almacenar de forma transparente los tipos de datos de JavaScript (matriz, booleano, fecha, flotante, entero, cadena y objeto)

    – Mac

    11 de junio de 2018 a las 0:54

avatar de usuario
Christian C. Salvado

Mirando a la Manzana, Mozilla y mozilla otra vez documentación, la funcionalidad parece estar limitada para manejar solo pares de clave/valor de cadena.

Una solución alternativa puede ser encadenar su objeto antes de almacenarlo, y luego analizarlo cuando lo recupere:

var testObject = { 'one': 1, 'two': 2, 'three': 3 };

// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));

// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');

console.log('retrievedObject: ', JSON.parse(retrievedObject));

  • tenga en cuenta que se eliminarán todos los metadatos. simplemente obtiene un objeto con los pares clave-valor, por lo que cualquier objeto con comportamiento debe reconstruirse.

    – oligofreno

    7 oct 2013 a las 18:48

  • @CMS puede setItem lanzar alguna excepción si los datos están por encima de la capacidad?

    –Ashish Negi

    26 de marzo de 2014 a las 9:27

  • … se aplica solo a objetos con referencias circulares, JSON.stringify() expande el objeto al que se hace referencia a su “contenido” completo (implícitamente encadenado) en el objeto que encadenamos. Ver: stackoverflow.com/a/12659424/2044940

    – CodeManX

    23 de julio de 2014 a las 16:54

  • El problema con este enfoque son los problemas de rendimiento, si tiene que manejar matrices u objetos grandes.

    – Marca

    29 de octubre de 2014 a las 9:35

  • @oligofren cierto, pero como maja correctamente sugerido eval() => , este es uno de los buenos usos de , puede recuperar fácilmente el código de función => almacenarlo como cadena y luego eval() de nuevo 🙂

    – jave.web

    06/07/2015 a las 20:03


avatar de usuario
Guría

Una pequeña mejora en una variante:

Storage.prototype.setObject = function(key, value) {
    this.setItem(key, JSON.stringify(value));
}

Storage.prototype.getObject = function(key) {
    var value = this.getItem(key);
    return value && JSON.parse(value);
}

Porque evaluación de cortocircuito, getObject() voluntad inmediatamente devolver null si key no está en almacenamiento. Tampoco arrojará un SyntaxError excepción si value es "" (la cadena vacía; JSON.parse() no puede manejar eso).

  • Solo quiero agregar rápidamente el uso, ya que no me quedó claro de inmediato: var userObject = { userId: 24, name: 'Jack Bauer' }; y para configurarlo localStorage.setObject('user', userObject); Luego recuperarlo del almacenamiento userObject = localStorage.getObject('user'); Incluso puede almacenar una serie de objetos si lo desea.

    – zuallauz

    16 de agosto de 2011 a las 21:38

  • Es solo una expresión booleana. La segunda parte se evalúa solo si la izquierda es verdadera. En ese caso, el resultado de la expresión completa será de la parte derecha. Es una técnica popular basada en la forma en que se evalúan las expresiones booleanas.

    – Guría

    1 de noviembre de 2011 a las 21:04

  • No veo el punto de la variable local y la evaluación del acceso directo aquí (aparte de las mejoras menores de rendimiento). Si key no está en el almacenamiento local, window.localStorage.getItem(key) devoluciones null – lo hace no lanzar una excepción de “Acceso ilegal” – y JSON.parse(null) devoluciones null también – lo hace no lanzar una excepción, ni en Chromium 21 ni por ES 5.1 sección 15.12.2porque String(null) === "null" que puede interpretarse como un Literal JSON.

    – Orejas puntiagudas

    8 oct 2012 a las 10:20


  • Los valores en Almacenamiento local son siempre valores de cadena primitivos. Entonces, lo que maneja esta evaluación de atajos es cuando alguien almacenó "" (la cadena vacía) antes. Porque se convierte en tipo false y JSON.parse("")lo que arrojaría un SyntaxError excepción, no se llama.

    – Orejas puntiagudas

    8 oct 2012 a las 10:42


  • Esto no funcionará en IE8, por lo que es mejor que use las funciones en la respuesta confirmada si necesita soporte.

    – Ezequiel

    16 de enero de 2013 a las 12:15


Puede que le resulte útil ampliar el objeto de almacenamiento con estos prácticos métodos:

Storage.prototype.setObject = function(key, value) {
    this.setItem(key, JSON.stringify(value));
}

Storage.prototype.getObject = function(key) {
    return JSON.parse(this.getItem(key));
}

De esta manera, obtiene la funcionalidad que realmente deseaba, aunque debajo de la API solo se admiten cadenas.

  • Envolver el enfoque de CMS en una función es una buena idea, solo necesita pruebas de características: una para JSON.stringify, otra para JSON.parse y otra para probar si localStorage puede establecer y recuperar un objeto. Modificar objetos host no es una buena idea; Preferiría ver esto como un método separado y no como localStorage.setObject.

    – Garrett

    9 de diciembre de 2010 a las 1:28


  • Este getObject() lanzará un SyntaxError excepción si el valor almacenado es ""porque JSON.parse() no puedo manejar eso. Vea mi edición de la respuesta de Guria para más detalles.

    – Orejas puntiagudas

    8 oct 2012 a las 10:45


  • Solo mis dos centavos, pero estoy bastante seguro de que no es una buena idea extender los objetos proporcionados por el proveedor de esta manera.

    – Setén

    6 de julio de 2014 a las 18:54


  • Estoy completamente de acuerdo con @Sethen. Por favor, no aplique parches globales implementados por el navegador de esta manera. Puede descifrar el código y no es compatible en el futuro con los navegadores que pueden enviar un setObject método en este global en el futuro.

    – Flamm

    16 dic 2021 a las 18:45

avatar de usuario
alex grande

Crear una fachada para el objeto Storage es una solución increíble. De esa manera, puede implementar su propia get y set métodos. Para mi API, he creado una fachada para localStorage y luego compruebo si es un objeto o no mientras lo configuro y lo obtengo.

var data = {
  set: function(key, value) {
    if (!key || !value) {return;}

    if (typeof value === "object") {
      value = JSON.stringify(value);
    }
    localStorage.setItem(key, value);
  },
  get: function(key) {
    var value = localStorage.getItem(key);

    if (!value) {return;}

    // assume it is an object that has been stringified
    if (value[0] === "{") {
      value = JSON.parse(value);
    }

    return value;
  }
}

avatar de usuario
maja

Stringify no resuelve todos los problemas

Parece que las respuestas aquí no cubren todos los tipos que son posibles en JavaScript, por lo que aquí hay algunos ejemplos breves sobre cómo manejarlos correctamente:

// Objects and Arrays:
    var obj = {key: "value"};
    localStorage.object = JSON.stringify(obj);  // Will ignore private members
    obj = JSON.parse(localStorage.object);

// Boolean:
    var bool = false;
    localStorage.bool = bool;
    bool = (localStorage.bool === "true");

// Numbers:
    var num = 42;
    localStorage.num = num;
    num = +localStorage.num;    // Short for "num = parseFloat(localStorage.num);"

// Dates:
    var date = Date.now();
    localStorage.date = date;
    date = new Date(parseInt(localStorage.date));

// Regular expressions:
    var regex = /^No\.[\d]*$/i;     // Usage example: "No.42".match(regex);
    localStorage.regex = regex;
    var components = localStorage.regex.match("^/(.*)/([a-z]*)$");
    regex = new RegExp(components[1], components[2]);

// Functions (not recommended):
    function func() {}

    localStorage.func = func;
    eval(localStorage.func);      // Recreates the function with the name "func"

no lo recomiendo almacenar funciones, porque eval() es maligno y puede generar problemas relacionados con la seguridad, la optimización y la depuración.

En general, eval() nunca debe usarse en código JavaScript.

miembros privados

El problema con el uso JSON.stringify() para almacenar objetos es que esta función no puede serializar miembros privados.

Este problema se puede resolver sobrescribiendo el .toString() método (que se llama implícitamente cuando se almacenan datos en el almacenamiento web):

// Object with private and public members:
    function MyClass(privateContent, publicContent) {
        var privateMember = privateContent || "defaultPrivateValue";
        this.publicMember = publicContent  || "defaultPublicValue";

        this.toString = function() {
            return '{"private": "' + privateMember + '", "public": "' + this.publicMember + '"}';
        };
    }
    MyClass.fromString = function(serialisedString) {
        var properties = JSON.parse(serialisedString || "{}");
        return new MyClass(properties.private, properties.public);
    };

// Storing:
    var obj = new MyClass("invisible", "visible");
    localStorage.object = obj;

// Loading:
    obj = MyClass.fromString(localStorage.object);

Referencias circulares

Otro problema stringify no puede tratar son referencias circulares:

var obj = {};
obj["circular"] = obj;
localStorage.object = JSON.stringify(obj);  // Fails

En este ejemplo, JSON.stringify() lanzará un TypeError “Conversión de estructura circular a JSON”.

Si se debe admitir el almacenamiento de referencias circulares, el segundo parámetro de JSON.stringify() podría usarse:

var obj = {id: 1, sub: {}};
obj.sub["circular"] = obj;
localStorage.object = JSON.stringify(obj, function(key, value) {
    if(key == 'circular') {
        return "$ref" + value.id + "$";
    } else {
        return value;
    }
});

Sin embargo, encontrar una solución eficiente para almacenar referencias circulares depende en gran medida de las tareas que deben resolverse, y restaurar dichos datos tampoco es trivial.

Ya hay algunas preguntas sobre Stack Overflow que tratan este problema: Stringify (convertir a JSON) un objeto JavaScript con referencia circular

  • Por lo tanto, y no hace falta decir que el almacenamiento de datos en Storage debe basarse únicamente en la premisa de copias de datos simples. No objetos vivos.

    – Roko C. Bulján

    24 de marzo de 2020 a las 13:04

  • Probablemente usaría una costumbre aJSON en lugar de toString() en estos días. Desafortunadamente, no hay un equivalente simétrico para el análisis.

    – oligofreno

    16 dic 2021 a las 23:32

  • toJSON no admitirá tipos que no tengan una representación json directa como fecha, expresión regular, función y muchos otros tipos más nuevos que se agregaron a JavaScript después de que escribí esta respuesta.

    – maja

    17 dic 2021 a las 16:10

  • ¿Por qué “+” delante de localStorage.num (num = +localStorage.num)?

    -Peter Mortensen

    28 abr a las 21:36


  • @PeterMortensen para convertir la cadena almacenada nuevamente en un número

    – maja

    29 de abril a las 6:04

avatar de usuario
mateo

Hay una gran biblioteca que envuelve muchas soluciones, por lo que incluso es compatible con navegadores más antiguos llamados jAlmacenamiento

Puede establecer un objeto

$.jStorage.set(key, value)

Y recuperarlo fácilmente

value = $.jStorage.get(key)
value = $.jStorage.get(key, "default value")

  • Por lo tanto, y no hace falta decir que el almacenamiento de datos en Storage debe basarse únicamente en la premisa de copias de datos simples. No objetos vivos.

    – Roko C. Bulján

    24 de marzo de 2020 a las 13:04

  • Probablemente usaría una costumbre aJSON en lugar de toString() en estos días. Desafortunadamente, no hay un equivalente simétrico para el análisis.

    – oligofreno

    16 dic 2021 a las 23:32

  • toJSON no admitirá tipos que no tengan una representación json directa como fecha, expresión regular, función y muchos otros tipos más nuevos que se agregaron a JavaScript después de que escribí esta respuesta.

    – maja

    17 dic 2021 a las 16:10

  • ¿Por qué “+” delante de localStorage.num (num = +localStorage.num)?

    -Peter Mortensen

    28 abr a las 21:36


  • @PeterMortensen para convertir la cadena almacenada nuevamente en un número

    – maja

    29 de abril a las 6:04

Llegué a esta publicación después de encontrar otra publicación que se cerró como un duplicado de esta, titulada ‘¿cómo almacenar una matriz en el almacenamiento local?’. Lo cual está bien, excepto que ninguno de los subprocesos proporciona una respuesta completa sobre cómo puede mantener una matriz en localStorage; sin embargo, he logrado crear una solución basada en la información contenida en ambos subprocesos.

Entonces, si alguien más quiere poder empujar/abrir/cambiar elementos dentro de una matriz, y quiere que esa matriz se almacene en localStorage o, de hecho, en sessionStorage, aquí tiene:

Storage.prototype.getArray = function(arrayName) {
  var thisArray = [];
  var fetchArrayObject = this.getItem(arrayName);
  if (typeof fetchArrayObject !== 'undefined') {
    if (fetchArrayObject !== null) { thisArray = JSON.parse(fetchArrayObject); }
  }
  return thisArray;
}

Storage.prototype.pushArrayItem = function(arrayName,arrayItem) {
  var existingArray = this.getArray(arrayName);
  existingArray.push(arrayItem);
  this.setItem(arrayName,JSON.stringify(existingArray));
}

Storage.prototype.popArrayItem = function(arrayName) {
  var arrayItem = {};
  var existingArray = this.getArray(arrayName);
  if (existingArray.length > 0) {
    arrayItem = existingArray.pop();
    this.setItem(arrayName,JSON.stringify(existingArray));
  }
  return arrayItem;
}

Storage.prototype.shiftArrayItem = function(arrayName) {
  var arrayItem = {};
  var existingArray = this.getArray(arrayName);
  if (existingArray.length > 0) {
    arrayItem = existingArray.shift();
    this.setItem(arrayName,JSON.stringify(existingArray));
  }
  return arrayItem;
}

Storage.prototype.unshiftArrayItem = function(arrayName,arrayItem) {
  var existingArray = this.getArray(arrayName);
  existingArray.unshift(arrayItem);
  this.setItem(arrayName,JSON.stringify(existingArray));
}

Storage.prototype.deleteArray = function(arrayName) {
  this.removeItem(arrayName);
}

uso de ejemplo: almacenar cadenas simples en la matriz localStorage:

localStorage.pushArrayItem('myArray','item one');
localStorage.pushArrayItem('myArray','item two');

ejemplo de uso: almacenamiento de objetos en la matriz sessionStorage:

var item1 = {}; item1.name="fred"; item1.age = 48;
sessionStorage.pushArrayItem('myArray',item1);

var item2 = {}; item2.name="dave"; item2.age = 22;
sessionStorage.pushArrayItem('myArray',item2);

métodos comunes para manipular arreglos:

.pushArrayItem(arrayName,arrayItem); -> adds an element onto end of named array
.unshiftArrayItem(arrayName,arrayItem); -> adds an element onto front of named array
.popArrayItem(arrayName); -> removes & returns last array element
.shiftArrayItem(arrayName); -> removes & returns first array element
.getArray(arrayName); -> returns entire array
.deleteArray(arrayName); -> removes entire array from storage

  • Este es un conjunto muy útil de métodos para manipular arreglos almacenados en localStorage o sessionStorage, y merece mucho más crédito del que atrae. @Andy Lorenz ¡Gracias por tomarse el tiempo para compartir!

    – Velojet

    21 de agosto de 2017 a las 8:19

  • Por lo general, no es una buena idea parchear un parche global enviado por el navegador de esta manera. Puede hacer que otro código se rompa y no es compatible con navegadores futuros que deseen enviar sus propios métodos con nombres idénticos en el archivo global.

    – Flamm

    16 dic 2021 a las 18:49

  • @Flimm Estoy de acuerdo en que GENERALMENTE no es una buena idea hacer esto, pero esa opinión se basa mucho más en la teoría que en la práctica. Por ejemplo, desde mi publicación en 2014, no ha cambiado nada en las implementaciones de localStorage o sessionStorage que se hayan visto comprometidas. Y dudo que alguna vez lo hagan, para ser honesto. Pero si esa posibilidad fuera una preocupación para alguien, y es una decisión personal considerar el riesgo, no un ‘deberás/no’, mi respuesta podría usarse fácilmente como un modelo para implementar una clase de matriz personalizada que se ajuste al almacenamiento local real. /sesiónAlmacenamiento.

    –Andy Lorenz

    20 de diciembre de 2021 a las 11:36

¿Ha sido útil esta solución?