Almacenamiento de objetos en HTML5 localStorage

Me gustaría almacenar un objeto JavaScript en HTML5. localStorage, pero mi objeto aparentemente 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 es convertir la entrada en una cadena antes de almacenarla.

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

He tratado de darle sentido a la clon estructurado algoritmo descrito en http://www.w3.org/TR/html5/infrastructure.html. No entiendo completamente lo que dice, pero tal vez mi problema tenga que ver con que las propiedades de mi objeto no son enumerables (???)

¿Existe una solución alternativa 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 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.

preguntado el 06 de enero de 10 a las 01:01

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 las implementaciones salieron. Presenté error bugzilla.mozilla.org/show_bug.cgi?id=538142 con mozilla para realizar un seguimiento de este problema. -

Esto parece un trabajo para indexedDB ... -

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

en su lugar, ¿podría simplemente serializar la matriz? como almacenar con JSON stringify y luego analizar nuevamente al cargar? -

Puede usar el almacenamiento de datos local para almacenar de forma transparente tipos de datos de JavaScript (Array, Boolean, Date, Float, Integer, String y Object) -

23 Respuestas

En cuanto al Apple, Mozilla y Mozilla de nuevo documentación, la funcionalidad parece estar limitada para manejar solo pares 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));

Respondido el 15 de junio de 19 a las 05:06

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

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

... se aplica solo a objetos con referencias circulares, JSON.stringify() expande el objeto referenciado a su "contenido" completo (implícitamente en cadena) en el objeto que clasificamos en cadena. Ver: stackoverflow.com/a/12659424/2044940 - CódigoManX

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

@oligofren cierto, pero como Maja eval () => sugerido correctamente, este es uno de los buenos usos de, puede recuperar fácilmente el código de función => almacenarlo como una cadena y luego evaluar () de nuevo :) - jave.web

Una pequeña mejora en un 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()inmediatamente retorno null if key no está almacenado. Tampoco arrojará un SyntaxError excepción si value is "" (la cadena vacía; JSON.parse() no puedo manejar eso).

Respondido 28 Abr '18, 20:04

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 recupéralo del almacenamiento userObject = localStorage.getObject('user'); Incluso puede almacenar una serie de objetos si lo desea. - zuallauz

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. - Guria

No veo el sentido de la variable local y la evaluación del atajo aquí (dejando de lado las mejoras menores en el desempeño). 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 tampoco, ni en Chromium 21 ni por ES 5.1 sección 15.12.2, porque String(null) === "null" que se puede interpretar como un Literal JSON. - Orejas puntiagudas

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

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

Puede que le resulte útil ampliar el objeto Storage 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 admite cadenas.

Respondido el 06 de enero de 10 a las 04:01

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

Este planteamiento de « 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

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

Extender el objeto Storage es una solución asombrosa. 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;
  }
}

Respondido 25 Oct 13, 17:10

Esto era casi exactamente lo que necesitaba. Solo tenía que agregar if (value == null) {return false} antes del comentario, de lo contrario resultó en un error al verificar la existencia de una clave en localStorage. - francesco frapporti

En realidad, esto es bastante bueno. De acuerdo con @FrancescoFrapporti, necesita un if para valores nulos. También agregué un '|| valor [0] == "[" 'prueba en caso de que haya una matriz allí. - rober_james

Buen punto, editaré esto. Aunque no necesita la parte nula, pero si la necesita, le recomiendo tres ===. Si usa JSHint o JSLint, se le advertirá que no use ==. - alex grande

Y para los que no son ninjas (como yo), ¿podría alguien proporcionar un ejemplo de uso para esta respuesta? Lo es: data.set('username': 'ifedi', 'fullname': { firstname: 'Ifedi', lastname: 'Okonkwo'});? - Ifedi Okonkwo

¡Sí de verdad! Cuando superé mi deseo de ser alimentado con cuchara, tomé el código para probarlo y lo obtuve. Creo que esta respuesta es excelente porque 1) A diferencia de la respuesta aceptada, lleva tiempo hacer ciertas verificaciones en los datos de la cadena y 2) A diferencia de la siguiente, no amplía un objeto nativo. - Ifedi Okonkwo

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 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 para almacenar funciones porque eval() Este mal puede provocar problemas de seguridad, optimización y depuración. En general, eval() nunca debe usarse en código JavaScript.

