Let’s start with the some background information. JSON. or Javascript Object Notation, was developed as a way to transfer data between web browsers and servers without using special plugins. It was designed to be a textual format that could represent arrays, name-value pairs and other complex data with very little textual overhead. Compared to XML, JSON uses fewer bytes to convey the same meaning. It should be noted that while JSON was initially designed to be used with Javascript, it is actually language independent. It is also possible to make a JSON file that is not compatible with Javascript. This is due to the fact that Javascript has limitations on key names and certain special characters whereas JSON does not.
JSON has the following data types:
Type | | Legato Define | | Description |
Null | | JSON_DATA_TYPE_NULL (0) | | The value is empty |
String | | JSON_DATA_TYPE_STRING (1) | | A sequence of zero of more Unicode characters. |
Number | | JSON_DATA_TYPE_NUMBER(2) | | A signed decimal number that may contain a fractional part. Exponential E notation is allowed. |
Object | | JSON_DATA_TYPE_OBJECT (3) | | An unordered collection of name-value pairs where the names are strings. The names (also known as keys) are intended to represent associative arrays. It is up to the individual JSON implementation as to how non-unique keys are handled. |
Array | | JSON_DATA_TYPE_ARRAY (4) | | An ordered list of zero or more values, each of which may be any data type. |
Boolean | | JSON_DATA_TYPE_BOOL (5) | | true or false |
There are a few things to note here. First, JSON supports Unicode strings. This isn’t a problem for Legato since JSON uses UTF-8, which is supported in Legato. Second, numbers can be either floating point or integers. Third, arrays and objects can contain different data types. For example:
[1, "kitten", 25.312];
This array contains numbers and strings. In Legato, arrays must have a single type. This won’t stop us from being able to read the data but it does make it a little more difficult.
Now that we’ve covered some of the background for JSON, we can start to talk about using it in Legato. For the reasons stated above it should be clear that we cannot simply load the JSON data into a single variable. However, the process is still straightforward. To load JSON, we have the JSONLoad SDK function.
handle = JSONLoad ( string data );
This function can take either a string that is JSON data or a qualified filename/URL to a file with the JSON data. It returns a handle that we can use with our other JSON functions. Now that the JSON is loaded, we can move onto reading it.
Generally, when reading a file format like JSON, as developers we know some or all of the file’s structure. While knowing the format is not required in Legato, it will simplify the code dramatically. The reason for this will become clear once we dive in. Let’s look at the functions to read JSON data using the handle we obtained.
int = JSONGetType ( handle hJSON, string item );
var = JSONGetValue ( handle hJSON, string item, [bool native] );
The JSONGetValue function is the core of reading the JSON file. It will return the value of a specified item in the JSON object. The return type of this function is var, indicating that its type varies based on the parameters passed. While this is good because it reduces the number of functions needed to process different types of data, it can lead to problems with error handling. The JSONGetType function returns the data type of the item. The big question for most readers will be: “What does the item parameter mean?” The answer is complicated and simple at the same time. item is simply the Javascript notation to retrieve the value. If you are wondering what that exactly means, consider the following code.
Legato:
handle hJSON;
hJSON = JSONLoad("1234");
AddMessage(JSONGetValue(hJSON, "obj"));
Javascript:
var obj;
obj = JSON.parse("1234");
console.log(obj);
These two sets of code do exactly the same thing. They load the text “1234” as JSON and then print the value of the loaded JSON data to the screen. The Legato code does not specify the name of the variable that becomes JSON data because the handle of the Legato JSON object is always passed to the function. For this reason, the API always assumes the name of the loaded data is “obj”. Let’s adjust the example slightly to be more complex.
Legato:
handle hJSON;
hJSON = JSONLoad("{\"a\":1, \"c\":2}");
AddMessage(JSONGetValue(hJSON, "obj.c"));
Javascript:
var obj;
obj = JSON.parse("{\"a\":1, \"c\":2}");
console.log(obj.c);
It looks a little complicated because of the escaped quotes. In Javascript, we could avoid that by using single quotes to create the string, but in Legato (and JSON) strings must use double quotes. However, both of these examples write out just “2”. This is because that is the value of the “c” key of our loaded JSON object. So, as the samples show, the item parameter is the Javascript notation of the object. However, unlike Javascript, all keys and array indices must be hard values. Consider the following Javascript code:
var key;
key = "c";
console.log(obj[key]);
This accomplishes the same as the above Javascript code but we used a variable to access the key value pair. This cannot be done in Legato. However, developers can simply build the string with the variables before passing it to the JSONGetValue function. The same example would look like this:
string key;
key = "c";
AddMessage(JSONGetValue(hJSON, "obj[\"" + key + "\"]"));
// This notation will also work
AddMessage(JSONGetValue(hJSON, "obj." + key));
This approach is simple yet complex, like JSON itself. Knowing the structure of the loaded JSON very quickly becomes important. How did we know that there was a key “c”? The easiest answer is to have well defined inputs but that is not always the case. Let’s make a Legato function that will print out a JSON object.
string print_json(handle hj, string node) {
string res;
string tmp;
string keys[];
int type;
int cnt;
int ix;
if (node == "") {
node = "obj";
}
type = JSONGetType(hj, node);
if (IsError(type)) {
return "";
}
switch (type) {
case JSON_DATA_TYPE_NULL:
res = "null";
break;
case JSON_DATA_TYPE_STRING:
case JSON_DATA_TYPE_NUMBER:
case JSON_DATA_TYPE_BOOL:
res = JSONGetValue(hj, node);
break;
case JSON_DATA_TYPE_OBJECT:
keys = JSONGetValue(hj, node);
cnt = ArrayGetAxisDepth(keys);
ix = 0;
res += "Object(";
while (ix < cnt) {
if (ix != 0) {
res += ", ";
}
res += keys[ix] + ": ";
res += print_json(hj, node + "." + keys[ix]);
ix++;
}
res += ")";
break;
case JSON_DATA_TYPE_ARRAY:
ix = 0;
res += "Array(";
while (ix >= 0) {
tmp = print_json(hj, node + FormatString("[%d]", ix));
if (tmp == "") {
break;
}
if (ix != 0) {
res += ", ";
}
res += tmp;
ix++;
}
res += ")";
break;
}
return res;
}
This function uses recursion to process the arrays and objects. Recursion is a good choice because JSON arrays and objects can contain other arrays, objects, or both. We start off by checking the node variable. If it is empty, we will set it to “obj” to be safe. Then we will get the type of the current node using the JSONGetType function. If this function fails, we assume this node does not exist. If we wanted to be thorough we could call the GetLastErrorMessage function to check what caused the function to fail.
We then set the res variable based on the data type of the JSON item. For the simple types we can simply call the JSONGetValue function since it returns a string version. We could do this for the null type too, but I wanted to make null different from the error case (empty string). For an object, we can use the JSONGetValue function to get an array of keys for the object. Then, for each key, we make a new node address using the key and run our function again. Likewise, for arrays we call our own function for each index until there is a failure. Because we are always calling the JSONGetType function before calling the JSONGetValue function, we know exactly what the return type will be even though we know nothing of the JSON structure.
If you want to try the function out here is some sample code:
void main() {
handle hj;
string s1;
hj = JSONLoad("https://www.novaworkssoftware.com/samples/ldc-sample-json.json");
s1 = print_json(hj, "");
StringToFile(s1, R"C:\Windows\Temp\test.txt");
}
As you can see, even without complex objects or typeless variables, Legato can parse your JSON files and allow for random access of the resulting object. In future versions of Legato, we will add even more JSON support and maybe even complex object support. With Legato, connecting different systems of representing and decoding information no longer seems like such a daunting task.
David Theis has been developing software for Windows operating systems for over fifteen years. He has a Bachelor of Sciences in Computer Science from the Rochester Institute of Technology and co-founded Novaworks in 2006. He is the Vice President of Development and is one of the primary developers of GoFiler, a financial reporting software package designed to create and file EDGAR XML, HTML, and XBRL documents to the U.S. Securities and Exchange Commission. |
Additional Resources
Novaworks’ Legato Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato