Erik's ToolBox Documentation
Index

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 the DefaultIfNotFound overload are a ToolBox convenience — they are not on the platform JsonObject.

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;