[javascript] Storing Objects in HTML5 localStorage

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 spec, not a browser-specific bug or limitation.

I've tried to make sense of the structured clone algorithm described in http://www.w3.org/TR/html5/infrastructure.html. 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 https://www.w3.org/Bugs/Public/show_bug.cgi?id=12111. So this question is no longer 100% valid, but the answers still may be of interest.

This question is related to javascript html local-storage

The answer is


Here some extented version of the code posted by @danott

It'll also implement delete value from localstorage and shows how to adds a Getter and Setter layer so instead of

localstorage.setItem(preview, true)

you can write

config.preview = true

Okay here were go:

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

Well you may strip the aliases part with .bind(...). However I just put it in since it's really good to know about this. I tooked me hours to find out why a simple get = localStorage.getItem; don't work


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

Then to retrieve it from the store and convert to an object again:

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

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

localStorage.clear();


You can use localDataStorage to transparently store javascript data types (Array, Boolean, Date, Float, Integer, String and Object). It also provides lightweight data obfuscation, automatically compresses strings, facilitates query by key (name) as well as query by (key) value, and helps to enforce segmented shared storage within the same domain by prefixing keys.

[DISCLAIMER] I am the author of the utility [/DISCLAIMER]

Examples:

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

As you can see, the primitive values are respected.


https://github.com/adrianmay/rhaboo is a localStorage sugar layer that lets you write things like this:

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

It doesn't use JSON.stringify/parse because that would be inaccurate and slow on big objects. Instead, each terminal value has its own localStorage entry.

You can probably guess that I might have something to do with rhaboo.


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.


You cannot store key value without String Format.

LocalStorage only support String format for key/value.

That is why you should convert your data to string whatever it is Array or Object.

To Store data in localStorage first of all stringify it using JSON.stringify() method.

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

Then when you want to retrieve data , you need to parse the String to Object again.

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

Hope it helps.


I arrived at this post after hitting on another post that has been closed as a duplicate of this - titled 'how to store an array in localstorage?'. Which is fine except neither thread actually provides a full answer as to how you can maintain an array in localStorage - however I have managed to craft a solution based on information contained in both threads.

So if anyone else is wanting to be able to push/pop/shift items within an array, and they want that array stored in localStorage or indeed sessionStorage, here you go:

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

example usage - storing simple strings in localStorage array:

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

example usage - storing objects in sessionStorage array:

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

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

common methods to manipulate arrays:

.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

I made another minimalistic wrapper with only 20 lines of code to allow using it like it should:

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


I made a thing that doesn't break the existing Storage objects, but creates a wrapper so you can do what you want. The result is a normal object, no methods, with access like any object.

The thing I made.

If you want 1 localStorage property to be magic:

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

If you need several:

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

Everything you do to prop, or the objects inside storage will be automatically saved into localStorage. You're always playing with a real object, so you can do stuff like this:

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

And every new object inside a tracked object will be automatically tracked.

The very big downside: it depends on Object.observe() so it has very limited browser support. And it doesn't look like it'll be coming for Firefox or Edge anytime soon.


To store an object, you could make a letters that you can use to get an object from a string to an object (may not make sense). For example

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

This technique will cause some glitches if you use the letter that you used to split the object, and it's also very experimental.


Another option would be to use an existing plugin.

For example persisto is an open source project that provides an easy interface to localStorage/sessionStorage and automates persistence for form fields (input, radio buttons, and checkboxes).

persisto features

(Disclaimer: I am the author.)


I found a way to make it work with objects that have cyclic references.

Let's make an object with cyclic references.

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;

We can't do JSON.stringify here, because of the circular references.

circularUncle

LOCALSTORAGE.CYCLICJSON has .stringify and .parse just like normal JSON, but works with objects with circular references. ("Works" meaning parse(stringify(obj)) and obj are deep equal AND have identical sets of 'inner equalities')

But we can just use the shortcuts:

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

Then, recovered will be "the same" to obj, in the following sense:

[
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
]

Here is the implementation of LOCALSTORAGE

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


Recommend using an abstraction library for many of the features discussed here as well as better compatibility. Lots of options:


You can use ejson to store the objects as strings.

EJSON is an extension of JSON to support more types. It supports all JSON-safe types, as well as:

All EJSON serializations are also valid JSON. For example an object with a date and a binary buffer would be serialized in EJSON as:

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

Here is my localStorage wrapper using ejson

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

I added some types to my wrapper including regular expressions and functions


There is a great library that wraps many solutions so it even supports older browsers called jStorage

You can set an object

$.jStorage.set(key, value)

And retrieve it easily

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

In theory, it is possible to store objects with functions:

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

However, Function serialization/deserialization is unreliable because it is implementation-dependent.


Extending the Storage object is an awesome solution. 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;
  }
}

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


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 can lead to issues regarding security, optimisation and debugging. In general, eval() should never be used in JavaScript code.

Private members

The problem with using JSON.stringify() for storing objects is, that this function can not serialise private members. This issue can be solved by overwriting the .toString() method (which is called implicitly when storing data in web storage):

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

Circular references

Another problem stringify can't deal with are circular references:

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

In this example, JSON.stringify() will throw a TypeError "Converting circular structure to JSON". If storing circular references should be supported, the second parameter of JSON.stringify() might be used:

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

However, finding an efficient solution for storing circular references highly depends on the tasks that need to be solved, and restoring such data is not trivial either.

There are already some question on SO dealing with this problem: Stringify (convert to JSON) a JavaScript object with circular reference


For Typescript users willing to set and get typed properties:

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

Example usage:

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

Circular References

In this answer I focus on data-only objects (without functions etc.) with circular references and develop ideas mention by maja and mathheadinclouds (I use his test case and my code is several times shorter). Actually we can use JSON.stringify with proper replacer - if source object contains multi-references to some object, or contains circular references then we reference it by special path-string (similar to JSONPath)

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

Parse such json with JSONpath-like references

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

To load/save result json into storage use following code

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

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to local-storage

Angular 6: saving data to local storage Is it safe to store a JWT in localStorage with ReactJS? How to save to local storage using Flutter? Setting and getting localStorage with jQuery Local storage in Angular 2 How to store token in Local or Session Storage in Angular 2? QuotaExceededError: Dom exception 22: An attempt was made to add something to storage that exceeded the quota What is the difference between localStorage, sessionStorage, session and cookies? How to save an image to localStorage and display it on the next page? Can local storage ever be considered secure?