The Zurich release has arrived! Interested in new features and functionalities? Click here for more

Maik Skoddow
Tera Patron
Tera Patron

The Driving Use Case: A Vision for Flexible Data Ingestion

 

The exploration of GlideJsonPath wasn't merely an academic exercise; it stemmed from a practical need to enhance our integration capabilities. Specifically, the goal was to provide a highly flexible mechanism for external partners to create Work Orders within our ServiceNow instance. Rather than enforcing a rigid, predefined payload structure on each partner, the vision was to empower them to send data in a format that best suited their systems, such as industry standards like TMF697 or their own native structures.

 

To achieve this, a transformation layer within ServiceNow was envisioned. The newly introduced GlideJsonPath  API was identified as the cornerstone of this layer. The plan was to configure partner-specific JSONPath expressions that would precisely extract and normalize the required data points from these varied incoming payloads, mapping them to the appropriate Work Order fields.

 

Initial tests focusing on extracting atomic values (e.g., a single string, number, or boolean) from individual JSON nodes using GlideJsonPath were promising and seemed to validate this approach. However, the challenges began to surface when the requirement expanded to extracting entire sub-structures from the JSON payload – for example, a list of related items or a complex object representing contact details. It was at this juncture that the output from GlideJsonPath started to deviate significantly from expectations, prompting a deeper and more systematic investigation into the API's behavior, the findings of which are detailed below.

 

 

Understanding JSONPath

 

The Standard

 

Before diving into ServiceNow's GlideJsonPath API, it's essential to understand what JSONPath is and what behavior one should typically expect. JSONPath is a query language for JSON, analogous to XPath for XML. It provides a way to select and extract parts of a JSON document using path expressions.

 

The definitive specification for JSONPath is now RFC 9535, published by the IETF. This document outlines the syntax, semantics, and expected behavior of JSONPath processors. You can find the full specification here: https://datatracker.ietf.org/doc/rfc9535/

 

JSONPath expressions use a notation similar to that used for accessing objects and arrays in JavaScript. Common elements include:

  • $ The root object or element.

  • . Dot notation for child operators.

  • [] Bracket notation for child operators (useful for names with special characters or for array indexing).

  • * Wildcard, selecting all members of an object or all items in an array.

  • .. Deep scan or recursive descent, searching for names at any level.

  • [index] Accessing a specific array element.

  • [start:end:step] Array slice operator.

  • [?(expression)] Filter expression, allowing selection based on criteria.

 

 

Testing JSONPath Expressions

 

To validate JSONPath expressions against a given JSON payload independently of ServiceNow's implementation, online tools can be invaluable. One popular and easy-to-use option is https://jsonpath.com/

 

This site allows you to paste your JSON data and test various JSONPath expressions, immediately seeing the results. This is an excellent way to confirm what the expected output should be according to common JSONPath engine behavior, which generally aims to align with the standard. Throughout this article, when I refer to "expected output," it will be based on the behavior demonstrated by such standard-compliant tools and the principles outlined in RFC-9535.

 

 

Basic Usage of GlideJsonPath - The Initial Promise

 

For the following examples, I'll use the following JSON payload. For ease of demonstration in this article, I'll define it as a system property, though in a real scenario, this might be retrieved from an integration:

 

{
  "nodeId":  24403649,
  "externalId": "MOD24403649_25050812533905",
  "shortDescription": "Leitungsumschaltung",
  "category": "Leitungsschaltung",
  "requestedStart": "2025-07-01T08:00:00Z",
  "contact": {
    "email": "Max.Müller@test.de",
    "firstName": "Max",
    "lastName": "Müller"
  },
  "characteristics": [
    {
      "name": "test1",
      "value": "value1"
    }, {
      "name": "test2",
      "value": "value2"
    }
  ]
}

 

 

To understand the issues I have encountered, let's first look at how GlideJsonPath is intended to be used for straightforward data extraction. The API is instantiated with a JSON string, and then its single primary method, read(), is called with a JSONPath expression to retrieve the desired data. In accordance with the documentation the read() method's should return any JavaScript object. Its type can vary depending on what the JSONPath expression resolves to – it could be a single value (string, number, boolean), an array of values, or even a JSON representation of an object.

 

var _objJsonPathEvaluator = 
	new GlideJsonPath(gs.getProperty('Example JSON Payload'));

gs.print(_objJsonPathEvaluator.read('$.nodeId'));
gs.print(_objJsonPathEvaluator.read('$.shortDescription'));

MaikSkoddow_0-1749042928999.png

 

 

 

Analysis of the GlideJsonPath Issues

 

