Suppose you have the following:
function myfunc() {
// JS code
}
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": myfunc }';
The problem: how do you process the args
variable before submitting it to the JSON parser so that myfunc
is replaced with the result of myfunc.toString()
(that is, the body of the function)? The proposed solution should work on arbitrary functions and such quasi-JSON strings.
Suppose you have the following:
function myfunc() {
// JS code
}
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": myfunc }';
The problem: how do you process the args
variable before submitting it to the JSON parser so that myfunc
is replaced with the result of myfunc.toString()
(that is, the body of the function)? The proposed solution should work on arbitrary functions and such quasi-JSON strings.
- Interesting... but I cannot imagine that there is a way to actually get the code in the body of the function as string. But I can be wrong and if there is way, that would be awesome :) – Felix Kling Commented Nov 27, 2010 at 20:36
- Huh? There is such a way: that myfunc.toString() call didn't e out of nowhere :P developer.mozilla/en/JavaScript/Reference/Global_Objects/… The problem is not getting the function's code, but rather elegantly substituting the fields' value with it without having to mutilate the JSON parser. – dpq Commented Nov 27, 2010 at 20:45
-
I suppose (assuming
toString
is cross-platform) that you could parse the args string into an object, iterate through every property in the object (recursively), and if the value of any property is defined and is a function, replace it with that function's code, then convert that object back to JSON. The question then bees, "How do I detect if a JS object is a function?" – Cameron Commented Nov 27, 2010 at 20:52 -
Using
typeof field == "function"
. What you propose would generate a double overhead (and even more, if you pare against native JSON parsers), although it will certainly work... Can you advice any improvements? – dpq Commented Nov 27, 2010 at 21:02 -
@David: I realize the overhead is large (and no immediate improvements e to mind), but I can't think of any other way. I wouldn't worry about performance until it bees an issue. And I didn't know about
typeof field == "function"
-- cool! – Cameron Commented Nov 27, 2010 at 21:14
2 Answers
Reset to default 7We use the optional second replacer argument to JSON.stringify
to preprocess key/values, emitting functions in stringified form:
function stringify_with_fns(obj) {
return JSON.stringify(obj, function(key, value) {
return typeof value === "function" ? value.toString() : value;
});
}
Then to turn the stringified functions back into real functions on the way out, we use the optional second reviver parameter to JSON.parse
, as in
function parse_with_fns(json) {
return JSON.parse(json, function(key, value) {
if (looks_like_a_function_string(value)) {
return make_function_from_string(value);
} else {
return value;
}
});
}
looks_like_a_function_string
is just a regexp, and the first cut at make_function_from_string
can use eval
:
function looks_like_a_function_string(value) {
return /^function.*?\(.*?\)\s*\{.*\}$/.test(value);
}
function make_function_from_string(value) {
return eval(value);
}
To avoid eval
, we can pick apart the function string to find its arguments and body, so we can pass them to new Function
:
function make_function_from_string(value) {
var args = value
.replace(/\/\/.*$|\/\*[\s\S]*?\*\//mg, '') //strip ments
.match(/\(.*?\)/m)[0] //find argument list
.replace(/^\(|\)$/, '') //remove parens
.match(/[^\s(),]+/g) || [], //find arguments
body = value.match(/\{(.*)\}/)[1] //extract body between curlies
return Function.apply(0, args.concat(body);
}
Testing:
x = parse_with_fns(stringify_with_fns({a: function() {var x=1;}}))
x.a
> function anonymous() {var x=1;}
Note however that since functions created by new Function
are all in the global scope, they will lose their enclosing scope and closures.
The only remaining question is whether this is useful. I suppose it might be for small utility functions.
Extending the concept to serializing/deserializing regexps or date objects is left as an exercise.
Like this? I had to change it because what you have isn't a valid JSON string since myfunc
doesn't have double quotes. So I added those.
This parses it, gets the value of funcfield
, finds the function (although it assumes it is in the global context), calls toString()
updating the value of funcfield
, the re-stringifies it as JSON.
Example: http://jsfiddle/patrick_dw/QUR6Z/
var myfunc = function() {
alert('hi');
};
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": "myfunc" }';
var parsed = JSON.parse( args );
parsed.funcfield = window[parsed.funcfield].toString();
var stringified = JSON.stringify( parsed );
alert(stringified);
Was this what you meant?
EDIT:
I guess you could use the current context, as long as that context is contained within the context that owns the function.
parsed.funcfield = this[parsed.funcfield].toString();
Or if you can't double quote the function name, you could use eval
instead if you're certain the data is safe.
Example: http://jsfiddle/patrick_dw/QUR6Z/1/
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": myfunc }';
window.eval( 'var parsed = ' + args );
parsed.funcfield = parsed.funcfield.toString();
var stringified = JSON.stringify( parsed );
alert(stringified);