Json Support
ToolBox scripts can build, parse, query, and serialize JSON using four types that mirror the Business Central JSON API: JsonObject, JsonArray, JsonToken, and JsonValue. This makes it straightforward to work with REST responses, configuration data, and structured payloads.
The Four Json Types
| Type | Represents |
|---|---|
JsonObject |
A { } object: a set of key/value pairs |
JsonArray |
A [ ] array of tokens (0-based) |
JsonToken |
Any JSON node — an object, array, or value |
JsonValue |
A scalar leaf: string, number, boolean, or null |
Declare them like any other variable:
var
Obj: JsonObject;
Arr: JsonArray;
Token: JsonToken;
Value: JsonValue;
Parsing and Serializing
ReadFrom
jsonobject.ReadFrom(json: Text): Boolean
jsonobject.ReadFrom(inStream: InStream): Boolean
Parses JSON from a text string or an InStream. Returns false when the text is not valid JSON. JsonObject, JsonArray, and JsonToken all support ReadFrom.
WriteTo
jsonobject.WriteTo(var result: Text): Boolean
jsonobject.WriteTo(outStream: OutStream): Boolean
Serializes to compact JSON, either into a Text variable or to an OutStream.
var
Obj: JsonObject;
Json: Text;
begin
if Obj.ReadFrom('{"name":"Contoso","active":true,"orders":5}') then begin
Obj.WriteTo(Json);
Message('Round-tripped: %1', Json);
end else
Message('Invalid JSON');
end;
JsonObject
Structural Members
| Member | Returns | Description |
|---|---|---|
add(Key, Value) |
Errors when the key already exists | |
contains(Key) |
Boolean | Whether the key exists |
get(Key, var Token) |
Boolean | false when the key is missing |
remove(Key) |
Boolean | false when the key is missing |
replace(Key, Value) |
Boolean | false when the key is missing |
selecttoken(Path, var Token) |
Boolean | JPath query, e.g. customer.name |
astoken() |
JsonToken | Wrap the object as a token |
clone() |
JsonObject | Deep copy |
keys() |
List of [Text] | All property names |
values() |
List of [JsonToken] | All property values (copies) |
var
Obj: JsonObject;
Key: Text;
begin
Obj.ReadFrom('{"name":"Contoso","orders":5}');
foreach Key in Obj.Keys() do
Message('%1 = %2', Key, Obj.GetText(Key, true));
end;
Typed Getters
In addition to the structural members, JsonObject offers typed getters that read a key and return it already converted. Each getter has a second overload with a DefaultIfNotFound Boolean — pass true to get a default value when the key is missing instead of an error.
jsonobject.GetText(Key: Text): Text
jsonobject.GetText(Key: Text, DefaultIfNotFound: Boolean): Text
| Getter | Returns |
|---|---|
GetText |
Text |
GetInteger |
Integer |
GetDecimal |
Decimal |
GetBoolean |
Boolean |
GetObject |
JsonObject |
GetArray |
JsonArray |
GetValue |
JsonValue |
GetToken |
JsonToken |
var
Obj: JsonObject;
Name: Text;
Orders: Integer;
Note: Text;
begin
Obj.ReadFrom('{"name":"Contoso","orders":5}');
Name := Obj.GetText('name'); // Contoso
Orders := Obj.GetInteger('orders'); // 5
Note := Obj.GetText('note', true); // '' — key missing, default returned
Message('%1 has %2 orders. Note: [%3]', Name, Orders, Note);
end;
Differs from standard AL: The typed getters (
GetText,GetInteger, …) with theDefaultIfNotFoundoverload are a ToolBox convenience — they are not on the platformJsonObject.
Building an Object
var
Obj: JsonObject;
Json: Text;
begin
Obj.Add('name', 'Contoso');
Obj.Add('active', true);
Obj.Add('orders', 5);
Obj.WriteTo(Json);
Message(Json); // {"name":"Contoso","active":true,"orders":5}
end;
JsonArray
JsonArray is 0-based — the first element is at index 0. This matches the platform JSON API and differs from ToolBox List collections, which are 1-based.
Members
| Member | Returns | Description |
|---|---|---|
add(Value) |
Append an element | |
count() |
Integer | Number of elements |
get(Index, var Token) |
Boolean | 0-based; false when out of range |
indexof(Value) |
Integer | -1 when not found |
insert(Index, Value) |
Boolean | 0-based |
removeat(Index) |
Boolean | 0-based |
set(Index, Value) |
Boolean | 0-based; replace an element |
selecttoken(Path, var Token) |
Boolean | JPath, e.g. $[0].name |
astoken() |
JsonToken | Wrap the array as a token |
clone() |
JsonArray | Deep copy |
JsonArray also has the same typed getters as JsonObject, but indexed by position: GetText(Index), GetInteger(Index), GetDecimal(Index), GetBoolean(Index), GetObject(Index), GetArray(Index), GetValue(Index), GetToken(Index) — each with an optional DefaultIfNotFound overload.
var
Arr: JsonArray;
I: Integer;
begin
Arr.Add('Alice');
Arr.Add('Bob');
Arr.Add('Charlie');
for I := 0 to Arr.Count() - 1 do // 0-based
Message('%1: %2', I, Arr.GetText(I));
end;
A JsonArray can also be iterated with foreach. The loop variable must be a JsonToken:
var
Arr: JsonArray;
Tok: JsonToken;
begin
Arr.ReadFrom('["Alice","Bob"]');
foreach Tok in Arr do
Message(Tok.AsValue().AsText());
end;
JsonToken
A JsonToken is the general node type — what get/selecttoken fill and what array elements are. Test its shape and cast it to the concrete type:
| Member | Returns | Description |
|---|---|---|
isobject() / isarray() / isvalue() |
Boolean | Test the token's kind |
asobject() |
JsonObject | Errors when not an object |
asarray() |
JsonArray | Errors when not an array |
asvalue() |
JsonValue | Errors when not a value |
selecttoken(Path, var Token) |
Boolean | JPath query |
clone() |
JsonToken | Deep copy |
readfrom / writeto |
Parse / serialize (as above) |
var
Root: JsonToken;
Names: JsonToken;
NameToken: JsonToken;
I: Integer;
begin
Root.ReadFrom('{"names":["Alice","Bob"]}');
if Root.AsObject().Get('names', Names) then
for I := 0 to Names.AsArray().Count() - 1 do begin
Names.AsArray().Get(I, NameToken);
Message('%1', NameToken.AsValue().AsText());
end;
end;
JsonValue
A JsonValue is a scalar leaf. Read it with the As* accessors, which convert to the requested type:
| Accessor | Returns | Accessor | Returns | |
|---|---|---|---|---|
AsText |
Text | AsDateTime |
DateTime | |
AsCode |
Code | AsDuration |
Duration | |
AsInteger |
Integer | AsOption |
Integer | |
AsBigInteger |
BigInteger | AsByte |
Integer | |
AsDecimal |
Decimal | AsChar |
Integer | |
AsBoolean |
Boolean | IsNull |
Boolean | |
AsDate |
Date | IsUndefined |
Boolean | |
AsTime |
Time | AsToken |
JsonToken |
Write a value with:
| Member | Description |
|---|---|
setvalue(Value) |
Set any scalar value |
setvaluetonull() |
Set the value to JSON null |
setvaluetoundefined() |
Set the value to undefined |
var
Obj: JsonObject;
Value: JsonValue;
Big: BigInteger;
begin
Obj.ReadFrom('{"id":9000000000,"deleted":null}');
Value := Obj.GetValue('id');
Big := Value.AsBigInteger(); // large numbers need BigInteger
Value := Obj.GetValue('deleted');
if Value.IsNull() then
Message('deleted is null; id = %1', Big);
end;
Chaining Calls
Members can be chained in one expression as long as the chain starts with a variable: Root.AsObject().GetArray('items').Count() or Obj.GetText('name').ToUpper(). A chain cannot start from a literal. A chained receiver is a temporary value, so a method that rebinds its receiver (ReadFrom, SetValue) on a chained result does not write back to the source.
All four JSON types are reference types: assigning one variable to another, or passing a value to a procedure without var, shares the same underlying JSON. Use Clone() for an independent copy.
Querying with SelectToken (JPath)
selecttoken runs a JPath expression to reach nested data in one call. It is available on JsonObject, JsonArray, and JsonToken.
var
Root: JsonToken;
City: JsonToken;
begin
Root.ReadFrom('{"customer":{"address":{"city":"Redmond"}}}');
if Root.SelectToken('customer.address.city', City) then
Message('City: %1', City.AsValue().AsText()); // Redmond
end;
Example: Reading a REST Response
The Rest Client codeunit returns JSON responses as a JsonToken, so JSON handling and REST calls compose directly.
var
Client: Codeunit "Rest Client";
Response: JsonToken;
Results: JsonArray;
Item: JsonToken;
I: Integer;
begin
Client.SetBaseAddress('https://api.example.com');
Response := Client.GetAsJson('/products');
Results := Response.AsObject().GetArray('items');
for I := 0 to Results.Count() - 1 do begin
Results.Get(I, Item);
Message('Product: %1', Item.AsObject().GetText('name'));
end;
end;