At first glance, the result looks as expected, and most articles on the topic I could find so far remain at this stage. However, are the types also reflecting the expectations?

 

var _objJsonPathEvaluator = 
	new GlideJsonPath(gs.getProperty('Example JSON Payload'));

gs.print(typeof _objJsonPathEvaluator.read('$.nodeId'));
gs.print(typeof _objJsonPathEvaluator.read('$.shortDescription'));

MaikSkoddow_1-1749043325052.png

 

 

 

This definitely does not meet my expectations! I would have expected "number" in the first line and "string" in the second! I remembered that arrays are also returning "object" for a typeof check. So let's test this:

 

var _objJsonPathEvaluator = 
	new GlideJsonPath(gs.getProperty('Example JSON Payload'));

function evaluateJSONPath(strJsonPath) {
	var _objValue = _objJsonPathEvaluator.read(strJsonPath);

	return Array.isArray(_objValue) + ': ' + _objValue.length;
}

gs.print(evaluateJSONPath('$.nodeId'));
gs.print(evaluateJSONPath('$.shortDescription'));

MaikSkoddow_2-1749044311534.png

 

 

The method read() appears to return all results in an array of length 1. So, you should now be able to identify the correct data type in the first element of this array, right?

 

var _objJsonPathEvaluator = 
	new GlideJsonPath(gs.getProperty('Example JSON Payload'));

function evaluateJSONPath(strJsonPath) {
	var _objValue = _objJsonPathEvaluator.read(strJsonPath);

	return typeof _objValue[0];
}

gs.print(evaluateJSONPath('$.nodeId'));
gs.print(evaluateJSONPath('$.shortDescription'));

MaikSkoddow_3-1749044598870.png

 

 

Cool, it seems to work for atomic data types. Now, let's address the two tricky cases in which an array or an object is expected as the data structure:

 

var _objJsonPathEvaluator = 
	new GlideJsonPath(
		gs.getProperty('Example JSON Payload')
	);

function evaluateJSONPath(strJsonPath) {
	var _objValue = 
		_objJsonPathEvaluator.read(strJsonPath);

	return typeof _objValue[0];
}

gs.print(evaluateJSONPath('$.characteristics'));

MaikSkoddow_4-1749044940547.png

 

 

 

This behavior is quite problematic. Rather than mapping the array to a proper JavaScript type, the output appears to reflect internal Java representations used by the underlying implementation of the GlideJsonPath class. As an experienced ServiceNow developer, you may be aware of the j2js() helper method, which is intended to convert Java data types to their corresponding JavaScript equivalents. Unfortunately, this approach also fails to produce the desired result:

 

var _objJsonPathEvaluator = 
	new GlideJsonPath(
		gs.getProperty('Example JSON Payload')
	);

function evaluateJSONPath(strJsonPath) {
	var _objValue = 
		_objJsonPathEvaluator.read(strJsonPath);

	return typeof j2js(_objValue[0]);
}

gs.print(evaluateJSONPath('$.characteristics'));

MaikSkoddow_0-1749045673401.png

 

 

 

The same happens when trying to extract the "contact" branch of the payload! I was quite frustrated and disappointed by the flawed implementation at this point.

 

So, let's go to the beginning to see what the pure print output would be.

 

var _objJsonPathEvaluator = 
	new GlideJsonPath(
		gs.getProperty('Example JSON Payload')
	);

function evaluateJSONPath(strJsonPath) {
	var _objValue = 
		_objJsonPathEvaluator.read(strJsonPath);

	return _objValue[0];
}

gs.print(evaluateJSONPath('$.characteristics'));
gs.print(evaluateJSONPath('$.contact'));

MaikSkoddow_1-1749046150364.png

 


 

 

 

Now, things get really strange. Although you cannot work with object structures anymore, the pure print output resembles something like an array and an object. This is because the toString() method was implemented in the internal Java object and is implicitly executed when data is converted to a string. In this case, it is executed when gs.print() is called.

 

The real drama, however, is the toString() conversion of the "contact" object, because the plain text variant should look like this (with quotation marks and colons):

 

{"email":"Max.Müller@test.de", "firstName":"Max", "lastName":"Müller"}

 

 

That means you cannot use JSON.parse() as a workaround to retrieve JavaScript data types for object structures in the payload - only for arrays! 

 

Assuming I haven’t missed any critical detail, I must, at this stage, accept the current constraints and work with what is supported - atomic data types and arrays.

 

However, I hope that the ServiceNow engineering team will address this issue and implement a reliable conversion from the Java world to the JavaScript world. Even better would be the integration of a mature and well-tested JavaScript library for this purpose, for example: https://github.com/ashphy/jsonpath-js 

 

 

 

1 Comment