I'd like to store a JavaScript object in HTML5 localStorage, but my object is apparently being converted to a string.
I can store and retrieve primitive JavaScript types and arrays using localStorage, but objects don't seem to work. Should they?
Here's my code:
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);
The console output is
typeof testObject: object
testObject properties:
one: 1
two: 2
three: 3
typeof retrievedObject: string
Value of retrievedObject: [object Object]
It looks to me like the setItem method is converting the input to a string before storing it.
I see this behavior in Safari, Chrome, and Firefox, so I assume it's my misunderstanding of the HTML5 Web Storage specification, not a browser-specific bug or limitation.
I've tried to make sense of the structured clone algorithm described in 2 Common infrastructure. I don't fully understand what it's saying, but maybe my problem has to do with my object's properties not being enumerable (???).
Is there an easy workaround?
Update: The W3C eventually changed their minds about the structured-clone specification, and decided to change the spec to match the implementations. See 12111 – spec for Storage object getItem(key) method does not match implementation behavior. So this question is no longer 100% valid, but the answers still may be of interest.
24 Answers
Looking at the Apple, Mozilla and Mozilla again documentation, the functionality seems to be limited to handle only string key/value pairs.
A workaround can be to stringify your object before storing it, and later parse it when you retrieve it:
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));
A minor improvement on a variant:
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);
}
Because of short-circuit evaluation, getObject() will immediately return null if key is not in Storage. It also will not throw a SyntaxError exception if value is "" (the empty string; JSON.parse() cannot handle that).
You might find it useful to extend the Storage object with these handy methods:
Storage.prototype.setObject = function(key, value) {
this.setItem(key, JSON.stringify(value));
}
Storage.prototype.getObject = function(key) {
return JSON.parse(this.getItem(key));
}
This way you get the functionality that you really wanted even though underneath the API only supports strings.
Creating a facade for the Storage object is an awesome solution. That way, you can implement your own get and set methods. For my API, I have created a facade for localStorage and then check if it is an object or not while setting and getting.
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;
}
}
Stringify doesn't solve all problems
It seems that the answers here don't cover all types that are possible in JavaScript, so here are some short examples on how to deal with them correctly:
// 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"
I do not recommend to store functions, because eval() is evil and can lead to issues regarding security, optimisation and debugging.
In general, eval() should never be used in JavaScript code.