Miembros privados

El problema de usar 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 al almacenar 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 puedo 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 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

contestado el 23 de mayo de 17 a las 13:05

Por lo tanto, y no hace falta decirlo, el almacenamiento de datos en el almacenamiento debe basarse en la única premisa de copias de datos simples. Objetos no vivos. - Roko C. Bulján

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

Puedes configurar un objeto

$.jStorage.set(key, value)

Y recupéralo fácilmente

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

respondido 27 mar '18, 04:03

@SuperUberDuper jStorage requiere Prototype, MooTools o jQuery - jprogramador

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 es dependiente de la implementación.

Respondido 08 Oct 12, 17:10

La serialización / deserialización de funciones no es confiable porque es dependiente de la implementación. Además, desea reemplazar c.f[k] = escape(a[k]); con el seguro Unicode c.f[k] = encodeURIComponent(a[k]); y eval('b.' + k + ' = ' + unescape(data.f[k])); con b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");. Los paréntesis son obligatorios porque su función, si se serializa correctamente, es probable que sea anónima, lo cual no es como es una declaración / válida (por lo que eval()) lanzaría un SyntaxError excepción de lo contrario). - Orejas puntiagudas

Y también debes typeof es un operador, no lo escriba como si fuera una función. Reemplazar typeof(a[k]) con typeof a[k]. - Orejas puntiagudas

Además de aplicar mis sugerencias y enfatizar la falta de confiabilidad del enfoque, he corregido los siguientes errores: 1. No se declararon todas las variables. 2. for-in no se filtró por propiedades propias. 3. El estilo del código, incluidas las referencias, era inconsistente. - Orejas puntiagudas

@PointedEars ¿Qué diferencia práctica hace esto? la especificación dice the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent. No veo ninguna diferencia funcional. - Michael

@Michael La parte que citó comienza con Note *in particular* that …. Pero la especificación del valor de retorno comienza con An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. El valor de retorno puede ser function foo () {} - asumiendo un conforme implementación. - Orejas puntiagudas

Llegué a esta publicación después de hacer clic en otra publicación que se cerró como un duplicado de esta, titulada '¿Cómo almacenar una matriz en almacenamiento local?'. Lo cual está bien, excepto que ninguno de los hilos proporciona una respuesta completa sobre cómo puede mantener una matriz en localStorage; sin embargo, me las arreglé para crear una solución basada en la información contenida en ambos hilos.

Entonces, si alguien más quiere poder presionar / hacer pop / 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);
}

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

contestado el 07 de mayo de 14 a las 15:05

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

Recomiende el uso de una biblioteca de abstracción para muchas de las funciones que se analizan aquí, así como para una mejor compatibilidad. Muchas opciones:

Respondido 02 Jul 15, 00:07

Puede usar el almacenamiento de datos local 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 las cadenas, facilita la consulta por clave (nombre) así como la consulta por valor (clave) y ayuda a hacer cumplir el almacenamiento compartido segmentado dentro del mismo dominio mediante el prefijo de claves.

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

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, se respetan los valores primitivos.

Respondido 17 Jul 17, 18:07

Este es un recurso brillante y justo lo que necesito. Estoy haciendo aplicaciones Ionic con AngularJS donde necesito guardar ciertos objetos javascript en localStorage y hasta este punto solo he estado haciendo JSON.parse y JSON.stringify, y funcionan, pero es un poco más engorroso que poder para usar una utilidad como esta. Voy a intentarlo. - Nmuta

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

Por ejemplo: persistir 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).

características persisto

(Descargo de responsabilidad: soy el autor).

Respondido el 16 de junio de 16 a las 20:06

