The CreatorCon Call for Content is officially open! Get started here.

jonnyseymour
ServiceNow Employee
ServiceNow Employee

An attractive concept of tables has been implemented as GlideRecord in ServiceNow. The object comprises of functions, elements and methods to work with all fields available. As tables contain fields, and the fields have types, we tend to assume that table [dot] field will inherit that field type (e.g. string). If you think that, you backed the wrong horse! GlideRecord fields are not string, number, or boolean fields. I can confirm a GlideRecord field is represented by a GlideElement, so each field itself is an object. When used, Java will guess and cast them to the correct type. To "cast" means to take an object from one particular type and transform it into another, like dollars ($) to pounds ( £), you can lose something in the conversion.

There is no need to cry a river about it; just be aware of this behavior. Even professional developers will fail to spot these problem. Same as when you are driving and looking out for motorbikes, when you are using a GlideRecord, make sure you look out for operations on these GlideElement objects. As it reads on the THINK! campaign, expect the unexpected.

think-stay-in-control-web-image-cast.jpeg

What to look out for when using a GlideRecord

#

THINK! To look out

THINK! Advice when you are scripting

1

GlideRecord fields containing strings, numbers, or booleans, especially when passed to other functions as parameters

When passing parameters to functions, force the cast to the required variable input type. For example, to cast to string use .toString(). To cast to decimals use value + 0.

2

Undefined values  

When a functions does not exist, the result is undefined

3

The typeof the GlideRecord fields is "object" (e.g typeof gr.incident = object)

Operations that depends on the type of the object like === could fail on GlideRecord fields

4

Strings operations need to be applied to strings and not the GlideElement object

Ensure you are performing a .toString() when a string operation is required (e.g. gr.short_description.toString().length)

Casting issues when using GlideRecord

Using an example, I will try to validate these three potential casting problems:

  • In a GlideElement for a string field: When trying to use 'String' function 'length' on it, it returns undefined.
  • In a GlideElement for numbers: When trying to compare the value using === , it returns false.
  • In a GlideElement for a boolean: When trying to compare the value using === , it returns false.

Example of GlideRecord fields returning false/undefined results

To demonstrate the cast problems, I have created an example. When scripting, you can explicitly cast your field. Here is a script include I have used to explicitly cast the fields:

Script include: ParseGlideElement

gliderecord example.jpg

Example Script:

var ParseGlideElement = Class.create();

ParseGlideElement.prototype = {

      initialize: function() {},

      // Parse. Input: A glideElement object. Output: A cast of the field value into boolean, decimal, date_time or string based on field internalType

      parse: function(a) {

              if(a.nil())return null;

              var b = a.getED().getInternalType();

              return "boolean" == b ? this.parseBool(a) :

                      "integer" == b ? this.parseInt(a) :

                      "glide_date_time" == b ? new GlideDateTime(a) :

                      "string" == b ? a.toString() :

                      "decimal" == b ? this.parseFloat(a):

                      a

      },

      parseBool: function(a) {

              return "boolean" == typeof a ? a : /^(true|1|yes|on)$/i.test(a)

      },

      parseInt: function(a) {

              return a + 0

      },

      parseFloat: function(a) {

              return a + 0

      },

      type: "ParseGlideElement"

};

Here is the record I will be using to test:

test record.jpg

When executing the following background script:

var gr = new GlideRecord("u_test_record");

gr.get('6f46dfb913e576005e915f7f3244b020'); // sys_id of the test created

var vstring = gr.u_string1;

var vinteger = gr.u_integer;

var vboolean = gr.u_truefalse;

gs.print("Test without explicitly casting fields: \n " + testGlideRecord(vstring, vinteger, vboolean).join('\n'));

var gpe = new ParseGlideElement();

vstring = gpe.parse(gr.u_string1);   // casting to string based on the ED internaltype Same as gr.u_string1.toString()

vinteger = gpe.parse(gr.u_integer); // casting to integer based on the ED internaltype. Same as gr.u_integer + 0

vboolean = gpe.parse(gr.u_truefalse); // casting to boolean based on the ED internaltype

gs.print("Test explicitly casting fields: \n " + testGlideRecord(vstring, vinteger, vboolean).join('\n'));

function testGlideRecord(vstring, vinteger, vboolean) {

      var message = [];

      message.push(

              '\nGlide record u_test_record.do?sys_id=6f46dfb913e576005e915f7f3244b020'

      );

      // Example 1 - Expected cast to String

      message.push("\n****** Example 1   - Expected cast to String ");

      message.push("gr.u_string1: " + vstring + " - typeof: " + typeof vstring);

      message.push("gr.u_string1.length: " + vstring.length + " - expected: 11");

      // Example 2 - Expected cast to Integer

      message.push("\n****** Example 2 - Expected cast to Integer ");

      message.push("gr.u_integer: " + vinteger + " - typeof: " + typeof vinteger);

      message.push(vinteger + " === 77777 :" + (vinteger === 77777) + '- expected: true');

      // Example 3 - Expected cast to Boolean

      message.push("\n****** Example 3 - Expected cast to boolean ");

      message.push("gr.u_truefalse: " + vboolean + " - typeof: " + typeof vboolean);

      message.push(vboolean + " === false :" + (vboolean === false) +

              '- expected: true');

      return message;

}

... this is the result:

Field

Value

Scripting example simplified

Result

Expected

u_string1

teststring2

gr.u_string1.length

undefined

11

u_string1

teststring2

gr.u_string1.toString().length

11

11

u_integer

7777

gr.u_integer === 7777

false

true

u_integer

7777

(gr.u_integer + 0) === 7777

true

true

u_truefalse

false

gr.u_truefalse === false

false

true

u_truefalse

false

parseBool(gr.u_truefalse) === false

true

true

As you can see there are a few cases where you need to explicitly "cast" your field types and avoid mixing pears with apples.

How to use GlideRecord when the unexpected happens

In a nutshell, here are some recommendations for using GlideRecord when encountering a casting issue:

GlideElement with
Element Descriptor (ED)

Operations to look out

Areas of problem

Recommendation

String

concatenations or as parameters to other functions

Operations like startsWith(), endsWith(), length used directly could return undefined

Use .toString()

Integer or Float

When used on math operations against an integer or decimal

On some operations, it could be cast to string incorrectly or validated against the incorrect type

Consider using "value + 0 " or Number (value) to force the cast to decimal

Boolean

When used on a conditions

When on complex conditions, it could be evaluated to false

Consider transforming the field to a boolean value

I have tested using our Istanbul release and Chrome as the browser.

Want to learn more about GlideRecords? Here are some great resources to check out:

Thanks to reid (Reid Murray) for the pointers