javascript - Almacenamiento de objetos en HTML5 localStorage


Me gustaría almacenar un objeto de JavaScript en localStorage HTML5, pero mi objeto aparentemente se está convirtiendo en una cadena.

Puedo almacenar y recuperar tipos y matrices de JavaScript primitivos usando localStorage , pero parece que los objetos no funcionan. ¿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 que el método setItem convierte la entrada en una cadena antes de almacenarla.

Veo este comportamiento en Safari, Chrome y Firefox, así que supongo que es mi incomprensión de las especificaciones de almacenamiento web HTML5 , no un error o limitación específica del navegador.

Intenté dar sentido al algoritmo de clonación estructurada que se describe en http://www.w3.org/TR/html5/infrastructure.html . No entiendo completamente lo que dice, pero tal vez mi problema tiene 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 clonación estructurada y decidió cambiar la especificación para que coincida con las implementaciones. Ver https://www.w3.org/Bugs/Public/show_bug.cgi?id=12111 . Entonces esta pregunta ya no es 100% válida, pero las respuestas aún pueden ser de interés.



Answers


En cuanto a la documentación de Apple , Mozilla y Microsoft , la funcionalidad parece estar limitada para manejar únicamente pares clave / valor de cadena.

Una solución alternativa puede ser codificar el 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));



Un poco mejor a la variante de Justin:

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

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

UPD. Variable agregada que Mark Storer mencionó en el comentario




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 obtienes la funcionalidad que realmente querías, aunque debajo de la API solo se admiten cadenas.




La ampliación del objeto de almacenamiento es una solución increíble. Para mi API, he creado una fachada para localStorage y luego verifico si es un objeto o no mientras configuro y 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;
  }
}



Hay una gran biblioteca que contiene muchas soluciones, por lo que incluso admite navegadores antiguos llamados jStorage

Puedes establecer un objeto

$.jStorage.set(key, value)

Y recuperarlo fácilmente

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



Stringify no resuelve todos los problemas

Parece que las respuestas aquí no cubren todos los tipos que son posibles en JavaScript, así que aquí hay algunos ejemplos cortos sobre cómo tratarlos 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 recomiendo almacenar funciones porque eval() es malo y puede provocar problemas de seguridad, optimización y depuración. En general, eval() nunca debe usarse en código JavaScript.

Miembros privados

