Is there a built-in way to convert a Dictionary to JSON in Action Code?

I’m working on an integration project that requires creating JSON payloads from Tinderbox. My idea was to build the JSON structure using a Dictionary and then convert that Dictionary into a JSON string — since JSON essentially represents a dictionary. However, it seems that Tinderbox does not offer native support for converting a Dictionary variable to JSON.

Before I write my own “dictToJson” function, I want to make sure my understanding of the built-in operators is correct. If I end up developing a Dictionary-to-JSON function, I’ll post it here for feedback and posterity :slightly_smiling_face:

2 Likes

In most cases, it’s fairly trivial to do. There isn’t a built-in operator.

2 Likes

Further to the last, Dictionary data is stored as a string (in the XML) but isn’t stored with any expectation of the order of the keys. A number of operators should assist you JSON-ification task (in no particular order).

  • Dictionary.count() returns a Number-type, the number of keys in the dictionary.
  • Dictionary.keys() returns a List-type, a list of all the keys. The keys are not necessarily sorted, is you want an A-Z sort, even if only for output consistency, call .keys.sort, i.e. $MyList = $MyDictionary.keys.sort;.
  • list.each(loopVar){actions} allows you to iterate the list of keys you can get for the dictionary.

Tinderbox has no means to know the purpose of a key or the expected data type of its value (which might be a string, boolean, number, list or dictionary (or nested lists/dictionaries of such). You will need to provide such information yourself, e.g. by prior inspection.

You might, for instance want to make a user attribute (or variable) of Dictionary type and store a key name and the expected data type. That way the same loop variable (of a key name) can be used to access info of the type of JSON value mark-up you need. I guess these could be sub-functions, one for type of a single function with internal branching to return the key value in the correct mark-up, e.g nothing for a Number, double-quotes for a string, etc.

1 Like

How about this - just a quick idea:

The first function will convert the dict to a json object:

function dictToJSON_db(myDictionary:dictionary) {
	var:string myJSON   = "{";
	var:number myCnt    = 0;
	var:number maxCnt   = myDictionary.size;
	var:string curVal   = "";
	var:string dataType = "";
	var:string jsonVal  = "";

	if(maxCnt > 0) {
		myDictionary.keys.each(x){
			myCnt   += 1;
			curVal   = myDictionary[x].jsonEncode().trim();
			if(curVal != ""){
				dataType = checkDataType_db(curVal);

				if(dataType == "obj"){
					jsonVal = dictToJSON_db(curVal);
				};
				if(dataType == "list"){
					jsonVal = curVal.replace(";",",");
				};
				if(dataType == "listB"){
					jsonVal = curVal.replace(";",",");
				};
				if(dataType == "float"){
					jsonVal = curVal;
				};
				if(dataType == "int"){
					jsonVal = curVal;
				};
				if(dataType == "bol"){
					jsonVal = curVal;
				};
				if(dataType == "string"){
					jsonVal = '"' + curVal + '"';
				};

				myJSON += '"' + x + '":' + jsonVal;
				if(myCnt != maxCnt) {
					myJSON += ",";
				};
			};
		};
	};

	myJSON += "}";

	return myJSON;
};

This helper function will do a type casting - maybe not needed but I thought it might be a good idea:

function checkDataType_db(myRawData:string){
	// a number, an object (JSON object), an array, a boolean, null
	if(myRawData.contains("[\{\}]")) {
		return "obj";
	};
	if(myRawData.contains("[\[\]]")) {
		return "list";
	};
	if(myRawData.contains(";")) {
		return "listB";
	};
	if(myRawData.contains("[\d]+\.")) {
		return "float";
	};
	if(myRawData.contains("[\d]+")) {
		return "int";
	};
	if(myRawData.contains("true|false")) {
		return "bol";
	};

	return "string";
};

To test it create a stamp with:

var:dictionary rawDict = {num:22.2; int:22; str:“animal”; str2: mineral; arr: [1;2;3]; bol:true; obj:{a:1}; list: “1;2;3”};

$Text += “\n\n####################\n” + dictToJSON_db(rawDict);

checkDataType might get confused by nested structure such as [ {"apple":"fruit"}], which is a list. but which (I think) checkDataType will consider an object.

But this might not be an issue for one’s actual data.

@webline, thank you for sharing your dictToJSON_db and checkDataType_db functions. Unforunately, I ran into a snag with Emoji where “Supplementary Multilingual Plane” Unicode chars, such as :robot:, weren’t handled properly by Dictionary objects. So, I reverted to using string concatination to build JSON payloads. Here’s one helper function I created to build a key-value-pair in JSON:

//
// Creates a JSON-formatted KVP string using the
// provided key and value.
//
// NOTE: This function is inteded to be used by
// functions that pass JSON to stdin, so escaped chars
// are doubly escaped (e.g., '\\"').
//
function jsonKVP(key, value, isLast) {
    var:string kvp = '"' + key + '": "'
                   + value.jsonEncode.replace('\\', '\\\\') + '"';

    // If this isn't the last key,
    // ouput a comma.
    if (!isLast) {
        kvp += ", ";
    };

    return kvp;
}

As soon as @eastgate adds support for Supplementary Multilingual Plane Unicode, rest assured that I’ll implemented the functions you authored to support my data exchange workflows in Tinderbox. Thamk you again :pray:

P.S. The checkDataType_db is quite clever; I’m excited to crib it :slightly_smiling_face:

The current backstage build addresses the emoji issue.

1 Like

This is great news, @eastgate. I plan to put the new parser through its paces tomorrow when working on a new integration project using Tinderbox where emoji is likely to show up in JSON strings.

Thank you for your ongoing work supporting an indispensable product :blue_heart: