34

I am trying to develop an offline HTML5 application that should work in most modern browsers (Chrome, Firefox, IE 9+, Safari, Opera). Since IndexedDB isn't supported by Safari (yet), and WebSQL is deprecated, I decided on using localStorage to store user-generated JavaScript objects and JSON.stringify()/JSON.parse() to put in or pull out the objects. However, I found out that JSON.stringify() does not handle methods. Here is an example object with a simple method:

    var myObject = {};
    myObject.foo = 'bar';
    myObject.someFunction = function () {/*code in this function*/}

If I stringify this object (and later put it into localStorage), all that will be retained is myObject.foo, not myObject.someFunction().

    //put object into localStorage
    localStorage.setItem('myObject',JSON.stringify(myObject));

    //pull it out of localStorage and set it to myObject
    myObject = localStorage.getItem('myObject');

    //undefined!
    myObject.someFunction

I'm sure many of you probably already know of this limitation/feature/whatever you want to call it. The workaround that I've come up with is to create an object with the methods(myObject = new objectConstructor()), pull out the object properties from localStorage, and assign them to the new object I created. I feel that this is a roundabout approach, but I'm new to the JavaScript world, so this is how I solved it. So here is my grand question: I'd like the whole object (properties + methods) to be included in localStorage. How do I do this? If you can perhaps show me a better algorithm, or maybe another JSON method I don't know about, I'd greatly appreciate it.

Sharif
  • 481
  • 1
  • 4
  • 10
  • 4
    Functions don't exist in JSON. See: http://json.org. – Felix Kling Aug 06 '13 at 19:44
  • That's what I thought after reading the spec, but do you have any way that I could save the methods? Or is my way how you would do this? – Sharif Aug 06 '13 at 19:44
  • possible duplicate of [JSON.stringify function](http://stackoverflow.com/questions/6754919/json-stringify-function) – Bergi Aug 06 '13 at 19:46
  • 1
    I think using a constructor function would make sense. After all, why do you want to store the methods? The data is what makes an object unique, the methods are just ways to work with the data. I'd add a method that populates the object from a JSON string. – Felix Kling Aug 06 '13 at 19:46
  • 3
    Your "workaround" is exactly how to do it. Do only serialize and store the data, not the whole object. – Bergi Aug 06 '13 at 19:47
  • related questions (possibly even duplicates): http://stackoverflow.com/q/16974854/218196, http://stackoverflow.com/q/12975430/218196 – Felix Kling Aug 06 '13 at 19:51
  • Users should generate data. You can always recreate the code later using the data. Once you bake your JS into localstorage with functions attached. You'll have a difficult time upgrading that code. – Reactgular Aug 06 '13 at 19:53
  • Possible duplicate of http://stackoverflow.com/questions/6754919/json-stringify-function – plalx Aug 06 '13 at 19:56
  • @FelixKling, Bergi That's what I needed to hear. Javascript is a whole new animal compared to other languages like C++, so some of my efforts in creating a JavaScript app are solely towards understanding the web paradigm. Thanks for your comments. – Sharif Aug 06 '13 at 20:36
  • @MathewFoscarini, That is what I realized once I found out JSON doesn't take functions. Oh well, lesson learned! – Sharif Aug 06 '13 at 20:37

3 Answers3

56

Functions in javascript are more than just their code. They also have scope. Code can be stringified, but scope cannot.

JSON.stringify() will encode values that JSON supports. Objects with values that can be objects, arrays, strings, numbers and booleans. Anything else will be ignored or throw errors. Functions are not a supported entity in JSON. JSON handles pure data only, functions are not data, but behavior with more complex semantics.


That said you can change how JSON.stringify() works. The second argument is a replacer function. So you could force the behavior you want by forcing the strinigification of functions:

var obj = {
  foo: function() {
    return "I'm a function!";
  }
};

var json = JSON.stringify(obj, function(key, value) {
  if (typeof value === 'function') {
    return value.toString();
  } else {
    return value;
  }
});

console.log(json);
// {"foo":"function () { return \"I'm a function!\" }"}

But when you read that back in you would have to eval the function string and set the result back to the object, because JSON does not support functions.


All in all encoding functions in JSON can get pretty hairy. Are you sure you want to do this? There is probably a better way...

Perhaps you could instead save raw data, and pass that to a constructor from your JS loaded on the page. localStorage would only hold the data, but your code loaded onto the page would provide the methods to operate on that data.

// contrived example...

var MyClass = function(data) {
  this.firstName = data.firstName;
  this.lastName = data.lastName;
}

MyClass.prototype.getName() {
  return this.firstName + ' ' + this.lastName;
}

localStorage.peopleData = [{
  firstName: 'Bob',
  lastName:  'McDudeFace'
}];

var peopleData = localStorage.peopleData;

var bob = new MyClass(peopleData[0]);
bob.getName() // 'Bob McDudeFace'

We don't need to save the getName() method to localStorage. We just need to feed that data into a constructor that will provide that method.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • 2
    Ok, I see what you're saying. I just wanted to see if there was something I was missing. I will end up just storing data and passing that data to a constructor function. Thanks for clearing that up! – Sharif Aug 06 '13 at 20:32
  • Excellent answer! Just thought I'd add that the _contrived example_ should be clarified: `localStorage.peopleData = [{ firstName: 'Bob', lastName: 'McDudeFace' }];` should be: `localStorage.peopleData = JSON.stringify( [{ firstName: 'Bob', lastName: 'McDudeFace' }] );` That is, the _array of objects_ being stored in localStorage needs to be converted to a JSON string. – DanDan Nov 06 '13 at 02:05
  • When I try to parse the JSON, I have this error: myMethod is not a function ; why? – SphynxTech Nov 03 '17 at 07:35
1

If you want to stringify your objects, but they have functions, you can use JSON.stringify() with the second parameter replacer. To prevent cyclic dependencies on objects you can use a var cache = [].

In our project we use lodash. We use the following function to generate logs. Can be used it to save objects to localStorage.

var stringifyObj = function(obj) {
  var cache = []
  return JSON.stringify(obj, function(key, value) {
    if (
      _.isString(value) ||
      _.isNumber(value) ||
      _.isBoolean(value)
    ) {
      return value
    } else if (_.isError(value)) {
      return value.stack || ''
    } else if (_.isPlainObject(value) || _.isArray(value)) {
      if (cache.indexOf(value) !== -1) {
        return
      } else {
        // cache each item 
        cache.push(value)
        return value
      }
    }
  })
}

// create a circular object
var circularObject = {}
circularObject.circularObject = circularObject

// stringify an object
$('body').text(
  stringifyObj(
    {
      myBooblean: true,
      myString: 'foo',
      myNumber: 1,
      myArray: [1, 2, 3],
      myObject: {},
      myCircularObject: circularObject,
      myFunction: function () {}
    }
  )
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
0

Does not fix functions as requested, but a way to store variables locally...

<html>
  <head>
    <title>Blank</title>
    <script>
      if(localStorage.g===undefined) localStorage.g={};
      var g=JSON.parse(localStorage.g);
    </script>
  </head>
  <body>
    <input type=button onClick="localStorage.g=JSON.stringify(g, null, '  ')" value="Save">
    <input type=button onClick="g=JSON.parse(localStorage.g)" value="Load">
  </body>
</html>

Keep all variables in object g. Example:

  g.arr=[1,2,3];
  • note some types, such as Date, you'll need to do something like:
  g.date=new Date(g.date);
  • stores locally per page: different pages have different gs
iAmOren
  • 2,760
  • 2
  • 11
  • 23