El problema con el uso de JSON.stringify() para almacenar objetos es que esta función no puede serializar miembros privados. Este problema puede resolverse sobrescribiendo el método .toString() (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 con el que stringify no puede tratar son las referencias circulares:

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

En este ejemplo, JSON.stringify() arrojará un TypeError "Convertir la estructura circular a JSON" . Si debe JSON.stringify() almacenamiento de referencias circulares, se puede usar el segundo parámetro de JSON.stringify() :

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 la restauración de dichos datos tampoco es trivial.

Ya hay algunas preguntas sobre SO que se ocupan de este problema: Stringify (convertir a JSON) un objeto JavaScript con referencia circular




Usando objetos JSON para almacenamiento local:

//CONJUNTO

var m={name:'Hero',Title:'developer'};
localStorage.setItem('us', JSON.stringify(m));

//OBTENER

var gm =JSON.parse(localStorage.getItem('us'));
console.log(gm.name);

// Iteración de todas las claves y valores de almacenamiento local

for (var i = 0, len = localStorage.length; i < len; ++i) {
  console.log(localStorage.getItem(localStorage.key(i)));
}

// BORRAR

localStorage.removeItem('us');
delete window.localStorage["us"];



En teoría, es posible almacenar objetos con funciones:

function store (a)
{
  var c = {f: {}, d: {}};
  for (var k in a)
  {
    if (a.hasOwnProperty(k) && typeof a[k] === 'function')
    {
      c.f[k] = encodeURIComponent(a[k]);
    }
  }

  c.d = a;
  var data = JSON.stringify(c);
  window.localStorage.setItem('CODE', data);
}

function restore ()
{
  var data = window.localStorage.getItem('CODE');
  data = JSON.parse(data);
  var b = data.d;

  for (var k in data.f)
  {
    if (data.f.hasOwnProperty(k))
    {
      b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");
    }
  }

  return b;
}

Sin embargo, la serialización / deserialización de funciones no es confiable porque depende de la implementación .




También puede anular los setItem(key,value) predeterminados Storage setItem(key,value) y getItem(key) para manejar objetos / matrices como cualquier otro tipo de datos. De esta forma, puede simplemente llamar a localStorage.setItem(key,value) y localStorage.getItem(key) como lo haría normalmente.

No lo he probado exhaustivamente, pero parece que ha funcionado sin problemas en un proyecto pequeño con el que he estado retocando.

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

Storage.prototype._getItem = Storage.prototype.getItem;
Storage.prototype.getItem = function(key)
{  
  try
  {
    return JSON.parse(this._getItem(key));
  }
  catch(e)
  {
    return this._getItem(key);
  }
}



Llegué a esta publicación después de presionar en otra publicación que se ha cerrado como un duplicado de esta, titulada '¿cómo almacenar una matriz en localstorage?'. Lo cual está bien, excepto que ninguno de los hilos ofrece 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 hilos.

Entonces, si alguien más desea poder insertar / mover / desplazar elementos dentro de una matriz, y quiere esa matriz almacenada en localStorage o incluso 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: almacenamiento de cadenas simples en la matriz localStorage:

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

uso de ejemplo: 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 matrices:

.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






Modifiqué un poco una de las respuestas mejor votadas. Soy fanático de tener una sola función en lugar de 2 si no es necesaria.

Storage.prototype.object = function(key, val) {
    if ( typeof val === "undefined" ) {
        var value = this.getItem(key);
        return value ? JSON.parse(value) : null;
    } else {
        this.setItem(key, JSON.stringify(val));
    }
}

localStorage.object("test", {a : 1}); //set value
localStorage.object("test"); //get value

Además, si no se establece ningún valor, se devuelve null lugar de false . false tiene algún significado, null no.




Mejora en la respuesta de @Guria:

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


Storage.prototype.getObject = function (key) {
    var value = this.getItem(key);
    try {
        return JSON.parse(value);
    }
    catch(err) {
        console.log("JSON parse failed for lookup of ", key, "\n error was: ", err);
        return null;
    }
};



Otra opción sería usar un complemento existente.

Por ejemplo, persisto es un proyecto de código abierto que proporciona una interfaz sencilla para localStorage / sessionStorage y automatiza la persistencia de los campos de formulario (entrada, botones de opción y casillas de verificación).

(Descargo de responsabilidad: yo soy el autor)




Puede usar ejson para almacenar los objetos como cadenas.

EJSON es una extensión de JSON para admitir más tipos. Es compatible con todos los tipos JSON-safe, así como:

  • Fecha (Fecha de JavaScript)
  • Binario (JavaScript Uint8Array o el resultado de EJSON.newBinary )
  • Tipos definidos por el usuario (consulte EJSON.addType . Por ejemplo, Mongo.ObjectID se implementa de esta manera).

Todas las serializaciones EJSON son también JSON válidas. Por ejemplo, un objeto con una fecha y un búfer binario se serializaría en EJSON como:

{
  "d": {"$date": 1358205756553},
  "b": {"$binary": "c3VyZS4="}
}

Aquí está mi envoltorio de almacenamiento local usando ejson

https://github.com/UziTech/storage.js

Agregué algunos tipos a mi envoltorio incluyendo expresiones y funciones regulares




http://rhaboo.org es una capa de azúcar de almacenamiento local que te permite escribir cosas como esta:

var store = Rhaboo.persistent('Some name');
store.write('count', store.count ? store.count+1 : 1);
store.write('somethingfancy', {
  one: ['man', 'went'],
  2: 'mow',
  went: [  2, { mow: ['a', 'meadow' ] }, {}  ]
});
store.somethingfancy.went[1].mow.write(1, 'lawn');

No utiliza JSON.stringify / parse porque eso sería inexacto y lento en objetos grandes. En cambio, cada valor de terminal tiene su propia entrada localStorage.

Probablemente puedas adivinar que podría tener algo que ver con rhaboo ;-)

