1

I have written a Javascript function to retrieve JSON data from either a server or local files, in any browser. This code digests JSON data from either: (a) an XMLHttpRequest response, or (b) an imported script variable.

For the latter case, this code expects the variable 'JSONdata'. Instead of putting it inline in two (or more) places, it is initialised as a global constant JSCRIPTVAR. This is for legibility and maintenance - to change if the data file uses a different value. You'll see the code uses an eval function to extract the (pointer to our) JSON data - a bit too close to the actual data for my comfort. My question is, how do I read the value of the primitive variable 'JSONdata' via its alias JSCRIPTVAR without using (the dreaded) eval function?

I have tried closures and other methods described here (Reference a Javascript variable by a text alias) but am unable to get the JSON data, only the variable name or 'undefined'.

var JSCRIPTVAR = "JSONdata";        // any variable name less than 10 characters or less
function digestJSON(response, JSONobjName) {
    if (typeof response == 'string') 
        JSONstring = response;
    else if (typeof JScriptVar !== 'undefined' && typeof JSCRIPTVAR.valueOf() !== 'undefined')  
        JSONstring = eval(JSCRIPTVAR.substring(0, 10));     // ensure that we don't take in any malicious code
    try {
        // this method assigns the JSON data to a global object using JSONobjName, so can support multiple JSON objects
        window[JSONobjName] = JSON.parse(JSONstring);       // Parse JSON string into object - IE 8+ supports JSON.parse()
        console.log("Successfully digested JSON. Data is now available via DOM methods.");
    } catch (e) {
        alert("Error parsing the '" + JSONobjName + "' file'. Check that the file is properly formatted.");
        console.error("JSON parsing error: ", e + ", type: " + this.type);
    }
}

The function digestJSON is called in 3 functions that employ anonymous callbacks: loadViaXHR2, loadViaXHR1 and loadViaJScript. The latter is the one that imports the JSON data via a Javascript file (see example below). When invoked, the 'callback' parameter would be set to 'digestJSON'.

function loadViaJScript(urlSpec, JSONobjName, callback, isAsync) {
    console.log("Attempting to read JSON data using Javascript methods.");
    var script = document.createElement('script');
    var parent = document.getElementsByTagName('head').item(0) || document.documentElement;
    script.type = "text/javascript";
    script.onerror = function(e) {
        alert("Error loading the '" + JSONobjName + "' file: '" + urlSpec + "'. Check that the file exists.");
        console.error("Error loading via Javascript: ", e + ", type: " + this.type);
    }
    script.src = urlSpec;
    parent.appendChild(script);
    script.onload = function() { 
        callback(null, JSONobjName); 
    };
}

Sample data (trivial example): var JSONdata = '{"blue" : "is ok", "red" : "is my fave color"}';

Community
  • 1
  • 1
wmclaxton
  • 35
  • 9
  • It would probably help to see the code that calls `digestJSON()` – Garcia Hurtado Dec 10 '14 at 04:46
  • Thanks @GarciaHurtado. I ammended the submission. It's really a very simple question - I hope all the JSON stuff doesn't put anyone off. – wmclaxton Dec 10 '14 at 05:03
  • Why don't you simply use the same strategy as for your `JSONobjName`? – Bergi Dec 10 '14 at 05:05
  • You really should have a look at JSONP. – Bergi Dec 10 '14 at 05:06
  • 1
    Just use `JSON.parse()`. I don't know what all the rest of the code is for, but it seems likely that you don't need it. If you have the JSON data in a string you can use `JSON.parse()`. If you need to support old browsers that don't have this built in, you can use the parser from json.org. – Michael Geary Dec 10 '14 at 05:10
  • @Bergi, in the case of JSONobjName, the assignment is made within the function digestJSON. In the case of JSONdata, the variable is already initialised as a primitive string before the function is called. So what I need to do is reference the content of the variable JSONdata with only an a global alias. Eval works, it's just scary so close to the actual JSON data. – wmclaxton Dec 10 '14 at 05:12
  • @MichaelGeary, would 'JSON.parse(JSCRIPTVAR)' work? I don't think so. You're right that 'JSON.parse(JSONdata)' would work, and get rid of the eval, but I'm trying to use a global alias for legibility and maintenance. Perhaps this is a valid use of eval, I just thought there must be a safer way. – wmclaxton Dec 10 '14 at 05:17
  • @Bergi, JSONP requires a server. This project must also read JSON data from flat files on the local system (eg- flash drives). – wmclaxton Dec 10 '14 at 05:20
  • Perhaps the question can be restated as: (a) some asynchronous process initialises 'JSONdata' to a really long and potentially unsafe string, and (b) the code assigns 'JSONdata' to the global constant 'JSCRIPTVAR', so how does a function retrieve the mentioned string using *only* the alias 'JSCRIPTVAR', without using eval? – wmclaxton Dec 10 '14 at 05:25
  • I don't see how `window[JSCRIPTVAR]` would not work. And JSONP would not require a server, it just would require your data files to begin with a function call instead of that `var JSONdata = …` assignment. – Bergi Dec 10 '14 at 05:30

