Archiviazione di oggetti in HTML5 localStorage

Vorrei memorizzare un object JavaScript in HTML5 localStorage , ma il mio object apparentemente viene convertito in una stringa.

Posso archiviare e recuperare i tipi e gli array primitivi di JavaScript usando localStorage , ma gli oggetti non sembrano funzionare. Dovrebbero?

Ecco il mio codice:

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

L’output della console è

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

Mi sembra che il metodo setItem stia convertendo l’input in una stringa prima di memorizzarlo.

Vedo questo comportamento in Safari, Chrome e Firefox, quindi presumo sia la mia incomprensione delle specifiche di HTML Web Storage , non un bug o limitazione specifico del browser.

Ho cercato di dare un senso all’algoritmo clone strutturato descritto in http://www.w3.org/TR/html5/infrastructure.html . Non capisco perfettamente cosa stia dicendo, ma forse il mio problema ha a che fare con le proprietà del mio object che non sono enumerabili (???)

C’è una soluzione facile?


Aggiornamento: il W3C alla fine cambiò idea riguardo alle specifiche del clone strutturato e decise di cambiare le specifiche per adattarle alle implementazioni. Vedi https://www.w3.org/Bugs/Public/show_bug.cgi?id=12111 . Quindi questa domanda non è più valida al 100%, ma le risposte potrebbero comunque essere di interesse.

Guardando la documentazione Apple , Mozilla e Microsoft , la funzionalità sembra essere limitata a gestire solo coppie chiave / valore stringa.

Una soluzione alternativa può essere quella di stringificare l’object prima di memorizzarlo e successivamente analizzarlo quando lo si recupera:

 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 piccolo miglioramento su 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); } 

A causa della valutazione di cortocircuito , getObject() restituirà immediatamente null se key non è in Storage. Inoltre, non genera un’eccezione SyntaxError se il value è "" (la stringa vuota; JSON.parse() non può gestirlo).

Potresti trovare utile estendere l’object Storage con questi metodi pratici:

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

In questo modo si ottiene la funzionalità che si voleva veramente anche se sotto l’API supporta solo le stringhe.

Estendere l’object di archiviazione è una soluzione eccezionale. Per la mia API, ho creato una facciata per localStorage e poi controllo se si tratta di un object o meno durante l’impostazione e la ricezione.

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

C’è una grande libreria che avvolge molte soluzioni in modo da supportare anche i browser più vecchi chiamati jStorage

È ansible impostare un object

 $.jStorage.set(key, value) 

E recuperalo facilmente

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

Stringify non risolve tutti i problemi

Sembra che le risposte qui non coprano tutti i tipi che sono possibili in JavaScript, quindi ecco alcuni brevi esempi su come gestirli correttamente:

 //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("^/(.*)/([az]*)$"); 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" 

Non consiglio di memorizzare le funzioni perché eval() è malvagio può portare a problemi di sicurezza, ottimizzazione e debug. In generale, eval() non dovrebbe mai essere usato nel codice JavaScript.

Membri privati

Il problema con l’utilizzo di JSON.stringify() per l’archiviazione di oggetti è che questa funzione non può serializzare membri privati. Questo problema può essere risolto sovrascrivendo il metodo .toString() (che viene chiamato implicitamente quando si memorizzano i dati nella memoria 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); 

Riferimenti circolari

Un altro problema che stringify non può gestire sono i riferimenti circolari:

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

In questo esempio, JSON.stringify() genererà un JSON.stringify() TypeError “Conversione di una struttura circolare in JSON” . Se la memorizzazione di riferimenti circolari deve essere supportata, è ansible utilizzare il secondo parametro di 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; } }); 

Tuttavia, la ricerca di una soluzione efficiente per la memorizzazione di riferimenti circolari dipende molto dalle attività che devono essere risolte e il ripristino di tali dati non è banale.

Ci sono già alcune domande su SO che affronta questo problema: Stringify (convert a JSON) un object JavaScript con riferimento circolare

Utilizzo degli oggetti JSON per l’archiviazione locale:

//IMPOSTATO

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

//OTTENERE

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

// Iterazione di tutte le chiavi e i valori di archiviazione locali

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

