68

For example, say I have a function defined as follows:

function foo() {
  return "Hello, serialized world!";
}

I want to be able to serialize that function and store it using localStorage. How can I go about doing that?

10
  • 7
    why do you want to serialize a function? Commented Sep 13, 2011 at 0:53
  • 7
    What if the function being serialised references variables/functions in the containing scope? If you deserialise it in the wrong place it won't work... Commented Sep 13, 2011 at 1:07
  • 6
    Can't speak to Akash's use, but I want to serialize a function to store it as a validation function in CouchDB. The variables passed in and restricted scope are well defined. Commented Aug 14, 2013 at 16:39
  • 62
    @DanielA.White It really irritates me when this is the first response to somebody's question! Commented Mar 13, 2014 at 5:30
  • 25
    People blow off questions they consider bad practice all too often on StackOverflow instead of just objectively answering the question and then adding a word of advice afterwards. The latter is much more helpful to beginners. Commented Jul 13, 2016 at 17:34

5 Answers 5

44

Most browsers (Chrome, Safari, Firefox, possibly others) return the definition of functions from the .toString() method:

> function foo() { return 42; }
> foo.toString()
"function foo() { return 42; }"

Just be careful because native functions won't serialize properly. For example:

> alert.toString()
"function alert() { [native code] }"
Sign up to request clarification or add additional context in comments.

6 Comments

How would you get the function back from the string?
@David Wolever - the ES 3 and 5 specs say that Function.prototype.toString returns "[an] implementation-dependent representation of the function" that "has the syntax of a FunctionDeclaration" (§15.3.4.2). Probably worth noting that there is nothing to say that it must be the literal code of the function, it could just be function foo(){/* returns 42 */}.
You might want to note that this won't easily work if the function is a closure.
@AkashGupta use Function's constructor with return statement
The function constructor requires the arguments to be passed explicitly. Instead using eval(...) restores them from the toString representation and works with lambdas as well. Of course it might not be a bulletproof solution for a generic serializer/deserializer but it worked for me much better than the examples in the other answers.
|
41
function foo() {
  alert('native function');
  return 'Hello, serialised world!';
}

Serializing

var storedFunction = foo.toString();

Deserializing

var actualFunction = new Function('return ' + foo.toString())()

Explanation

foo.toString() will be string version of the function foo

"function foo() { ... return 'Hello, serialised world!';}"

But new Function takes the body of a function and not the function itself.

See MDN: Function

So we can create a function that returns us back this function and assign it to some variable.

"return function foo() { ... return 'Hello, serialised world!';}"

So now when we pass this string to the constructor we get a function and we immediately execute it to get back our original function. :)

2 Comments

or just use eval(storedFunction), which will directly return the function
22

I made this answer to address some pretty big flaws with the existing answers: .toString()/eval() and new Function() on their own wont work at all if your function uses this or named arguments (function (named, arg) {}), respectively.

Using toJSON() below, all you need to do is call JSON.stringify() as usual on the function, and use Function.deserialise when parse()ing.

The following wont work for concise functions (hello => 'there'), but for standard ES5 fat functions it'll return it as it was defined, closures notwithstanding of course. My other answer will work with all that ES6 goodness.


Function.prototype.toJSON = function() {
    var parts = this
        .toString()
        .match(/^\s*function[^(]*\(([^)]*)\)\s*{(.*)}\s*$/)
    ;
    if (parts == null)
        throw 'Function form not supported';

    return [
        'window.Function',
        parts[1].trim().split(/\s*,\s*/),
        parts[2]
    ];
};
Function.deserialise = function(key, data) {
    return (data instanceof Array && data[0] == 'window.Function') ?
        new (Function.bind.apply(Function, [Function].concat(data[1], [data[2]]))) :
        data
    ;
};

Take a look at the DEMO

At it's simplest:

var test = function(where) { return 'hello ' + where; };
test = JSON.parse(JSON.stringify(test), Function.deserialise);
console.log(test('there'));
//prints 'hello there'

More usefully, you can serialise entire objects containing functions and pull them back out:

test = {
  a : 2,
  run : function(x, y, z) { return this.a + x + y + z; }
};
var serialised = JSON.stringify(test);
console.log(serialised);
console.log(typeof serialised);

var tester = JSON.parse(serialised, Function.deserialise);
console.log(tester.run(3, 4, 5));

Outputs:

{"a":2,"run":["window.Function",["x","y","z"]," return this.a + x + y + z; "]}
string
14

I didn't test older IE's, but it works on IE11, FF, Chrome, Edge.

NB, the name of the function is lost, if you use that property then there's nothing you can do, really.
You can change it to not use prototype easily, but that's for you to do if that's what you need.

1 Comment

Comments are not for extended discussion; this conversation has been moved to chat.
14

If you needed a way to serialise Arrow Functions in ES6 I have written a serialiser that makes everything work.

All you need to do is call JSON.stringify() as usual on the function or object containing the function, and call Function.deserialise on the other side for the magic to work.

Obviously you shouldn't expect closures to work, it is serialisation after all, but defaults, destructuring, this, arguments, class member functions, it'll all be preserved.
If you're only using ES5 notations please just use my other answer. This one really is above and beyond


Here's the demonstration

Working in Chrome/Firefox/Edge.
Bellow is the output from the demo; a few functions, the serialised string, then calling the new function created after deserialisation.