Todavía estoy trabajando en mi archivo Léame, pero mi version no se exigir jQuery, como parece que persisto lo hace, pero proporciona una alternativa para tratar con objetos de elementos de jQuery. Agregaré más en el futuro cercano, a medida que trabaje más con él, para ayudarlo a manejar aún más los diferentes Objetos jQuery y mantener cosas como datos persistentes. Además, ¡+1 por intentar ofrecer una solución más sencilla! Además, utiliza todos los métodos tradicionales de localStroage; Exp: var lsh = new localStorageHelper(); lsh.setItem('bob', 'bill'); También incluye eventos. - SpYk3HH

Puede usar el 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 seguros para JSON, así como con:

Todas las serializaciones de EJSON también son 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 contenedor localStorage usando ejson

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

Agregué algunos tipos a mi contenedor, incluidas expresiones y funciones regulares

Respondido 28 Jul 16, 16:07

No puede almacenar el valor de la clave sin Cordón Formato.

Almacenamiento local solo admite formato de cadena para clave / valor.

Es por eso que debe convertir sus datos en una cadena, sea lo que sea Formación or Objeto.

Para almacenar datos en localStorage, en primer lugar, especifíquelo usando JSON.stringify () método.

var myObj = [{name:"test", time:"Date 2017-02-03T08:38:04.449Z"}];
localStorage.setItem('item', JSON.stringify(myObj));

Luego, cuando desee recuperar datos, debe volver a analizar la Cadena a Objeto.

var getObj = JSON.parse(localStorage.getItem('item'));

Espero eso ayude.

Respondido el 21 de enero de 20 a las 06:01

Hice otro contenedor 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

Respondido el 28 de Septiembre de 16 a las 11:09

Para los usuarios de TypeScript que deseen establecer y obtener propiedades escritas:

/**
 * Silly wrapper to be able to type the storage keys
 */
export class TypedStorage<T> {

    public removeItem(key: keyof T): void {
        localStorage.removeItem(key);
    }

    public getItem<K extends keyof T>(key: K): T[K] | null {
        const data: string | null =  localStorage.getItem(key);
        return JSON.parse(data);
    }

    public setItem<K extends keyof T>(key: K, value: T[K]): void {
        const data: string = JSON.stringify(value);
        localStorage.setItem(key, data);
    }
}

Ejemplo de uso:

// write an interface for the storage
interface MyStore {
   age: number,
   name: string,
   address: {city:string}
}

const storage: TypedStorage<MyStore> = new TypedStorage<MyStore>();

storage.setItem("wrong key", ""); // error unknown key
storage.setItem("age", "hello"); // error, age should be number
storage.setItem("address", {city:"Here"}); // ok

const address: {city:string} = storage.getItem("address");

contestado el 30 de mayo de 18 a las 12:05

https://github.com/adrianmay/rhaboo es una capa de azúcar localStorage 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 usa JSON.stringify / parse porque 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.

contestado el 08 de mayo de 20 a las 08:05

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

También implementará borrar valor de localstorage y muestra cómo agregar una capa Getter y Setter para que en lugar de

localstorage.setItem(preview, true)

puedes escribir

config.preview = true

Bien, aquí vamos:

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, lo puse porque es muy bueno saberlo. Me tomó horas descubrir por qué un simple get = localStorage.getItem; no trabajes

Respondido 10 Feb 15, 20:02

Hice algo que no rompe los objetos Storage existentes, pero crea una envoltura para que puedas hacer lo que quieras. El resultado es un objeto normal, sin métodos, con acceso como cualquier objeto.

Lo que hice.

Si quieres 1 localStorage propiedad para ser mágico:

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

Si necesita varios:

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

Todo lo que haces para prop, o los objetos dentro storage se guardará automáticamente en localStorage. Siempre estás jugando con un objeto real, así que puedes hacer cosas como esta:

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

Y cada nuevo objeto dentro un objeto rastreado será rastreado automáticamente.

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

respondido 28 nov., 15:20

Encontré una manera de hacerlo funcionar con objetos que tienen referencias cíclicas.

Hagamos un objeto con referencias cíclicas.

