JavaScript

Pass JavaScript function via JSON. Pitfall and solution.

JSON is a lightweight data-interchange format. It is well readable and writable for humans and it is easy for machines to parse and generate. The most of JavaScript libraries, frameworks, plugins and whatever are configurable by options in JSON format.

Sometimes you have to parse a JSON string (text) to get a real JavaScript object. For instance, sometimes you have to read the whole JSON structure from a file and make it available on the client-side as an JavaScript object. The JSON text should be well-formed. Passing a malformed JSON text results in a JavaScript exception being thrown.

What does “well-formed” mean? A well-formed JSON structure consists of data types stringnumberobjectarrayboolean or null. Other data types are not allowed. An example:

{
    'name': 'Max',
    'address': {
        'street': 'Big Avenue 5',
        'zipcode': 12345,
        'country': 'USA'
    },
    'age': 35,
    'married': true,
    'children': ['Mike', 'John']
}

You see here string values for keys “name”, “street” and “country”, number values for “zipcode” and “age”, boolean value for “married”, object for “address” and array for “children”. But what is about JavaScript functions? Some scripts allow to pass functions as values. For instance as in some chart libraries:

{
    'margin': '2px',
    'colors': ['#FFFFFF', 'CCCCCC'],
    'labelFormatter': function(value, axis) {return value + ' degree';}
}

If you try to parse this JSON text, you will face an error because function(value, axis) {return value + ‘ degree’;} is not allowed here. Try to put this structure into this online JSON viewer to see that JSON format doesn’t accept functions. So, the idea is to pass the function as string:

{
    'borderWidth': '2px',
    'colors': ['#FFFFFF', 'CCCCCC'],
    'labelFormatter': 'function(value, axis) {return value + ' degree';}'
}

This syntax can be parsed. But we have another problem now. We have to make a real JavaScript function from its string representation. After trying a lot I found an easy solution. Normally, to parse a well-formed JSON string and return the resulting JavaScript object, you can use a native JSON.parse(…) method (implemented in JavaScript 1.7), JSON.parse(…) from json2.js written by Douglas Crockford or the jQuery’s $.parseJSON(…). The jQuery’s method only expects one parameter, the JSON text: $.parseJSON(jsonText). So, you can not modify any values with it. The first two mentioned methods have two parameters

JSON.parse(text, reviver)
 

The second parameter is optional, but exactly this parameter will help us! reviver is a function, prescribes how the value originally produced by parsing is transformed, before being returned. More precise: the reviver function can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. An example:

var transformed = JSON.parse('{'p': 5}', function(k, v) {if (k === '') return v; return v * 2;});

// The object transformed is {p: 10}

In our case, the trick is to use eval in the reviver to replace the string value by the corresponding function object:

var jsonText = '.....';  // got from any source

var jsonTransformed = JSON.parse(jsonText, function (key, value) {
    if (value && (typeof value === 'string') && value.indexOf('function') === 0) {
        // we can only pass a function as string in JSON ==> doing a real function
        eval('var jsFunc = ' + value);
        return jsFunc;
    }

    return value;
});

I know, eval is evil, but it doesn’t hurt here. Well, who doesn’t like eval there is another solution without it.

var jsonTransformed = JSON.parse(jsonText, function (key, value) {
    if (value && (typeof value === 'string') && value.indexOf('function') === 0) {
        // we can only pass a function as string in JSON ==> doing a real function
        var jsFunc = new Function('return ' + value)();
        return jsFunc;
    }

    return value;
});
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Amidamaru
Amidamaru
9 years ago

I think JSON format which you supply may be wrong. We should replace Apostroph character with quotes mark character.
http://json.parser.online.fr/ – this page show error when I copy your JSON format to check.

Back to top button