Did you know that XPath from XML has a distant cousin in the JSON world called JPath? Well, it’s actually supported by Business Central. Check out how to use it in this video:

In this video, Erik shares a handy holiday-themed tip for working with JSON in AL: how to use JPath (the JSON equivalent of XPath for XML) to quickly drill into a JSON structure and extract exactly the data you need, using Business Central’s built-in SelectToken function.
Working with JSON in AL: The Usual Approach
If you work with Business Central, you’re dealing with JSON all the time — from web services, API integrations, and various data structures. The typical approach Erik demonstrates in his videos is to process JSON structurally — navigating through objects and arrays step by step — because that’s the fastest way to extract data.
But sometimes you have a large JSON structure and you only need one specific value buried deep inside it. For those cases, there’s a much more elegant approach.
Introducing JPath and SelectToken
If you’ve worked with XML, you’re probably familiar with XPath — a query language for selecting nodes from an XML document. JSON has its own equivalent called JPath (JSON Path), and Business Central has built-in support for it through the SelectToken function.
SelectToken is a function available on any JsonToken. You pass it a JPath expression as text along with a var JsonToken, and it returns true if it finds a match. It’s a remarkably powerful way to reach deep into a JSON structure with a single expression.
Understanding JSON Structure Basics
Before diving into JPath, Erik offers a quick refresher on JSON structure:
- Curly braces
{ }denote a JSON object - Square brackets
[ ]denote a JSON array - Unlike XML, the root element in JSON doesn’t have a name — it’s just an object or array. This sometimes confuses people coming from an XML background, but that’s simply how JSON works.
The Microsoft Documentation Example
Erik highlights that Microsoft’s documentation for SelectToken actually includes a really good example. Consider this JSON structure:
{
"company": {
"employees": [
{ "id": "John", "salary": 7 },
{ "id": "Jane", "salary": 9 }
]
}
}
Here we have an object with a child called company, which contains employees — an array of objects, each with an id and a salary field.
Writing a JPath Query
The JPath expression to find John’s salary looks like this:
$.company.employees[?(@.id=='John')].salary
Let’s break this down:
$— represents the root of the JSON structure (remember, the root doesn’t have a name in JSON).company— navigate to thecompanychild object.employees— navigate to theemployeeschild (which is an array)[?(@.id=='John')]— apply a filter: the?means “apply a filter,” and@.id=='John'means “where theidproperty equals ‘John'”.salary— return thesalaryvalue from the matching element
The AL Code
Here’s how you use this in AL. The function takes a JSON token (used when the JSON could be anything — object, array, or value), an employee ID, and returns the salary:
procedure SelectEmployeeSalary(CompanyData: JsonToken; EmployeeId: Text): Decimal
var
SalaryToken: JsonToken;
Query: Text;
begin
Query := StrSubstNo(
'$.company.employees[?(@.id==''%1'')].salary',
EmployeeId);
CompanyData.SelectToken(Query, SalaryToken);
exit(SalaryToken.AsValue().AsDecimal());
end;
And calling this function is straightforward:
var
JsonObj: JsonObject;
begin
JsonObj.ReadFrom('{"company":{"employees":[{"id":"John","salary":7},{"id":"Jane","salary":9}]}}');
Message('%1', SelectEmployeeSalary(JsonObj.AsToken(), 'John'));
// Displays: 7
end;
Note that when you have a JsonObject but the function expects a JsonToken, you convert it using AsToken(). The JsonToken type is the most flexible — it can represent an object, array, or value — making it ideal for generic utility functions.
When to Use a JsonToken vs. JsonObject
Erik explains the distinction clearly:
- Use
JsonObjectwhen you know the structure is specifically an object - Use
JsonTokenwhen the structure could be anything — object, array, or value. It’s the generic base type.
SelectToken is available on both JsonToken and JsonObject, so you can use it in either context.
Performance Considerations
Erik provides an important caveat: SelectToken is not a performance function. Each call processes the entire JSON structure to find your result. If you need to extract many values from a large JSON structure, you’re better off navigating structurally (object by object, array by array) rather than calling SelectToken repeatedly. However, for grabbing a single value from a complex structure, it’s incredibly convenient.
Testing JPath Expressions: jsonpath.com
To help you build and test JPath expressions before putting them in your AL code, Erik recommends jsonpath.com. This interactive tool lets you:
- Paste in your JSON structure
- Write JPath expressions and see results instantly
- Experiment with array indexing (e.g.,
[0]for the first element,[:2]for the first two elements) - Test filters using the
?(@.property == 'value')syntax - Access complete documentation on JPath syntax
The site also includes examples demonstrating various JPath features like wildcards, recursive descent, array slicing, and more.
Bonus: The JsonTools Codeunit
The source code for this project also includes a handy utility codeunit for converting between AL records and JSON. Here’s the full JsonTools codeunit:
codeunit 74500 "Json Tools"
{
procedure Json2Rec(JO: JsonObject; Rec: Variant): Variant
var
Ref: RecordRef;
begin
Ref.GetTable(Rec);
exit(Json2Rec(JO, Ref.Number()));
end;
procedure Json2Rec(JO: JsonObject; TableNo: Integer): Variant
var
Ref: RecordRef;
FR: FieldRef;
FieldHash: Dictionary of [Text, Integer];
i: Integer;
JsonKey: Text;
T: JsonToken;
JsonKeyValue: JsonValue;
RecVar: Variant;
begin
Ref.OPEN(TableNo);
for i := 1 to Ref.FieldCount() do begin
FR := Ref.FieldIndex(i);
FieldHash.Add(GetJsonFieldName(FR), FR.Number);
end;
Ref.Init();
foreach JsonKey in JO.Keys() do begin
if JO.Get(JsonKey, T) then begin
if T.IsValue() then begin
JsonKeyValue := T.AsValue();
FR := Ref.Field(FieldHash.Get(JsonKey));
AssignValueToFieldRef(FR, JsonKeyValue);
end;
end;
end;
RecVar := Ref;
exit(RecVar);
end;
procedure Rec2Json(Rec: Variant): JsonObject
var
Ref: RecordRef;
Out: JsonObject;
FRef: FieldRef;
i: Integer;
begin
if not Rec.IsRecord then
error('Parameter Rec is not a record');
Ref.GetTable(Rec);
for i := 1 to Ref.FieldCount() do begin
FRef := Ref.FieldIndex(i);
case FRef.Class of
FRef.Class::Normal:
Out.Add(GetJsonFieldName(FRef), FieldRef2JsonValue(FRef));
FRef.Class::FlowField:
begin
FRef.CalcField();
Out.Add(GetJsonFieldName(FRef), FieldRef2JsonValue(FRef));
end;
end;
end;
exit(Out);
end;
local procedure FieldRef2JsonValue(FRef: FieldRef): JsonValue
var
V: JsonValue;
D: Date;
DT: DateTime;
T: Time;
begin
case FRef.Type() of
FieldType::Date:
begin
D := FRef.Value;
V.SetValue(D);
end;
FieldType::Time:
begin
T := FRef.Value;
V.SetValue(T);
end;
FieldType::DateTime:
begin
DT := FRef.Value;
V.SetValue(DT);
end;
else
V.SetValue(Format(FRef.Value, 0, 9));
end;
exit(v);
end;
local procedure GetJsonFieldName(FRef: FieldRef): Text
var
Name: Text;
i: Integer;
begin
Name := FRef.Name();
for i := 1 to Strlen(Name) do begin
if Name[i] < '0' then
Name[i] := '_';
end;
exit(Name.Replace('__', '_').TrimEnd('_').TrimStart('_'));
end;
local procedure AssignValueToFieldRef(var FR: FieldRef; JsonKeyValue: JsonValue)
begin
case FR.Type() of
FieldType::Code,
FieldType::Text:
FR.Value := JsonKeyValue.AsText();
FieldType::Integer:
FR.Value := JsonKeyValue.AsInteger();
FieldType::Date:
FR.Value := JsonKeyValue.AsDate();
else
error('%1 is not a supported field type', FR.Type());
end;
end;
}
This codeunit provides two key capabilities:
Rec2Json— converts any AL record to a JSON object, automatically handling field names (replacing special characters with underscores) and different field types including dates, times, and FlowFieldsJson2Rec— converts a JSON object back into an AL record by building a dictionary that maps JSON key names to field numbers, then assigning values through FieldRef
Here's how it's used in the page extension:
pageextension 74500 CustomerListExt extends "Customer List"
{
trigger OnOpenPage();
var
Json: Codeunit "Json Tools";
JsonObj: JsonObject;
C: Record Customer;
begin
JsonObj.ReadFrom('{ "No": "99999", "Name": "Yotube Demo","Invoice_Copies": "99999"}');
c := Json.Json2Rec(JsonObj, c);
message('%1', format(c));
end;
}
Summary
JPath with SelectToken is a powerful tool in your AL toolbox for working with JSON in Business Central. Here are the key takeaways:
SelectTokenlets you use JPath expressions to drill directly into any JSON structure and extract specific values- The
$symbol represents the root of your JSON structure - Use dot notation to navigate child elements and
[?(@.property == 'value')]syntax for filtering arrays - Use jsonpath.com to test your JPath expressions interactively before writing AL code
- Don't overuse it — for extracting many values from a large structure, structural navigation is more performant.
SelectTokenshines when you need just one or two values from deep within a complex structure.