save a function in localstorage - javascript

I'm working with some objects that contains a number of data to be displayed and manipulated from browser, and I want to save it in local storage.
In order to save objects I used JSON.stringify() so everything become text, and it works well
{
"bindingRef": [],
"primo": {
"name": "primo",
"modifiable": true,
"binded": false,
"isInteger": false,
"label": "Numero di Primi"
},
"secondo": {
"name": "secondo",
"modifiable": true,
"binded": false,
"isInteger": false,
"label": "Numero di Secondi"
}
}
Now I'm trying to save also a function by converting it to a string and then saveing it
JSON.stringify(myFunction.toString());
but the output is this
"savedFunction": "function () {\n\t\t\t\tvar tot = menu.primo.get() * 6 + menu.secondo.get() * 8 + menu.dolce.get() * 4;\n\t\t\t\tif (menu.sconto.get()) {\n\t\t\t\t\treturn tot * 0.90;\n\t\t\t\t} else {\n\t\t\t\t\treturn tot;\n\t\t\t\t}\n\t\t\t}"
Is it the correct way to save a function in local storage or is there a better way to do this? If this is the correct way, is there a way to simply remove any tabulation/indentation character or should I manipulate the string, for example using some regexp function?

Function in JS as in many functional languages are closures: they wrap inside them the content of the environment scope at the moment of definition, including ephemeral data like db or file handles.
It's not a good idea because this can lead to problems due to JSON deserialization behaviour, so you have to check what in the function is wrapped and what is self-defined.
See also this SO thread for further information.

