2

I have a JSON object which looks like this:

string str = '[
  {
    "name": "data-input1",
    "type": "Element [in]",
    "description": "data-input1",
    "getConnectorPosition": "function (element) { return {x: 0, y: Math.floor(element.rectangle.width / 2)} }"
  },
  {
    "name": "data-output1",
    "type": "Element [out] [array]",
    "description": "data-output1",
    "getConnectorPosition": "function (element) { return {x: Math.floor(element.rectangle.width), y: Math.floor(element.rectangle.height / 2)} }"
  }
  ]';

and I'd like to parse this string into an object.

I receive this error:

Uncaught TypeError: string is not a function

when the method getConnectorPosition is invoked.

Reading previous questions I have understood that functions in JSON are not exactly "legal", and the error also suggests this.

How, exactly, can I correctly parse a JSON object which contains functions (as above) and then invoke those functions?

theonlygusti
  • 11,032
  • 11
  • 64
  • 119
Fabrizio Morello
  • 335
  • 1
  • 5
  • 18
  • 1
    JSON is for data interchange. It will never support functions. You can write your own code to post-process your parsed objects. *edit* There's also the `reviver` parameter, which basically just makes post-processing a little simpler (sometimes). – Pointy Jan 25 '15 at 19:52
  • Do you mean you want to parse this JSON string to JavaScript object? – meskobalazs Jan 25 '15 at 19:52
  • @Roko I don't think this is a duplicate. The dupe asks if it's valid. This question comes with the assumption that it's invalid, and asks how to get around it. – Scimonster Jan 25 '15 at 19:55
  • *"I'd like to parse this string into a json"* It rather seems you want to parse the string **containing** JSON into an array of objects. – Felix Kling Jan 25 '15 at 20:05
  • Okay, which browser are you using? – theonlygusti Jan 25 '15 at 20:07
  • @theonlygusti This is not a browser issue – meskobalazs Jan 25 '15 at 20:10
  • @FelixKling as you said, I want to parse the string containing JSON into an array of objects. The browser is not important, because my code shuold run everywhere. – Fabrizio Morello Jan 25 '15 at 20:11
  • Why does the function definition have to be part of the your data? – Felix Kling Jan 25 '15 at 20:12
  • @FabrizioMorello I want to know the browser so I can better understand your error - why do you have to be so rude about it? You could just answer the question and then give your opinion. – theonlygusti Jan 25 '15 at 20:18
  • @meskobalazs I know that, but if I know the browser, I can figure out what the error means. – theonlygusti Jan 25 '15 at 20:20
  • @theonlygusti: I guess the OP does something like `arr[0].getConnectorPosition()`, which doesn't work since `getConnectorPosition` is a string. – Felix Kling Jan 25 '15 at 20:22
  • @theonlygusti I apologize for browser stuff, I didn't want to be presumptuous, but I'm pretty sure that the problem is due to the presence of the function in the json object. Anyway, I use Chrome v 40.0.2214.91 m. – Fabrizio Morello Jan 25 '15 at 20:31
  • Check out the [jsonfn](https://github.com/vkiryukhin/jsonfn) library that's built for this. – Scimonster Jan 25 '15 at 20:47

3 Answers3

3

Your error is being thrown because in JavaScript you don't declare variables as a certain type, you always use the var keyword. If you change string str to var str then your current error will disappear.

You will then need to make sure your string is either single-line, or that you escape the literal newlines, as JavaScript can't handle multi-line strings:

     var str = '[\
        { "name": "data-input1", \
         "type": "Element [in]", \

Anyway, lets say you declare str correctly, and single-line or escape the newlines, you can parse it as normal:

var obj = JSON.parse(str);

and access your functions in string form like so:

obj[0].getConnectorPosition;

To actually use your functions, you will have to evaluate them:

eval(obj[0].getConnectorPosition)

is the same as

function (element) { return { x: 0, y: Math.floor(element.rectangle.width / 2) } }

Update for the more-modern day (ES6+):

You can define variables using let and const, variables declared const cannot later be changed, and let is preferred to var because it is more scope safe.

You can also use template string literals to multi-line strings.

So you can write:

const str = `[
  {
    "name": "data-input1",
    "type": "Element [in]",
    "description": "data-input1",
    "getConnectorPosition": "function (element) { return {x: 0, y: Math.floor(element.rectangle.width / 2)} }"
  },
  {
    "name": "data-output1",
    "type": "Element [out] [array]",
    "description": "data-output1",
    "getConnectorPosition": "function (element) { return {x: Math.floor(element.rectangle.width), y: Math.floor(element.rectangle.height / 2)} }"
  }
  ]`;
theonlygusti
  • 11,032
  • 11
  • 64
  • 119
  • It seems rational. Anyway, from this discussion I think that the best solution (in general) is to avoid the functions inside JSON if possible. Of course I have to thank you for effort, then I accepted this answer. – Fabrizio Morello Jan 25 '15 at 20:51
  • @FabrizioMorello Yes, there is generally a better approach to problems which would otherwise involve declaring functions within JSON, but all in all I think it is fairly safe to do that which I have described in this specific case. – theonlygusti Jan 25 '15 at 20:53
1

The actual error you get is because you should write var str = ... instead of string.

You could get the substrings containing the function, and evaluate them as a function using eval. However this could lead to serious security and other concerns. To quote Douglas Crockford:

The eval function (and its relatives, Function, setTimeout, and setInterval) provide access to the JavaScript compiler. This is sometimes necessary, but in most cases it indicates the presence of extremely bad coding. The eval function is the most misused feature of JavaScript.

If you really want to go down this road, you can do it using eval, but you have been warned :)

By the way, if you have parsed your JSON you could do this:

obj[0].getConnectorPosition = eval(obj[0].getConnectorPosition);

So you replace the previous string with the evaluated method.

meskobalazs
  • 15,741
  • 2
  • 40
  • 63
1

Here is an example that converts a json string containing a function back to a json object with a valid function declaration.

var jsonstring = "{\"schema\": {\"title\": \"User Feedback\",        \"description\":\"so\", \"type\":\"object\", \"properties\":{\"name\":{\"type\":\"string\"}}}," + "\"options\":{ \"form\":{\"attributes\":{}, \"buttons\":{ \"submit\":{ \"title\":\"It\", \"click\":\"function(){alert('hello');}\" }}} }}";

var jsonData = JSON.parse(jsonstring);

function Iterate(data)
{
      jQuery.each(data, function (index, value) {
        if (typeof value == 'object') {
            Iterate(value);
        }
        else {
            if (value.indexOf("function()") > -1)
                data[index] = eval("(" + value + ")");
        }
    });

};

Iterate(jsonData);
//call the embedded function 
jsonData.options.form.buttons.submit.click();
Vaughn Gavin
  • 69
  • 1
  • 3