Adrian.




Hice otra envoltura minimalista con solo 20 líneas de código para permitir su uso como debería:

localStorage.set('myKey',{a:[1,2,5], b: 'ok'});
localStorage.has('myKey');   // --> true
localStorage.get('myKey');   // --> {a:[1,2,5], b: 'ok'}
localStorage.keys();         // --> ['myKey']
localStorage.remove('myKey');

https://github.com/zevero/simpleWebstorage




Puede usar localDataStorage para almacenar de forma transparente tipos de datos de JavaScript (Array, Boolean, Date, Float, Integer, String y Object). También proporciona una ofuscación de datos liviana, comprime automáticamente cadenas, facilita la consulta por clave (nombre) y consulta por valor (clave), y ayuda a imponer el almacenamiento compartido segmentado dentro del mismo dominio prefijando claves.

[DESCARGO DE RESPONSABILIDAD] Soy el autor de la utilidad [/ DISCLAIMER]

Ejemplos:

localDataStorage.set( 'key1', 'Belgian' )
localDataStorage.set( 'key2', 1200.0047 )
localDataStorage.set( 'key3', true )
localDataStorage.set( 'key4', { 'RSK' : [1,'3',5,'7',9] } )
localDataStorage.set( 'key5', null )

localDataStorage.get( 'key1' )   -->   'Belgian'
localDataStorage.get( 'key2' )   -->   1200.0047
localDataStorage.get( 'key3' )   -->   true
localDataStorage.get( 'key4' )   -->   Object {RSK: Array(5)}
localDataStorage.get( 'key5' )   -->   null

Como puede ver, los valores primitivos son respetados.




Aquí una versión extensiva del código publicado por @danott

También implementará delete value from localstorage y mostrará cómo agregar una capa Getter y Setter para que en lugar de

localstorage.setItem(preview, true)

puedes escribir

config.preview = true

De acuerdo aquí fueron ir:

var PT=Storage.prototype

if (typeof PT._setItem >='u') PT._setItem = PT.setItem;
PT.setItem = function(key, value)
{
  if (typeof value >='u')//..ndefined
    this.removeItem(key)
  else
    this._setItem(key, JSON.stringify(value));
}

if (typeof PT._getItem >='u') PT._getItem = PT.getItem;
PT.getItem = function(key)
{  
  var ItemData = this._getItem(key)
  try
  {
    return JSON.parse(ItemData);
  }
  catch(e)
  {
    return ItemData;
  }
}

// Aliases for localStorage.set/getItem 
get =   localStorage.getItem.bind(localStorage)
set =   localStorage.setItem.bind(localStorage)

// Create ConfigWrapperObject
var config = {}

// Helper to create getter & setter
function configCreate(PropToAdd){
    Object.defineProperty( config, PropToAdd, {
      get: function ()      { return (  get(PropToAdd)      ) },
      set: function (val)   {           set(PropToAdd,  val ) }
    })
}
//------------------------------

// Usage Part
// Create properties
configCreate('preview')
configCreate('notification')
//...

// Config Data transfer
//set
config.preview = true

//get
config.preview

// delete
config.preview = undefined

Bueno, puedes quitar la parte de los alias con .bind(...) . Sin embargo, acabo de ponerlo, ya que es realmente bueno saber sobre esto. Me tomó horas averiguar por qué get = localStorage.getItem; simple get = localStorage.getItem; no trabajes




Creo que para evitar ese tipo de problema en las cookies locales, de sesión, puedes usar la biblioteca de opendb.

Ex- En el que puedes resolver esto usando este fragmento

// for set object in db
db.local.setJSON("key", {name: "xyz"});  

// for get object form db
db.local.getJSON("key");

https://github.com/pankajbisht/openDB




Hice una cosa que no rompe los objetos de almacenamiento existentes, pero crea un contenedor para que pueda hacer lo que quiera. El resultado es un objeto normal, sin métodos, con acceso como cualquier objeto.