2 Answers2

3

Maybe this fact will help you: Any variable in global scope can alternatively be accessed as a property of window. So if you have

var something=1;
var VARNAME="something";

Then the following are equivalent:

console.log(something);//1
console.log(eval(VARNAME));//1
console.log(window[VARNAME]);//1
console.log(window.something);//1

Here are some examples:

<script>
var something=1;
var SCRIPTVAR="something";

console.log(eval(SCRIPTVAR));//1
console.log(something);//1
console.log(window[SCRIPTVAR]);//1
console.log(window.something);//1

eval(SCRIPTVAR+"++");

console.log(eval(SCRIPTVAR));//2
console.log(something);//2
console.log(window[SCRIPTVAR]);//2
console.log(window.something);//2

window[SCRIPTVAR]++;

console.log(eval(SCRIPTVAR));//3
console.log(something);//3
console.log(window[SCRIPTVAR]);//3
console.log(window.something);//3


window.something++;

console.log(eval(SCRIPTVAR));//4
console.log(something);//4
console.log(window[SCRIPTVAR]);//4
console.log(window.something);//4
</script>
chiliNUT
  • 18,989
  • 14
  • 66
  • 106
  • chilNUT you're right. This stub works and the last line referencing by window object can be inside the function. var something=1; var SCRIPTVAR="something"; alert("got something: " + window[SCRIPTVAR]); // returns 1 However, it still doesn't work with JSCRIPTVAR - perhaps due to the aynchronous assignment of JSONdata. – wmclaxton Dec 10 '14 at 05:47
  • 1
    chiliNUT, I would upvote you but I don't have the reputation points. I may award you the answer, unless someone says "eval is legitimate in this situation". – wmclaxton Dec 10 '14 at 05:50
  • anything async needs to be handled in a callback function. beyond that...the problem of async access is a different one than the problem of a variable variable name, so perhaps you should find a smaller piece of code which demonstrates the issue and ask a new question – chiliNUT Dec 10 '14 at 05:52
  • glad this helped though. as a rule: never use `eval` if you can use something else. `JSON.parse` uses it internally and its one of the rare cases it is appropriate. – chiliNUT Dec 10 '14 at 05:54
  • 1
    chiliNUT, you *are* right. All I missed out was initializing the variable 'JSONdata' (like your 'something=1'). Because the actual value is loaded aynchronously, I was encountering 'undefined'. The corrected code is very simple: var JSONdata = ""; // initialise before loading var JSCRIPTVAR = "JSONdata"; // set global constant for referencing JSONstring = window[JSCRIPTVAR]; // can be inside a function – wmclaxton Dec 10 '14 at 06:09
0

The final code for digestJSON, with the fix by chiliNUT (to initialize 'JSONdata' and then access our global alias via the Window object), is:

var JSONdata = "";                // initialise before loading
var JSCRIPTVAR = "JSONdata";      // set global constant for referencing
function digestJSON(response, JSONobjName) {
    console.log("Attempting to digest JSON data using JSON parsing methods.");
    var JSONstring = "";
    if (typeof response == 'string') 
        JSONstring = response;
    else if (JSCRIPTVAR && typeof window[JSCRIPTVAR] == 'string' && window[JSCRIPTVAR].length > 0) 
        JSONstring = window[JSCRIPTVAR];
    try {
        // this method assigns the JSON data to a global object using JSONobjName, so can support multiple JSON objects
        window[JSONobjName] = JSON.parse(JSONstring);       // Parse JSON string into object - IE 8+ supports JSON.parse()
        console.log("Successfully digested JSON. Data is now available via DOM methods.");
    } catch (e) {
        alert("Error parsing the '" + JSONobjName + "' file'. Check that the file is properly formatted.");
        console.error("JSON parsing error: ", e + ", type: " + this.type);
    }
}

Hooray, the eval function is no longer required! Thanks to everyone who posted comments too.

wmclaxton
  • 35
  • 9