
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.
DIFFICULTY LEVEL: ADVANCED to EXPERT
Assumes complete knowledge and familiarity of Orchestration, Workflows, and Scripting in ServiceNow. Assumes having taken the class SSNF and has considerable cutting-edge level of knowledge and/or familiarity with Scripting in ServiceNow.
Some time ago, I had the need to port a large JSON object (not a GlideRecord) from code to a called Workflow. Simple, I said, I will just pass it through the variables parameter of my startFlow command. Not. startFlow, it appears, replaces all sorts of important characters in the object; scrambling it and making it garbage on the receiving end! It helps you! Arrrgh! - a technical pirate term expressing frustration. Well, a need-is-a-need and I had to have a solution. Here is what I came up with.
Caveats:
- DO NOT BOTHER USING THIS METHOD TO SEND A CONVERTED GLIDERECORD. You can just go get this via a GlideRecord call from the workflow. We ARE all server-side here after all! So in my examples...what did I do? I converted a couple of GlideRecords to give me some test data. Do as I say, not as I do! 😁
- Remember to play with these techniques in a sandbox or in your personal developer instance. Please don't play with code like this in Production! Seriously.
Design:
So the process will be to take our object, convert it to JSON, convert THAT to a string, then URL encode it (the secret). However, you still have to tweak the encode a bit to get it to work (some character replacement). Then on the workflow side-of-things we reverse the process. Think Star Trek Transporter! 😁
From the calling script:
- Create an object, or object array
- Convert the object to JSON
- JSON Stringify the object
- URL Encode the string
- Fix up the encoded URL
From the workflow:
- URL Decode the string
- Unfix the decoded string
- JSON Parse the object
- Begin using the object
Lab 1.1: The Calling Script
The following code can be run on any server-side script (Business Rules, Script Includes, Scheduled Jobs, Script Actions; to name a few). We will be using a Fix Script to do the work for us. I will be incorporating a couple of other articles I had written some time back to give us some test data to play with.
Community Code Snippets - Current Factory
Mini-Lab: Extending the GlideRecord Object <- to do it right (I ended up not using this in the lab)
I have attached the code for both of these to this article. I would suggest reading both if you want to understand how they actually work, but it is not necessary if you just want focus on this article. Import the attached files to bring these libraries into your developer instance.
- Navigate to Fix Scripts, and create a new one.
- Fill out the form with the following:
a. Name: Send JSON to Workflow
b. Description: Example of sending a JSON object to a workflow
c. Script:
var location = 'FS:Send JSON to Workflow';
// ---- BEGIN DATA CREATION ----
// get a single incident record and convert it to an object
var current = toObject(currentFactory('incident'), 'descending');
gs.info('---> single: ' + JSON.stringify(current)); // debug
// got get a bunch of records and convert it to an object array
var order = {};
order.type = 'descending';
order.field = 'number';
// just showing off here - the toObjectList part isn't really necessary
var incidentList = toObjectList(listFactory('incident', 5, order));
gs.info('---> multiple: ' + JSON.stringify(incidentList)); // debug
// there is a LOT of stuff in a GlideRecord so let's pick off a couple of things
// we could pass across for a test
var incidents = [];
for (var i = 0; i < incidentList.length; i++) {
var incident = {};
incident.number = incidentList[i].number;
incident.sys_id = incidentList[i].sys_id.toString();
incident.short_description = global.JSUtil.notNil(incidentList[i].short_description) ?
incidentList[i].short_description : 'nuttin here';
incidents.push(incident);
}
gs.info('---> multiple: ' + JSON.stringify(incidents)); // debug
// ---- END DATA CREATION ----
// ---- BEGIN LAB ----
try {
var short_description = current.short_description;
var number = current.number + '';
var testList = ['cows', 'chickens', 'pigs', 'horses'];
// From this point is where we set up the send
var payload = {};
payload.short_description = short_description; // single value
payload.number = number;
payload.testList = testList; // simple array
payload.incident = current; // complex object
payload.incidentList = incidents; // object array
// turn the payload into a JSON object and stringify it
payload = global.JSON.stringify(payload);
gs.info('---> payload: ' + JSON.stringify(payload)); // debug
var workflow = new global.Workflow();
var workflowID = workflow.getWorkflowFromName('Send JSON to Workflow');
gs.info('---> workflowID: ' + workflowID); // debug
var vars = {};
// now we encode it and fix it up before startFlow can destroy it
// colens, parenthesis and brackets are problems. Oddly you have to reconstitute the brackes here,
// and replace the colens. startFlow has a lot to answer for!
// place the string into our inbound variable.
vars.u_payload = encodeURI(payload).replace(/%5b/g, '[').replace(/%5D/g, ']').replace(/:/g, '%3A').replace(/\(/g,'%28').replace(/\)/g,'%29');
//vars.u_payload = encodeURI(payload).replace(/%5b/g, '[').replace(/%5D/g, ']').replace(/:/g, '%3A');
gs.info('---> vars: ' + JSON.stringify(vars)); // debug
// now call the workflow and pass our object
workflow.startFlow(workflowID, null, null, vars);
} catch (err) {
gs.error('---> ERROR: ' + err);
}
// ---- END LAB ---->
// ---- UTILITIES FOR DATA CREATION ----
function currentFactory(tableName, order, encodedQuery) {
var currentRecords = new GlideRecord(tableName);
if (JSUtil.notNil(encodedQuery)) {
currentRecords.addEncodedQuery(encodedQuery);
}
if (JSUtil.notNil(order)) {
if (order.type == 'descending') {
currentRecords.orderByDesc(order.field);
} else {
currentRecords.orderBy(order.field);
}
}
currentRecords.setLimit(1); // there is always ONLY one record in current
currentRecords.query();
if (currentRecords.hasNext()) {
currentRecords.next();
}
return currentRecords;
}
// for debugging - return one or more records from the given table, encodedQuery is optional
function listFactory(tableName, limit, order, encodedQuery) {
var listRecords = new GlideRecord(tableName);
try {
if (this.notNil(encodedQuery)) {
listRecords.addEncodedQuery(encodedQuery);
}
if (this.notNil(order)) {
if (order.type == 'descending') {
listRecords.orderByDesc(order.field);
} else {
listRecords.orderBy(order.field);
}
}
listRecords.setLimit(limit);
listRecords.query();
if (listRecords.hasNext() && limit == 1) {
listRecords.next();
}
} catch (err) {
throw err;
}
return listRecords;
}
// GlideRecord to Object Converter - akes a GlideRecord, converts it to an object array.
// Only returns display value of a reference field
function toObject(recordToPackage) {
var packageToSend = {};
for (var property in recordToPackage) {
try {
packageToSend[property] = recordToPackage[property].getDisplayValue();
} catch (err) {} // eat the error, don't care
}
return packageToSend;
}
function toObjectList(recordsToPackage) {
var objectList = [];
// loop through all the records and create the object array
while (recordsToPackage.next()) {
objectList.push(toObject(recordsToPackage));
}
return objectList;
};
I know, I know, that is a lot of code! I left in my debugging statements to allow you to see what happens to the data as it is prepped to send to the workflow.
NOTE: You have to use "global" in front of workflow or it has no idea what it is and will fail during runtime. BTW, the editor tells you this if you pay close enough attention! Of course, I didn't initially. 😋
NOTE: Try removing some of the .replace(...) statements at the end of the encodeURL. It is instructive to see the errors. You may run into something that didn't encode and you will need to debug it. I just included the things I ran into.
3. Save your work.
Lab 1.2: The Workflow
1. Navigate to Workflow -> Workflow Editor
2. Create a new Workflow
- Name: Send JSON to Workflow
- Table: Global
- Description: Show that JSON can be received from an external source and utilized by a workflow.
- Submit.
3. Create a new Input Variable. Type: String, Label: Payload, Column Name: u_payload, length 16000.
NOTE: you might want to make this bigger if you have a lot of data you are bringing across. Otherwise, your string gets truncated and you get strange conversion errors from the JSON decode!
4. Pull out a Run Script Activity
- Name: Initialize
- Script:
var location = context.name + '.' + activity.name;
var message = gs.getMessage('--->[{0}\n', [location]);
try {
gs.info('---> workflow.variables.u_payload: ' + workflow.variables.u_payload); // debug
var payloadDecode = decodeURI(workflow.variables.u_payload);
// prep the string prior to reconstituting the JSON.
// Colens are the only issue, all the rest get handled by the decodeURI
payloadDecode = payloadDecode.replace(/%3A/g, ':');
// we can now parse the string into a JSON object
var payload = JSON.parse(payloadDecode);
// now we can start using the values
var short_description = payload.short_description;
var number = payload.number;
var testList = payload.testList; // simple array
var incident = payload.incident; // complex object
var incidentList = payload.incidentList; // object array
message += gs.getMessage('\tshort_description: {0}\n\tpayload.number: {1}\n\ttestList length: {2}\n', [
short_description,
number,
testList.length
]);
for (var i = 0; i < testList.length; i++) {
message += gs.getMessage('\t- {0}\n', [testList[i]]);
}
message += gs.getMessage('\n--- incident\nnumber: {0}\tsys_id: {1}\n', [
incident.number,
incident.sys_id
]);
message += '--- incidentList\n';
for (var j = 0; j < incidentList.length; j++) {
var incidentElement = incidentList[j];
message += gs.getMessage('number: {0}\t- short_description: {1}\n', [
incidentElement.number,
incidentElement.short_description.replace(/~/g, ' ')
]);
}
gs.info(message);
} catch (err) {
gs.error('---> [{1}] ERROR: {0}\nMessage: {2}', [err, location, message]);
}
NOTE: I left in my debug statement. It is again instructive to see what happens to the string on import. Always, trap any errors that may occur. This can happen if you have bad unencoded data coming across!
5. Click Update to save your work.
Lab 1.3: Test
- Go back to your fix script, and run it.
- Navigate to System Logs > System Log > All and filter on Message of --->. Something like the following should appear:
And there you go! Remember: If you get strange conversion errors in the workflow code it is probably because you overran 16,000 characters in your string. Just increase the u_payload size to correct the issue.
Remember that Fix Script == Scripted Scheduled Job. Hint.
Enjoy!
Steven Bell.
If you find this article helps you, don't forget to log in and mark it as "Helpful"!
Originally published on: 09-04-2016 08:24 AM
I updated the code, fixed broken links, and brought the article into alignment with my new formatting standard.
- 5,501 Views
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.