Lo que hice.

Si quieres que 1 propiedad localStorage sea ​​mágica:

var prop = ObjectStorage(localStorage, 'prop');

Si necesita varios:

var storage = ObjectStorage(localStorage, ['prop', 'more', 'props']);

Todo lo que haga para prop , o los objetos dentro de storage se guardarán automáticamente en localStorage . Siempre juegas con un objeto real, por lo que puedes hacer cosas como esta:

storage.data.list.push('more data');
storage.another.list.splice(1, 2, {another: 'object'});

Y cada objeto nuevo dentro de un objeto rastreado se rastreará automáticamente.

La gran desventaja: depende de Object.observe() por lo que tiene un soporte de navegador muy limitado. Y no parece que vendrá para Firefox o Edge pronto.




Mirar esto

Supongamos que tiene la siguiente matriz llamada películas:

var movies = ["Reservoir Dogs", "Pulp Fiction", "Jackie Brown", 
              "Kill Bill", "Death Proof", "Inglourious Basterds"];

Usando la función de stringify, su matriz de películas se puede convertir en una cadena usando la siguiente sintaxis:

localStorage.setItem("quentinTarantino", JSON.stringify(movies));

Tenga en cuenta que mis datos se almacenan con la clave llamada quentinTarantino.

Recuperando tus datos

var retrievedData = localStorage.getItem("quentinTarantino");

Para convertir de una cadena a un objeto, use la función de análisis JSON:

var movies2 = JSON.parse(retrievedData);

Puede llamar a todos los métodos de matriz en sus películas2




Para almacenar un objeto, podría hacer una letra que pueda usar para obtener un objeto de una cadena a un objeto (puede no tener sentido). Por ejemplo

var obj = {a: "lol", b: "A", c: "hello world"};
function saveObj (key){
    var j = "";
    for(var i in obj){
        j += (i+"|"+obj[i]+"~");
    }
    localStorage.setItem(key, j);
} // Saving Method
function getObj (key){
    var j = {};
    var k = localStorage.getItem(key).split("~");
    for(var l in k){
        var m = k[l].split("|");
        j[m[0]] = m[1];
    }
    return j;
}
saveObj("obj"); // undefined
getObj("obj"); // {a: "lol", b: "A", c: "hello world"}

Esta técnica causará algunas fallas si usas la letra que usaste para dividir el objeto, y también es muy experimental.




Un pequeño ejemplo de una biblioteca que usa localStorage para hacer un seguimiento de los mensajes recibidos de los contactos:

// This class is supposed to be used to keep a track of received message per contacts.
// You have only four methods:

// 1 - Tells you if you can use this library or not...
function isLocalStorageSupported(){
    if(typeof(Storage) !== "undefined" && window['localStorage'] != null ) {
         return true;
     } else {
         return false;
     }
 }

// 2 - Give the list of contacts, a contact is created when you store the first message
 function getContacts(){
    var result = new Array();
    for ( var i = 0, len = localStorage.length; i < len; ++i ) {
        result.push(localStorage.key(i));
    }
    return result;
 }

 // 3 - store a message for a contact
 function storeMessage(contact, message){
    var allMessages;
    var currentMessages = localStorage.getItem(contact);
    if(currentMessages == null){
        var newList = new Array();
        newList.push(message);
        currentMessages = JSON.stringify(newList);
    }
    else
    {
        var currentList =JSON.parse(currentMessages);
        currentList.push(message);
        currentMessages = JSON.stringify(currentList);
    }
    localStorage.setItem(contact, currentMessages);
 }

 // 4 - read the messages of a contact
 function readMessages(contact){

    var result = new Array();
    var currentMessages = localStorage.getItem(contact);

    if(currentMessages != null){
        result =JSON.parse(currentMessages);
    }
    return result;
 }



Loop throughto localstorage

var retrievedData = localStorage.getItem("MyCart");                 

                    retrievedData.forEach(function (item) {
                        console.log(item.itemid);
                    })