// ELIMINA

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

In teoria, è ansible memorizzare oggetti con funzioni:

 function store (a) { var c = {f: {}, d: {}}; for (var k in a) { if (a.hasOwnProperty(k) && typeof a[k] === 'function') { cf[k] = encodeURIComponent(a[k]); } } cd = 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; } 

Tuttavia, la serializzazione / deserializzazione delle funzioni non è affidabile perché dipende dall’implementazione .

È anche ansible sovrascrivere i setItem(key,value) Storage setItem(key,value) e getItem(key) per gestire oggetti / matrici come qualsiasi altro tipo di dati. In questo modo, puoi semplicemente chiamare localStorage.setItem(key,value) e localStorage.getItem(key) come faresti normalmente.

Non l’ho testato estesamente, ma è sembrato funzionare senza problemi per un piccolo progetto con cui ho lavorato.

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

Sono arrivato a questo post dopo aver colpito un altro post che è stato chiuso come duplicato di questo – intitolato “come archiviare un array in localstorage?”. Che va bene, ma nessuno dei due thread fornisce una risposta completa su come è ansible mantenere un array in localStorage – tuttavia sono riuscito a creare una soluzione basata sulle informazioni contenute in entrambi i thread.

Quindi, se qualcun altro vuole essere in grado di spingere / muovere / spostare gli elementi all’interno di un array, e vogliono che l’array sia memorizzato in localStorage o effettivamente sessionStorage, qui vai:

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

esempio di utilizzo: memorizzazione di stringhe semplici nell’array localStorage:

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

esempio di utilizzo: memorizzazione di oggetti nella sessioneStruttura di archiviazione:

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

metodi comuni per manipolare gli array:

 .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 

Raccomandare l’uso di una libreria di astrazione per molte delle funzionalità qui discusse e una migliore compatibilità. Molte opzioni:

  • jStorage o simpleStorage << la mia preferenza
  • localForage
  • alekseykulikov / storage
  • Sedia da giardino
  • Store.js << un'altra buona opzione
  • Oh mio Dio

Ho modificato un po ‘la risposta più votata. Sono un fan di avere una singola funzione invece di 2 se non è necessario.

 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 

Inoltre, se non è impostato alcun valore, restituisce null invece di false . false ha un significato, null no.

È ansible utilizzare localDataStorage per archiviare in modo trasparente i tipi di dati javascript (Array, Boolean, Date, Float, Integer, String e Object). Fornisce inoltre un’ombreggiatura leggera dei dati, comprime automaticamente le stringhe, facilita la query per chiave (nome) e query per valore (chiave) e aiuta a rafforzare l’archiviazione condivisa segmentata all’interno dello stesso dominio tramite il prefisso delle chiavi.

[DISCLAIMER] Sono l’autore dell’utilità [/ DISCLAIMER]

Esempi:

 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 

Come puoi vedere, i valori primitivi sono rispettati.

Miglioramento della risposta di @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; } }; 

Un’altra opzione sarebbe quella di utilizzare un plug-in esistente.

Ad esempio, persisto è un progetto open source che fornisce un’interfaccia semplice a localStorage / sessionStorage e automatizza la persistenza per i campi modulo (input, pulsanti di opzione e caselle di controllo).

caratteristiche di persisto

(Disclaimer: I am the author.)

È ansible utilizzare ejson per memorizzare gli oggetti come stringhe.

EJSON è un’estensione di JSON per supportare più tipi. Supporta tutti i tipi JSON-safe, così come:

  • Data (Data JavaScript)
  • Binario (JavaScript Uint8Array o il risultato di EJSON.newBinary )
  • Tipi definiti dall’utente (vedere EJSON.addType . Ad esempio, Mongo.ObjectID è implementato in questo modo).

Tutte le serializzazioni EJSON sono anche JSON valide. Ad esempio, un object con una data e un buffer binario sarebbe serializzato in EJSON come:

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

Ecco il mio wrapper localStorage che usa ejson

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

Ho aggiunto alcuni tipi al mio wrapper includendo espressioni regolari e funzioni

http://rhaboo.org è uno strato di zucchero localStorage che ti permette di scrivere cose come questa:

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