obj = {
    L: {
        L: { v: 'lorem' },
        R: { v: 'ipsum' }
    },
    R: {
        L: { v: 'dolor' },
        R: {
            L: { v: 'sit' },
            R: { v: 'amet' }
        }
    }
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;

No podemos hacer JSON.stringify aquí, debido a las referencias circulares.

tío circular

LOCALSTORAGE.CYCLICJSON tiene .stringify y .parse como de costumbre JSON, pero trabaja con objetos con referencias circulares. ("Works" significa parse (stringify (obj)) y obj son profundamente iguales Y tienen conjuntos idénticos de 'igualdades internas')

Pero podemos usar los atajos:

LOCALSTORAGE.setObject('latinUncles', obj)
recovered = LOCALSTORAGE.getObject('latinUncles')

Entonces, recovered será "lo mismo" que obj, en el siguiente sentido:

[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
]

Aquí está la implementación de LOCALSTORAGE

LOCALSTORAGE = (function(){
  "use strict";
  var ignore = [Boolean, Date, Number, RegExp, String];
  function primitive(item){
    if (typeof item === 'object'){
      if (item === null) { return true; }
      for (var i=0; i<ignore.length; i++){
        if (item instanceof ignore[i]) { return true; }
      }
      return false;
    } else {
      return true;
    }
  }
  function infant(value){
    return Array.isArray(value) ? [] : {};
  }
  function decycleIntoForest(object, replacer) {
    if (typeof replacer !== 'function'){
      replacer = function(x){ return x; }
    }
    object = replacer(object);
    if (primitive(object)) return object;
    var objects = [object];
    var forest  = [infant(object)];
    var bucket  = new WeakMap(); // bucket = inverse of objects 
    bucket.set(object, 0);    
    function addToBucket(obj){
      var result = objects.length;
      objects.push(obj);
      bucket.set(obj, result);
      return result;
    }
    function isInBucket(obj){ return bucket.has(obj); }
    function processNode(source, target){
      Object.keys(source).forEach(function(key){
        var value = replacer(source[key]);
        if (primitive(value)){
          target[key] = {value: value};
        } else {
          var ptr;
          if (isInBucket(value)){
            ptr = bucket.get(value);
          } else {
            ptr = addToBucket(value);
            var newTree = infant(value);
            forest.push(newTree);
            processNode(value, newTree);
          }
          target[key] = {pointer: ptr};
        }
      });
    }
    processNode(object, forest[0]);
    return forest;
  };
  function deForestIntoCycle(forest) {
    var objects = [];
    var objectRequested = [];
    var todo = [];
    function processTree(idx) {
      if (idx in objects) return objects[idx];
      if (objectRequested[idx]) return null;
      objectRequested[idx] = true;
      var tree = forest[idx];
      var node = Array.isArray(tree) ? [] : {};
      for (var key in tree) {
        var o = tree[key];
        if ('pointer' in o) {
          var ptr = o.pointer;
          var value = processTree(ptr);
          if (value === null) {
            todo.push({
              node: node,
              key: key,
              idx: ptr
            });
          } else {
            node[key] = value;
          }
        } else {
          if ('value' in o) {
            node[key] = o.value;
          } else {
            throw new Error('unexpected')
          }
        }
      }
      objects[idx] = node;
      return node;
    }
    var result = processTree(0);
    for (var i = 0; i < todo.length; i++) {
      var item = todo[i];
      item.node[item.key] = objects[item.idx];
    }
    return result;
  };
  var console = {
    log: function(x){
      var the = document.getElementById('the');
      the.textContent = the.textContent + '\n' + x;
	},
	delimiter: function(){
      var the = document.getElementById('the');
      the.textContent = the.textContent +
		'\n*******************************************';
	}
  }
  function logCyclicObjectToConsole(root) {
    var cycleFree = decycleIntoForest(root);
    var shown = cycleFree.map(function(tree, idx) {
      return false;
    });
    var indentIncrement = 4;
    function showItem(nodeSlot, indent, label) {
      var leadingSpaces = ' '.repeat(indent);
      var leadingSpacesPlus = ' '.repeat(indent + indentIncrement);
      if (shown[nodeSlot]) {
        console.log(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
      } else {
        console.log(leadingSpaces + label + ' object#' + nodeSlot);
        var tree = cycleFree[nodeSlot];
        shown[nodeSlot] = true;
        Object.keys(tree).forEach(function(key) {
          var entry = tree[key];
          if ('value' in entry) {
            console.log(leadingSpacesPlus + key + ": " + entry.value);
          } else {
            if ('pointer' in entry) {
              showItem(entry.pointer, indent + indentIncrement, key);
            }
          }
        });
      }
    }
	console.delimiter();
    showItem(0, 0, 'root');
  };
  function stringify(obj){
    return JSON.stringify(decycleIntoForest(obj));
  }
  function parse(str){
    return deForestIntoCycle(JSON.parse(str));
  }
  var CYCLICJSON = {
    decycleIntoForest: decycleIntoForest,
    deForestIntoCycle : deForestIntoCycle,
    logCyclicObjectToConsole: logCyclicObjectToConsole,
    stringify : stringify,
    parse : parse
  }
  function setObject(name, object){
    var str = stringify(object);
    localStorage.setItem(name, str);
  }
  function getObject(name){
    var str = localStorage.getItem(name);
    if (str===null) return null;
    return parse(str);
  }
  return {
    CYCLICJSON : CYCLICJSON,
    setObject  : setObject,
    getObject  : getObject
  }
})();
obj = {
	L: {
		L: { v: 'lorem' },
		R: { v: 'ipsum' }
	},
	R: {
		L: { v: 'dolor' },
		R: {
			L: { v: 'sit' },
			R: { v: 'amet' }
		}
	}
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;

// LOCALSTORAGE.setObject('latinUncles', obj)
// recovered = LOCALSTORAGE.getObject('latinUncles')
// localStorage not available inside fiddle ):
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(obj)
putIntoLS = LOCALSTORAGE.CYCLICJSON.stringify(obj);
recovered = LOCALSTORAGE.CYCLICJSON.parse(putIntoLS);
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(recovered);

var the = document.getElementById('the');
the.textContent = the.textContent + '\n\n' +
JSON.stringify(
[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
]
)
<pre id='the'></pre>

respondido 12 nov., 19:19

Para almacenar un objeto, puede hacer letras que pueda usar para llevar un objeto de una cadena a un objeto (puede que no tenga 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á algunos fallos si usa la letra que usó para dividir el objeto, y también es muy experimental.

contestado el 21 de mayo de 16 a las 17:05

Referencias circulares

En esta respuesta, me enfoco en objetos de solo datos (sin funciones, etc.) con referencias circulares y desarrollo ideas mencionadas por Maja y Mathheadinclouds (Yo uso su caso de prueba y mi código es varias veces más corto). De hecho, podemos usar JSON.stringify con la debida reemplazar - si el objeto de origen contiene referencias múltiples a algún objeto, o contiene referencias circulares, lo hacemos referencia mediante una cadena de ruta especial (similar a JSONPath)

// JSON.strigify replacer for objects with circ ref
function refReplacer() {
  let m = new Map(), v= new Map(), init = null;

  return function(field, value) {
    let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field); 
    let isComplex= value===Object(value)
    
    if (isComplex) m.set(value, p);  
    
    let pp = v.get(value)||'';
    let path = p.replace(/undefined\.\.?/,'');
    let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
    
    !init ? (init=value) : (val===init ? val="#REF:$" : 0);
    if(!pp && isComplex) v.set(value, path);
   
    return val;
  }
}


// ---------------
// TEST
// ---------------

// gen obj with duplicate/circular references
let obj = {
    L: {
        L: { v: 'lorem' },
        R: { v: 'ipsum' }
    },
    R: {
        L: { v: 'dolor' },
        R: {
            L: { v: 'sit' },
            R: { v: 'amet' }
        }
    }
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;
testObject = obj;

let json = JSON.stringify(testObject, refReplacer(), 4);

console.log("Test Object\n", testObject);
console.log("JSON with JSONpath references\n",json);

Analizar ese json con referencias similares a JSONpath

// parse json with JSONpath references to object
function parseRefJSON(json) {
  let objToPath = new Map();
  let pathToObj = new Map();
  let o = JSON.parse(json);
  
  let traverse = (parent, field) => {
    let obj = parent;
    let path = '#REF:$';

    if (field !== undefined) {
      obj = parent[field];
      path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
    }

    objToPath.set(obj, path);
    pathToObj.set(path, obj);
    
    let ref = pathToObj.get(obj);
    if (ref) parent[field] = ref;

    for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
  }
  
  traverse(o);
  return o;
}


// ---------------
// TEST 1
// ---------------

let json = `
{
    "L": {
        "L": {
            "v": "lorem",
            "uncle": {
                "L": {
                    "v": "dolor",
                    "uncle": "#REF:$.L"
                },
                "R": {
                    "L": {
                        "v": "sit",
                        "uncle": "#REF:$.L.L.uncle.L"
                    },
                    "R": {
                        "v": "amet",
                        "uncle": "#REF:$.L.L.uncle.L"
                    },
                    "uncle": "#REF:$.L"
                }
            }
        },
        "R": {
            "v": "ipsum",
            "uncle": "#REF:$.L.L.uncle"
        }
    },
    "R": "#REF:$.L.L.uncle"
}`;

let testObject = parseRefJSON(json);

console.log("Test Object\n", testObject);


// ---------------
// TEST 2
// ---------------

console.log('Tests from mathheadinclouds anser:');

let recovered = testObject;

let obj = { // original object
    L: {
        L: { v: 'lorem' },
        R: { v: 'ipsum' }
    },
    R: {
        L: { v: 'dolor' },
        R: {
            L: { v: 'sit' },
            R: { v: 'amet' }
        }
    }
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;

[
  obj.L.L.v === recovered.L.L.v,
  obj.L.R.v === recovered.L.R.v,
  obj.R.L.v === recovered.R.L.v,
  obj.R.R.L.v === recovered.R.R.L.v,
  obj.R.R.R.v === recovered.R.R.R.v,
  obj.R.L.uncle === obj.L,
  obj.R.R.uncle === obj.L,
  obj.R.R.L.uncle === obj.R.L,
  obj.R.R.R.uncle === obj.R.L,
  obj.L.L.uncle === obj.R,
  obj.L.R.uncle === obj.R,
  recovered.R.L.uncle === recovered.L,
  recovered.R.R.uncle === recovered.L,
  recovered.R.R.L.uncle === recovered.R.L,
  recovered.R.R.R.uncle === recovered.R.L,
  recovered.L.L.uncle === recovered.R,
  recovered.L.R.uncle === recovered.R
].forEach(x=> console.log('test pass: '+x));

Para cargar / guardar el resultado json en el almacenamiento, use el siguiente código

localStorage.myObject = JSON.stringify(testObject, refReplacer());  // save
testObject = parseRefJSON(localStorage.myObject);                   // load

Respondido 04 ago 20, 16:08

Sugiero usar Jackson-js, es una biblioteca que maneja la serialización y desirialización de Objetos conservando su estructura, basada en decoradores.

la biblioteca maneja todas las trampas como referencia cíclica, alias de atributos, etc.

simplemente describe tu clase usando los decoradores @JsonProperty () @JsonClassType () serializan tu objeto usando:

const objectMapper = new ObjectMapper();        
localstore.setItem(key, objectMapper.stringify<yourObjectType>(yourObject));

para una explicación un poco más detallada, consulte mi respuesta aquí:

https://stackoverflow.com/a/66706365/1146499

y el tutorial de Jackson-js aquí:

https://itnext.io/jackson-js-powerful-javascript-decorators-to-serialize-deserialize-objects-into-json-and-vice-df952454cf

respondido 19 mar '21, 10:03

localStorage.setItem('user', JSON.stringify(user));

Luego, para recuperarlo de la tienda y convertirlo en un objeto nuevamente:

var user = JSON.parse(localStorage.getItem('user'));

If we need to delete all entries of the store we can simply do:

localStorage.clear();

Respondido 28 Feb 21, 20:02

Esta es una pregunta de hace 10 años. ¿Crees que tu respuesta agrega algo que no esté cubierto por las otras respuestas? - cristobal johnson

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.