You can put the function in an object and use the functions storage.set and storage.get that I created instead of localStorage.set and localStorage.get (localStorage doesn't allow functions to be added, unlike JSON).
storage.set will stringify an object including functions before using localStorage.setItem().
storage.get will parse an object including functions after using localStorage.getItem().
I modified the functions JSON.stringify and JSON.parse to be able to handle functions, so you can use it in other parts of your code without changing function names. I appended a 2 to the original functions, so I can use them within the updated functions.
JSON.stringify2 = JSON.stringify;
JSON.parse2 = JSON.parse;
JSON.stringify = function(value) {
return JSON.stringify2(value, function(key, val) {
return (typeof val === 'function') ? val.toString().replace(/\t|\n/g, '') : val;
});
}
JSON.parse = function(value) {
return JSON.parse2(value, function(key, val) {
if (typeof val === 'string') {
var regex = /^function\s*\([^()]*\)\s*{.*}$/;
if (regex.exec(val) !== null)
return eval('key = ' + val);
else
return val;
} else
return val;
});
}
var storage = {};
storage.set = function(key, value) {
if (typeof value === 'object')
value = JSON.stringify(value);
localStorage.setItem(key, value);
}
storage.get = function(key) {
var value = localStorage.getItem(key);
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}

Related

WeakMap polyfill throwing error when trying to define property on inextensible object

The WeakMap polyfill is throwing an error when trying to define property on inextensible object.
Those are in the middle of a bunch of node, javascript code and libraries so I couldn't actually point out where causes the issue. There are many other libraries which also have their own polyfill. It's difficult to debug in which library causes the error. And, the errors are ONLY on IE10.
To get rid of it, I added a checker before defining a property like (line 26 in the above file):
if (entry && entry[0] === key) {
entry[1] = value;
}
else if (Object.isExtensible(key)) {
defineProperty(key, this.name, {
value: [ key, value ],
writable: true
});
}
My question is, Is it safe/right to fix it as in my code above? If not, how should I fix my problem?
As best I can tell that weakMap polyfill is ONLY designed to work with extensible objects as keys. It simply won't work with a non-extensible object.
Your modification has made it so it doesn't throw an exception, but then the non-extensible item will not be in the weakMap either. So, your fix isn't really a fix. That particular polyfill would have to be rewritten to handle non-extensible keys. It would not be a simple fix as it would need a conceptually different approach.
There are a number of other polyfills that take a different approach. I have not researched which ones might be better in this regard. I suspect it's a little bit of conundrum between actually being "weak" (e.g. allows garbage collection) vs. can handle a non-extensible object. The fundamental issue is that if you are going to be weak, then you can't store a reference to the object itself. So, you need to store a reference to some string representation of the object. Well, JS objects don't have a built-in guaranteed unique string representation. So, the usually fix is to coin one using some sort of counter and store it on the object as a property and then you store that string representation in your map. But, if the object isn't extensible, then you can't do that either. So, you're stuck storing the actual object reference in your map, but then it's not really "weak" any more. You can see how you're kind of stuck.
I think this is one case where a polyfill just can't quite live up to the real thing. Different polyfills will have different tradeoffs in this regard. You picked one that is truly weak, but requires the objects to be extensible so a property can be added.
As in my question, I want a solution which does not have huge impact on other functionality or plugins, so here is my fix for this issue. The fix is based on the answer of #jfriend00 above and reference from other implementation in the internet.
var defineProperty = Object.defineProperty;
var counter = Date.now() % 1e9;
var FrozenStore = function() {
this.a = [];
};
var findFrozen = function(store, key){
return store.a.forEach(function(it){
if (it[0] === key) {
return it;
}
});
};
var findIndexFrozen = function(store, key){
return store.a.forEach(function(it, id){
if (it[0] === key) {
return id;
}
});
};
FrozenStore.prototype = {
get: function(key){
var entry = findFrozen(this, key);
if (entry) return entry[1];
},
has: function(key){
return !!findFrozen(this, key);
},
set: function(key, value){
var entry = findFrozen(this, key);
if (entry) entry[1] = value;
else this.a.push([key, value]);
},
"delete": function(key){
var index = findIndexFrozen(this, key);
if (~index) this.a.splice(index, 1);
return !!~index;
}
};
var WeakMap = function() {
this.name = "__st" + (Math.random() * 1e9 >>> 0) + (counter++ + "__");
};
var frozenStore = function(that){
return that._l || (that._l = new FrozenStore);
};
WeakMap.prototype = {
set: function(key, value) {
var entry = key[this.name];
if (entry && entry[0] === key) {
entry[1] = value;
} else {
if (!Object.isExtensible(key)) {
frozenStore(this).set(key, value);
} else {
defineProperty(key, this.name, {
value: [ key, value ],
writable: true
});
}
}
return this;
},
get: function(key) {
var entry;
if ((entry = key[this.name]) && entry[0] === key) {
return entry[1];
} else if (!Object.isExtensible(key)) {
frozenStore(this).get(key);
} else {
return undefined;
}
},
"delete": function(key) {
var entry = key[this.name];
if (!entry || entry[0] !== key) return false;
if (!Object.isExtensible(key)) frozenStore(this)['delete'](key);
entry[0] = entry[1] = undefined;
return true;
},
has: function(key) {
var entry = key[this.name];
if (!entry) return false;
if(!Object.isExtensible(key)) return frozenStore(this).has(key);
return entry[0] === key;
}
};
window.WeakMap = WeakMap;
This introduces the FrozenStore which will manage all non-extensible keys being added to WeakMap. I'm not sure whether it breaks the concept of WeakMap or not but it does rescue me from the issue.

NodeJS - Parse JSON (only strings or numbers)

I have got a little dilema what to do (What should I do). I've got in my app several places where I am using AJAX to transfer a JSON data. So for example receiving a data from the registration form:
try {
var data = JSON.parse(json);
}
catch(e) {
// ...
}
var fields = {
firstName: data.firstName || "",
lastName: data.lastName || "",
...
};
Then I need to do something with these fields. Let's say:
if (fields.firstName) {
// OK save it to the DB
}
if(fields.lastName.xxx()) { // xxx() is a method that belongs to JS String Object
// Do something...
}
The problem is what if fields.firstName or fields.lastName is {} / []? That may happen if somebody sent a modified JSON with a wrong data types, the whole app will then crash since the method xxx is missing.
Do I have to check the type of every field whether it is really a string, a number, an array... or is there some another maybe nicer way how to do this?
Unfortunately that's a problem you'll have to face anytime using a dynamic language like JavaScript.
One possible solution is not using String methods, but creating a function that will check it's arguments first:
// original, with string methods
if (text instanceof String && text.trim().length === 0) { ... }
// alternative, define function first
var isEmptyString = function(text) {
if (!(text instanceof String)) throw new Error("Argument must be a String");
return text.trim().length === 0;
}
// use it in your code
if (isEmptyString(text)) { ... }
That can lead to cleaner code.
Just be sure not to define those functions in a global scope, when you have to use them in a Browser. Within the node.js modules that should not be a problem.
You'll have to make a little validator. Like that:
var format = {
foo: "string",
bar: "string"
};
var data = {
foo: "bar",
bar: []
}
var validate = function (data, format) {
for(var d in data) {
if(typeof data[d] !== format[d])
return false;
}
return true;
}
console.log(validate(data, format));
You can also remove invalid data like this:
var format = {
foo: "string",
bar: "string"
};
var data = {
foo: "bar",
bar: []
}
var validate = function (data, format) {
for(var d in data) {
if(typeof data[d] !== format[d])
delete data[d];
}
return data;
}
console.log(validate(data, format));
In addition to what Tharabas suggests, you could take advantage of the 2nd argument of JSON.parse for doing your sanity check:
JSON.parse(data, function(key, value) {
return key === 'firstName' ? cleanup(value) : '';
});
See this page for further information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

Javascript, passing a function in an object literal and is it callable?

always in the process of learning Javascript and modifying a cool autocomplete library, i am now in front of this :
i need to check if something passed in an object literal is a variable/field (that is to be considered as a simple value) or is something that can be called.
(as MY autocomplete depend on many input fields, i need to "value" the right things, just before the Ajax.Request) so that this declaration (see the 'extra' parts...)
myAutoComplete = new Autocomplete('query', {
serviceUrl:'autoComplete.rails',
minChars:3,
maxHeight:400,
width:300,
deferRequestBy:100,
// callback function:
onSelect: function(value, data){
alert('You selected: ' + value + ', ' + data);
}
// the lines below are the extra part that i add to the library
// an optional parameter, that will handle others arguments to pass
// if needed, these must be value-ed just before the Ajax Request...
, extraParametersForAjaxRequest : {
myExtraID : function() { return document.getElementById('myExtraID').value; }
}
see the "1 // here i'm lost..." below, and instead of 1 => i would like to check, if extraParametersForAjaxRequest[x] is callable or not, and call it if so, keeping only its value if not. So that, i get the right value of my other inputs... while keeping a really generic approach and clean modification of this library...
{
var ajaxOptions = {
parameters: { query: this.currentValue , },
onComplete: this.processResponse.bind(this),
method: 'get'
};
if (this.options.hasOwnProperty('extraParametersForAjaxRequest'))
{
for (var x in this.options.extraParametersForAjaxRequest)
{
ajaxOptions.parameters[x] = 1 // here i'm lost...
}
}
new Ajax.Request(this.serviceUrl, ajaxOptions );
You can do a typeof to see if the parameter is a function, and call it if it is.
var value;
for (var x in this.options.extraParametersForAjaxRequest)
{
value = this.options.extraParametersForAjaxRequest[x];
if (typeof(value) == 'function') {
ajaxOptions.parameters[x] = value();
}
else {
ajaxOptions.parameters[x] = value;
}
}
if (typeof this.options.extraParametersForAjaxRequest[x]==='function') {
}
You should also do this:
if (this.options.extraParametersForAjaxRequest.hasOwnProperty(x) {
if (typeof this.options.extraParametersForAjaxRequest[x]==='function') {
}
}
when iterating through properties of objects, otherwise you can end up looking at prototype members too.
Another suggestion is to make this more readable with an alias for the thing you're working with. So the ultimate would be:
var opts = this.options.extraParametersForAjaxRequest;
// don't need to check for existence of property explicitly with hasOwnProperty
// just try to access it, and check to see if the result is
// truthy. if extraParametersForAjaxRequest isn't there, no error will
// result and "opts" will just be undefined
if (opts)
{
for (var x in opts) {
if (opts.hasOwnProperty(x) && typeof opts[x]==='function') {
}
}
}

Is there any possibility to have JSON.stringify preserve functions?

Take this object:
x = {
"key1": "xxx",
"key2": function(){return this.key1}
}
If I do this:
y = JSON.parse( JSON.stringify(x) );
Then y will return { "key1": "xxx" }. Is there anything one could do to transfer functions via stringify? Creating an object with attached functions is possible with the "ye goode olde eval()", but whats with packing it?
json-stringify-function is a similar post to this one.
A snippet discovered via that post may be useful to anyone stumbling across this answer. It works by making use of the replacer parameter in JSON.stringify and the reviver parameter in JSON.parse.
More specifically, when a value happens to be of type function, .toString() is called on it via the replacer. When it comes time to parse, eval() is performed via the reviver when a function is present in string form.
var JSONfn;
if (!JSONfn) {
JSONfn = {};
}
(function () {
JSONfn.stringify = function(obj) {
return JSON.stringify(obj,function(key, value){
return (typeof value === 'function' ) ? value.toString() : value;
});
}
JSONfn.parse = function(str) {
return JSON.parse(str,function(key, value){
if(typeof value != 'string') return value;
return ( value.substring(0,8) == 'function') ? eval('('+value+')') : value;
});
}
}());
Code Snippet taken from Vadim Kiryukhin's JSONfn.js or see documentation at Home Page
I've had a similar requirement lately. To be clear, the output looks like JSON but in fact is just javascript.
JSON.stringify works well in most cases, but "fails" with functions.
I got it working with a few tricks:
make use of replacer (2nd parameter of JSON.stringify())
use func.toString() to get the JS code for a function
remember which functions have been stringified and replace them directly in the result
And here's how it looks like:
// our source data
const source = {
"aaa": 123,
"bbb": function (c) {
// do something
return c + 1;
}
};
// keep a list of serialized functions
const functions = [];
// json replacer - returns a placeholder for functions
const jsonReplacer = function (key, val) {
if (typeof val === 'function') {
functions.push(val.toString());
return "{func_" + (functions.length - 1) + "}";
}
return val;
};
// regex replacer - replaces placeholders with functions
const funcReplacer = function (match, id) {
return functions[id];
};
const result = JSON
.stringify(source, jsonReplacer) // generate json with placeholders
.replace(/"\{func_(\d+)\}"/g, funcReplacer); // replace placeholders with functions
// show the result
document.body.innerText = result;
body { white-space: pre-wrap; font-family: monospace; }
Important: Be careful about the placeholder format - make sure it's not too generic. If you change it, also change the regex as applicable.
Technically this is not JSON, I can also hardly imagine why would you want to do this, but try the following hack:
x.key2 = x.key2.toString();
JSON.stringify(x) //"{"key1":"xxx","key2":"function (){return this.key1}"}"
Of course the first line can be automated by iterating recursively over the object. Reverse operation is harder - function is only a string, eval will work, but you have to guess whether a given key contains a stringified function code or not.
You can't pack functions since the data they close over is not visible to any serializer.
Even Mozilla's uneval cannot pack closures properly.
Your best bet, is to use a reviver and a replacer.
https://yuilibrary.com/yui/docs/json/json-freeze-thaw.html
The reviver function passed to JSON.parse is applied to all key:value pairs in the raw parsed object from the deepest keys to the highest level. In our case, this means that the name and discovered properties will be passed through the reviver, and then the object containing those keys will be passed through.
This is what I did https://gist.github.com/Lepozepo/3275d686bc56e4fb5d11d27ef330a8ed
function stringifyWithFunctions(object) {
return JSON.stringify(object, (key, val) => {
if (typeof val === 'function') {
return `(${val})`; // make it a string, surround it by parenthesis to ensure we can revive it as an anonymous function
}
return val;
});
};
function parseWithFunctions(obj) {
return JSON.parse(obj, (k, v) => {
if (typeof v === 'string' && v.indexOf('function') >= 0) {
return eval(v);
}
return v;
});
};
The naughty but effective way would be to simply:
Function.prototype.toJSON = function() { return this.toString(); }
Though your real problem (aside from modifying the prototype of Function) would be deserialization without the use of eval.
I have come up with this solution which will take care of conversion of functions (no eval). All you have to do is put this code before you use JSON methods. Usage is exactly the same but right now it takes only one param value to convert to a JSON string, so if you pass remaning replacer and space params, they will be ignored.
void function () {
window.JSON = Object.create(JSON)
JSON.stringify = function (obj) {
return JSON.__proto__.stringify(obj, function (key, value) {
if (typeof value === 'function') {
return value.toString()
}
return value
})
}
JSON.parse = function (obj) {
return JSON.__proto__.parse(obj, function (key, value) {
if (typeof value === 'string' && value.slice(0, 8) == 'function') {
return Function('return ' + value)()
}
return value
})
}
}()
// YOUR CODE GOES BELOW HERE
x = {
"key1": "xxx",
"key2": function(){return this.key1}
}
const y = JSON.parse(JSON.stringify(x))
console.log(y.key2())
It is entirely possible to create functions from string without eval()
var obj = {a:function(a,b){
return a+b;
}};
var serialized = JSON.stringify(obj, function(k,v){
//special treatment for function types
if(typeof v === "function")
return v.toString();//we save the function as string
return v;
});
/*output:
"{"a":"function (a,b){\n return a+b;\n }"}"
*/
now some magic to turn string into function with this function
var compileFunction = function(str){
//find parameters
var pstart = str.indexOf('('), pend = str.indexOf(')');
var params = str.substring(pstart+1, pend);
params = params.trim();
//find function body
var bstart = str.indexOf('{'), bend = str.lastIndexOf('}');
var str = str.substring(bstart+1, bend);
return Function(params, str);
}
now use JSON.parse with reviver
var revivedObj = JSON.parse(serialized, function(k,v){
// there is probably a better way to determ if a value is a function string
if(typeof v === "string" && v.indexOf("function") !== -1)
return compileFunction(v);
return v;
});
//output:
revivedObj.a
function anonymous(a,b
/**/) {
return a+b;
}
revivedObj.a(1,2)
3
To my knowledge, there are no serialization libraries that persist functions - in any language. Serialization is what one does to preserve data. Compilation is what one does to preserve functions.
It seems that people landing here are dealing with structures that would be valid JSON if not for the fact that they contain functions. So how do we handle stringifying these structures?
I ran into the problem while writing a script to modify RequireJS configurations. This is how I did it. First, there's a bit of code earlier that makes sure that the placeholder used internally (">>>F<<<") does not show up as a value in the RequireJS configuration. Very unlikely to happen but better safe than sorry. The input configuration is read as a JavaScript Object, which may contain arrays, atomic values, other Objects and functions. It would be straightforwardly stringifiable as JSON if functions were not present. This configuration is the config object in the code that follows:
// Holds functions we encounter.
var functions = [];
var placeholder = ">>>F<<<";
// This handler just records a function object in `functions` and returns the
// placeholder as the value to insert into the JSON structure.
function handler(key, value) {
if (value instanceof Function) {
functions.push(value);
return placeholder;
}
return value;
}
// We stringify, using our custom handler.
var pre = JSON.stringify(config, handler, 4);
// Then we replace the placeholders in order they were encountered, with
// the functions we've recorded.
var post = pre.replace(new RegExp('"' + placeholder + '"', 'g'),
functions.shift.bind(functions));
The post variable contains the final value. This code relies on the fact that the order in which handler is called is the same as the order of the various pieces of data in the final JSON. I've checked the ECMAScript 5th edition, which defines the stringification algorithm and cannot find a case where there would be an ordering problem. If this algorithm were to change in a future edition the fix would be to use unique placholders for function and use these to refer back to the functions which would be stored in an associative array mapping unique placeholders to functions.

How do you know if an object is JSON in javascript? [duplicate]

This question already has answers here:
How to check if it's a string or json [duplicate]
(5 answers)
Closed 8 years ago.
How do I know if a variable is JSON or if it is something else? Is there a JQuery function or something I can use to figure this out?
Based on your comments, it sounds like you don't want to know whether a string is valid JSON, but rather whether an object could be successfully encoded as JSON (e.g. doesn't contain any Date objects, instances of user-defined classes, etc.).
There are two approaches here: try to analyze the object and its "children" (watch out for recursive objects) or suck-it-and-see. If you have a JSON encoder on hand (JSON.stringify in recent browsers or a plugin such as jquery-json), the latter is probably the simpler and more robust approach:
function canJSON(value) {
try {
JSON.stringify(value);
return true;
} catch (ex) {
return false;
}
}
Analyzing an object directly requires that you be able to tell whether it is a "plain" object (i.e. created using an object literal or new Object()), which in turn requires you be able to get its prototype, which isn't always straightforward. I've found the following code to work in IE7, FF3, Opera 10, Safari 4, and Chrome (and quite likely other versions of those browsers, which I simply haven't tested).
var getPrototypeOf;
if (Object.getPrototypeOf) {
getPrototypeOf = Object.getPrototypeOf;
} else if (typeof ({}).__proto__ === "object") {
getPrototypeOf = function(object) {
return object.__proto__;
}
} else {
getPrototypeOf = function(object) {
var constructor = object.constructor;
if (Object.prototype.hasOwnProperty.call(object, "constructor")) {
var oldConstructor = constructor; // save modified value
if (!(delete object.constructor)) { // attempt to "unmask" real constructor
return null; // no mask
}
constructor = object.constructor; // obtain reference to real constructor
object.constructor = oldConstructor; // restore modified value
}
return constructor ? constructor.prototype : null;
}
}
// jQuery.isPlainObject() returns false in IE for (new Object())
function isPlainObject(value) {
if (typeof value !== "object" || value === null) {
return false;
}
var proto = getPrototypeOf(value);
// the prototype of simple objects is an object whose prototype is null
return proto !== null && getPrototypeOf(proto) === null;
}
var serializablePrimitives = { "boolean" : true, "number" : true, "string" : true }
function isSerializable(value) {
if (serializablePrimitives[typeof value] || value === null) {
return true;
}
if (value instanceof Array) {
var length = value.length;
for (var i = 0; i < length; i++) {
if (!isSerializable(value[i])) {
return false;
}
}
return true;
}
if (isPlainObject(value)) {
for (var key in value) {
if (!isSerializable(value[key])) {
return false;
}
}
return true;
}
return false;
}
So yeah… I'd recommend the try/catch approach. ;-)
function isJSON(data) {
var isJson = false
try {
// this works with JSON string and JSON object, not sure about others
var json = $.parseJSON(data);
isJson = typeof json === 'object' ;
} catch (ex) {
console.error('data is not JSON');
}
return isJson;
}
You can use [json2.js] from Douglas Crockfords JSON Github site to parse it.
JSON is an encoding method not an internal variable type.
You might load in some text that is JSON encoded that javascript then uses to populate your variables. Or you might export a string that contains a JSON encoded dataset.
The only testing I've done is to check for a string, with and without double quotes, and this passes that test. http://forum.jquery.com/topic/isjson-str
Edit:
It looks like the latest Prototype has a new implementation similar to the one linked above. http://prototypejs.org/assets/2010/10/12/prototype.js
function isJSON() {
var str = this;
if (str.blank()) return false;
str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '#');
str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
return (/^[\],:{}\s]*$/).test(str);
}

Categories

Resources