xRobot
xRobot

Reputation: 26573

Store object in the localStorage

// Extending the Storage object:
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);
}



// START HERE:

var dog = { 
   name : '',
   woof : function(){ console.log('woof woof, I am ' + this.name); }
}
dog.name = 'Lovely';

//Store in the localstorage
localStorage.setObject('mydog', dog); 

//Get from the localstorage
var mydog = localStorage.getObject('mydog');
console.log(mydog.name); // prints 'Lovely'
console.log(mydog.woof()); // ootest.html:36 Uncaught TypeError: mydog.woof is not a function(…)

Why I am getting that error ?

Upvotes: 1

Views: 1435

Answers (2)

terales
terales

Reputation: 3200

The right way: use object constructor

let Dog = function(name) {
  this.name = name
}

Dog.prototype.woof = function woof() {
  console.log('woof woof, I am ' + this.name);
}

// Create a 'static' method for a Dog 'class'
Dog.fromJSON = function fromJSON(json) {
 return new Dog(JSON.parse(json).name)
}

// Instantinate your objects with `new` keyword
let dog = new Dog('Lovely')
dog.woof()

let storedDog = JSON.stringify(dog)
console.log('stored:', storedDog)

// Dog.fromJSON() returns new Dog object,
// with the same parameters for a constructor
// and with all methods
let restoredDog = Dog.fromJSON(storedDog)
restoredDog.woof()

Limitation

This approach will work well only for objects than will behave similar if created with similar constructor parameter. If you want to store objects with rich data inside, please refer to How come JSON can't save object's functions? accepted answer.

Just for learning something new: storing functions in JSON

Functions can be created from string at runtime via Function object. To create function it we need to pass arguments and body:

new Function ([arg1[, arg2[, ...argN]],] functionBody)

To get a method params and body we need to call inherited toString() method on method:

dog.woof.toString()

We can store function declaration in string and we can create function from string. Next steps are: implement conventional functions storage as object properties, implement restoring saved functions from JSON string.

Dirty working implementation example in the snippet below.

Why you should not implement this?

Security risk. Somebody could hack the serialised functions with any arbitrary code.

Bad code design. Having objects without predefined constructors leads to maintenance hell, because you can't you javascript duck typing to make assumptions about object behavior.

Versioning. If you update you code, you can't be sure about which version of objects stored on clients.

let dog = { 
   name : '',
   woof : function() {
     console.log('woof woof, I am ' + this.name);
   }
}
dog.name = 'Lovely';
dog.woof()

let storedDog = saveWithFuncToJSON(dog)
console.log('stored:', storedDog)

let restoredDog = restoreWithFuncFromJSON(storedDog)
restoredDog.woof()

console.log("Don't use this approach in production!")

// Code below is created only for fun,
// if you need this code in production,
// then your code smells

// Return JSON string
function saveWithFuncToJSON(object) {
  // Use Object.assign() just for simplicity,
  // something more solid needed for real object copying
  let preparedObject = Object.assign({}, object)

  for (let prop in preparedObject) {
    if (typeof(preparedObject[prop]) !== 'function') continue
	
    // Different platforms constructing function string in different ways
    // so you'll have to put a lot of efforts to make it work stable
    let funcStr = preparedObject[prop].toString()

    let startParams = funcStr.indexOf('(') + 1
    let endParams = funcStr.indexOf(')')
    let hasParams = (endParams - startParams)

    let funcParams = !hasParams ? [] : funcStr.slice(
      funcStr.indexOf('(') + 1,
      funcStr.indexOf('\n')
    ).split(',')

    let funcBody = funcStr.slice(
      funcStr.indexOf('{') + 1,
      funcStr.lastIndexOf('}')
    )
	
    // This is the most interesting part
    // We will store function as a string like freezing humans
    preparedObject[`__${prop}Func`] = {
      params: funcParams,
      body: funcBody
    }
  }

  return JSON.stringify(preparedObject)
}
      
function restoreWithFuncFromJSON(jsonSting) {
  let object = JSON.parse(jsonSting)

  for (let prop in object) {
    // Functions to be restored should be named differently
    let shouldConvertToFunc = prop.startsWith('__') && prop.endsWith('Func')
    if (!shouldConvertToFunc) continue
  
    let funcName = prop.slice(2, -4)
    let funcData = object[prop]
    let contructorArgsArray = funcData.params.concat([funcData.body])

    // Function() does all work for us
    object[funcName] = Function.apply(null, contructorArgsArray)
    delete object[prop]
  }

  return object;
}

Upvotes: 2

Brad
Brad

Reputation: 163272

LocalStorage only supports strings. You can store objects with JSON-encoding, but that won't work for your functions.

JSON only supports numbers, strings, booleans, arrays, and basic object structures.

Upvotes: 2

Related Questions