Non usa JSON.stringify / parse perché sarebbe inaccurato e lento sui grandi oggetti. Al contrario, ciascun valore del terminale ha la propria voce localStorage.

Probabilmente puoi immaginare che potrei avere qualcosa a che fare con rhaboo 😉

Adriano.

Ho creato un altro wrapper minimalista con solo 20 righe di codice per consentirne l’utilizzo come dovrebbe:

 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

Per gli utenti di Typescript che desiderano impostare e ottenere proprietà digitate:

 /** * Silly wrapper to be able to type the storage keys */ export class TypedStorage { public removeItem(key: keyof T): void { localStorage.removeItem(key); } public getItem(key: K): T[K] | null { const data: string | null = localStorage.getItem(key); return JSON.parse(data); } public setItem(key: K, value: T[K]): void { const data: string = JSON.stringify(value); localStorage.setItem(key, data); } } 

Esempio di utilizzo :

 // write an interface for the storage interface MyStore { age: number, name: string, address: {city:string} } const storage: TypedStorage = new TypedStorage(); 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"); 

Qui una versione estesa del codice pubblicato da @danott

Implementerà anche il valore delete da localstorage e mostra come aggiungere un layer Getter e Setter così invece di

localstorage.setItem(preview, true)

tu puoi scrivere

config.preview = true

Va bene, qui andavano:

 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 

Bene, puoi .bind(...) parte degli alias con .bind(...) . Comunque l’ho appena inserito poiché è davvero bello saperlo. Mi ci sono voluti ore per scoprire perché un semplice get = localStorage.getItem; non lavorare

Ho creato una cosa che non rompe gli oggetti di archiviazione esistenti, ma crea un wrapper in modo da poter fare ciò che vuoi. Il risultato è un object normale, nessun metodo, con accesso come qualsiasi object.

La cosa che ho fatto.

Se vuoi che 1 proprietà localStorage sia magica:

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

Se hai bisogno di più:

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

Tutto ciò che fai per prop , o gli oggetti all’interno della storage verranno automaticamente salvati in localStorage . Stai giocando sempre con un object reale, quindi puoi fare cose del genere:

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

E ogni nuovo object all’interno di un object tracciato verrà automaticamente tracciato.

Lo svantaggio molto grande: dipende da Object.observe() quindi ha un supporto browser molto limitato. E non sembra che arriverà presto per Firefox o Edge.

Guarda questo

Supponiamo che tu abbia il seguente array chiamato film:

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

Usando la funzione stringify, l’array di filmati può essere trasformato in una stringa usando la seguente syntax:

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

Si noti che i miei dati vengono memorizzati sotto la chiave chiamata quentinTarantino.

Recupero dei dati

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

Per convertire da una stringa a un object, utilizzare la funzione di analisi JSON:

 var movies2 = JSON.parse(retrievedData); 

Puoi chiamare tutti i metodi dell’array sui tuoi film2

Per memorizzare un object, puoi creare una lettera che puoi usare per ottenere un object da una stringa a un object (potrebbe non avere senso). Per esempio

 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"} 

Questa tecnica causerà alcuni problemi se usi la lettera che hai usato per dividere l’object, ed è anche molto sperimentale.

Un piccolo esempio di una libreria che utilizza localStorage per tenere traccia dei messaggi ricevuti dai contatti:

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

I think to avoid those kind of problem on local, session, cookies you can use opendb library..

Ex- In which you can solve this using this snippet

 // 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

For more details about web storage you can read web storage article.

Better you make functions as setter and getter to localStorage , this way you will have better control and won’t have to repeat the JSON parsing and all. it will even handle your (” “) empty string key/data case smoothly.

  function setItemInStorage(dataKey, data){ localStorage.setItem(dataKey, JSON.stringify(data)); } function getItemFromStorage(dataKey){ var data = localStorage.getItem(dataKey); return data? JSON.parse(data): null ; } setItemInStorage('user', {name:'tony stark'}); getItemFromStorage('user') *// return {name:'tony stark'}* 

Loop throught localstorage

 var retrievedData = localStorage.getItem("MyCart"); retrievedData.forEach(function (item) { console.log(item.itemid); })