test = {
    //make the function
    run : function name(x, y, z) { return this.a + x + y + z; },
    a : 2
};
//serialise it, see what it looks like
test = JSON.stringify(test) //{"run":["window.Function",["x","y","z"],"return this.a + x + y + z;"],"a":2}
test = JSON.parse(test, Function.deserialise)
//see if `this` worked, should be 2+3+4+5 : 14
test.run(3, 4, 5) //14

test = () => 7
test = JSON.stringify(test) //["window.Function",[""],"return 7"]
JSON.parse(test, Function.deserialise)() //7

test = material => material.length
test = JSON.stringify(test) //["window.Function",["material"],"return material.length"]
JSON.parse(test, Function.deserialise)([1, 2, 3]) //3

test = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c
test = JSON.stringify(test) //["window.Function",["[a, b] = [1, 2]","{ x: c } = { x: a + b }"],"return a + b + c"]
JSON.parse(test, Function.deserialise)([3, 4]) //14

class Bob {
    constructor(bob) { this.bob = bob; }
    //a fat function with no `function` keyword!!
    test() { return this.bob; }
    toJSON() { return {bob:this.bob, test:this.test} }
}
test = new Bob(7);
test.test(); //7
test = JSON.stringify(test); //{"bob":7,"test":["window.Function",[""],"return this.bob;"]}
test = JSON.parse(test, Function.deserialise);
test.test(); //7

And finally, the magic

Function.deserialise = function(key, data) {
    return (data instanceof Array && data[0] == 'window.Function') ?
        new (Function.bind.apply(Function, [Function].concat(data[1], [data[2]]))) :
        data
    ;
};
Function.prototype.toJSON = function() {
    var whitespace = /\s/;
    var pair = /\(\)|\[\]|\{\}/;

    var args = new Array();
    var string = this.toString();

    var fat = (new RegExp(
        '^\s*(' +
        ((this.name) ? this.name + '|' : '') +
        'function' +
        ')[^)]*\\('
    )).test(string);

    var state = 'start';
    var depth = new Array(); 
    var tmp;

    for (var index = 0; index < string.length; ++index) {
        var ch = string[index];

        switch (state) {
        case 'start':
            if (whitespace.test(ch) || (fat && ch != '('))
                continue;

            if (ch == '(') {
                state = 'arg';
                tmp = index + 1;
            }
            else {
                state = 'singleArg';
                tmp = index;
            }
            break;

        case 'arg':
        case 'singleArg':
            var escaped = depth.length > 0 && depth[depth.length - 1] == '\\';
            if (escaped) {
                depth.pop();
                continue;
            }
            if (whitespace.test(ch))
                continue;

            switch (ch) {
            case '\\':
                depth.push(ch);
                break;

            case ']':
            case '}':
            case ')':
                if (depth.length > 0) {
                    if (pair.test(depth[depth.length - 1] + ch))
                        depth.pop();
                    continue;
                }
                if (state == 'singleArg')
                    throw '';
                args.push(string.substring(tmp, index).trim());
                state = (fat) ? 'body' : 'arrow';
                break;

            case ',':
                if (depth.length > 0)
                    continue;
                if (state == 'singleArg')
                    throw '';
                args.push(string.substring(tmp, index).trim());
                tmp = index + 1;
                break;

            case '>':
                if (depth.length > 0)
                    continue;
                if (string[index - 1] != '=')
                    continue;
                if (state == 'arg')
                    throw '';
                args.push(string.substring(tmp, index - 1).trim());
                state = 'body';
                break;

            case '{':
            case '[':
            case '(':
                if (
                    depth.length < 1 ||
                    !(depth[depth.length - 1] == '"' || depth[depth.length - 1] == '\'')
                )
                    depth.push(ch);
                break;

            case '"':
                if (depth.length < 1)
                    depth.push(ch);
                else if (depth[depth.length - 1] == '"')
                    depth.pop();
                break;
            case '\'':
                if (depth.length < 1)
                    depth.push(ch);
                else if (depth[depth.length - 1] == '\'')
                    depth.pop();
                break;
            }
            break;

        case 'arrow':
            if (whitespace.test(ch))
                continue;
            if (ch != '=')
                throw '';
            if (string[++index] != '>')
                throw '';
            state = 'body';
            break;

        case 'body':
            if (whitespace.test(ch))
                continue;
            string = string.substring(index);

            if (ch == '{')
                string = string.replace(/^{\s*(.*)\s*}\s*$/, '$1');
            else
                string = 'return ' + string.trim();

            index = string.length;
            break;

        default:
            throw '';
        }
    }

    return ['window.Function', args, string];
};

3 Comments

I have made a much simpler version here, but the tradeoff is that your function gets reconstructed for every call to the deserialised one. This should be negligible, so use this if you're not fond of the huge arguments parser above
I am trying to serialize this ƒ unsubscribeOne(i) { if (this.observers === undefined || this.observers[i] === undefined) { return; } delete this.observers[i]; this.observerCount -= 1… It's a function that is returned from calling this method firebase.google.com/docs/reference/js/… . However, I get the unexpected identifier error
@savram You are trying to serialise native browser code, that is, non-javascript, probably C++ code. The unsub function is laundered through bind, you cannot get it. Have a nice day
-2
w = (function(x){
    return function(y){ 
        return x+y; 
    };
});""+w returns "function(x){
    return function(y){
        return x+y;
    };
}" but ""+w(3) returns "function(y){
    return x+y; 
}"

which is not the same as w(3) which somehow still remembers to add 3.

1 Comment

While this code may answer the question, providing additional context regarding how and why it solves the problem would improve the answer's long-term value.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.